讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 12.11 屬性的內存管理策略 >

12.11 屬性的內存管理策略

在Objective-C中,@property聲明(參見第10章)包含了一個內存管理策略語句,後面跟著相應的setter訪問器方法。意識到這一點並知道如何將這種策略語句轉換為Swift是很有必要的。

比如,之前曾提到過UIViewController會保持其view(其主視圖)。我是怎麼知道的呢?這是@property聲明告訴我的:


@property(null_resettable, nonatomic, strong) UIView *view;
  

關鍵字strong表示setter會保持接收到的UIView對象。這個聲明轉換為Swift後並不會向變量添加任何特性:


var view: UIView!
  

在Swift中,默認做法是指向引用對像類型的變量是個強引用,即持久化引用。這意味著它會保持對象。這樣就可以從這個聲明中推斷出,UIViewController會保持其view。

對於Cocoa屬性來說,其內存管理策略有:

strong,retain(Swift中沒有等價之物)

默認值。這兩個關鍵字彼此等價;retain源自ARC出現之前。為該屬性賦值會保持接收到的值並釋放現有值。

copy(Swift中沒有等價之物,或@NSCopying)

與strong和retain相同,只不過setter會通過向其發送copy複製接收到的值;進來的值必須是使用了NSCopying的對象類型,從而確保這麼做是可行的。副本(已經增加了保持計數值)會成為新值。

weak(Swift weak)

一個ARC弱引用。接收到的值不會被保持,不過如果在我們不知情的情況下銷毀了,那麼ARC會將nil替換為該屬性的值,其類型必須是聲明為var的Optional。

assign(Swift unowned(unsafe))

無內存管理。該策略源自ARC出現之前,本身就是不安全的(因此,轉換為Swift後會有額外的unsafe警告):如果被引用的對象銷毀了,那麼該引用就會變成一個野指針,後面如果使用它就會導致應用崩潰。

你可能很想瞭解關於copy策略的一些內容,因為之前並沒有介紹過。當不變類有可變子類時(比如,NSString與NSMutableString,以及NSArray與NSMutableArray;參見第10章),Cocoa就會使用該策略。它解決了setter調用者傳遞可變子類對象的風險。稍微想一下就知道這是可能的,因為根據多態的替換法則(參見第4章),在需要一個類的實例時,我們可以傳遞其子類的實例。不過,這麼做可能不太好,因為現在調用者會保持著對傳遞進來的值的引用,因為它是可變的,因此後面可能會在我們不知情的情況下發生變化。為了防止這種情況的發生,setter會調用傳遞進來對象的copy;這會創建新的實例,它與所提供的對象不同,並且屬於不可變類。

在Swift中,這個問題基本不會出現在字符串與數組上,因為在Swift這一邊,它們是值類型(結構體),賦值時會被複製,然後才作為參數傳遞,或作為返回值被接收。這樣,Cocoa的NSString與NSArray屬性聲明在轉換為Swift的String與Array屬性聲明時,它們並不需要與Objective-C copy對應的任何特殊標記。不過,不會自動從Swift結構體橋接的Cocoa類型是會顯示一個標記的,即@NSCopying。比如,在Swift中,UILabel的attributedText屬性聲明如下所示:


@NSCopying var attributedText: NSAttributedString?
  

NSAttributedString有一個可變子類NSMutableAttributedString。你可能已經將這個特性字符串配置為了NSMutableAttributedString,現在要將UILabel的attributedText賦給它。UILabel並不希望你保持一個對該可變字符串的引用並修改它,因為這會在不使用setter的情況下改變屬性值。這樣,它會複製傳遞進來的值,確保它是一個不同的可變NSAttributedString。

你也可以在自己的代碼中這麼做,並且也願意這麼做。如果類中有一個NSAttributed-String實例屬性,那麼可以將其標識為@NSCopying,對於其他的可變/不變成員對也是類似的,比如,NSIndexSet、NSParagraphStyle及NSURLRequest等。只提供@NSCopying標識即可;Swift會強制應用copy策略,並且在為該屬性賦值時進行實際的複製動作。

有時,你希望自己的類擁有改變屬性值的能力,同時又想防止外部傳遞進來可變的值,那麼就需要在其前面加上一個私有的計算屬性門面,它會將其轉換為相應的可變類型:


class StringDrawer {
    @NSCopying var attributedString : NSAttributedString!
    private var mutableAttributedString : NSMutableAttributedString! {
        get {
            if self.attributedString == nil {return nil}
            return NSMutableAttributedString(
                attributedString:self.attributedString)
        }
        set {
            self.attributedString = newValue
        }
    }
}
  

@NSCopying只能用於類的實例屬性,結構體與枚舉是不行的,並且只能用在使用了Foundation的場合中,這是因為NSCopying協議定義在Foundation中,標記為

@NSCopying的變量類型都需要使用該協議。