高效編寫代碼的方法(四):屬性

實例變量

@interface Teacher : NSObject{
    NSString *name;
    NSString *workNumber;
}

定義實例變量的方法,說實話我在OC中用的不多,一般都是直接聲明成一個屬性。

這種方式存在一個問題就是對象的定義是在編譯階段的,如果需要訪問某個對象,結果都是編譯器直接去內存區該對象存儲的地方來進行訪問。這樣的方式看來沒什么問題,但是但我想添加一個新實例變量在name上方的時候,就不起作用了。
比如新聲明如下:

@interface Teacher : NSObject{
    NSDate *birthDay;
    NSString *name;
    NSString *workNumber;
}

以上是OC中常見的定義實例變量的方法。
之前指向name的offset現在指向了birthDay,如果不進行重新編譯,那么數據訪問就會錯亂。
為了解決這個問題,OC采用的方法是存儲這個offset,當變量改變的時候,offset也進行相應的升級。因此每次訪問的時候,offset都是最新的,不會造成數據錯亂。

屬性

我的理解是,屬性是在實例變量的基礎上,多封裝了一層訪問方法及變量屬性。
關于Setter和Getter方法也不再贅述。
一般定義完屬性之后,屬性的訪問方法都是系統自動生成的,雖然在你的代碼中看不到,但如果不自己重寫,她們也是存在的。
如果不想使用系統生成的訪問方法,你可以自己實現方法。只實現其中一個方法的話,比如重寫Getter,那么Setter還是系統幫你寫好的方法。
如果真的不想使用任何系統生成的訪問方法,那么需要使用@dynmaic關鍵字, 這個關鍵字我們可以在使用CoreData生成類文件的時候看到,類的所有屬性都是@dynamic打頭。表示屬性的訪問方法是動態生成的。

屬性的內存引用修飾

  • assign 對一些特定的純量對象類使用,比如CGFloat,NSInteger
    下面會牽扯到一些引用計數的知識,假設A有一個屬性b,C也有一個屬性d
  • strong
    賦值時:A.b = C.d,此時內存中b = d,b引用計數+1 = 2
    A持有b,C持有d, C被釋放時,d沒有釋放,因為b=d, A持有b。
  • weak
    賦值時:A.b = C.d,此時內存中b = d,因為聲明為weak,b引用計數+0 = 1
    A不持有b,C持有d, C被釋放時,d釋放,因為b=d, A不持有b,b的計數清零。
  • unsafe_unretained
    基本和Weak一樣,不持有對象,與Weak不同之處在于:
    假設A中有一個unsafe_unretained的b,A被釋放后,b卻不會被釋放,可能會造成野指針的問題。
  • copy
    與strong類似,表示持有對象,不同的是實際上是進行了一次復制(對象必須遵守NSCopy協議)。

關于Copy和Strong的不同,下面舉個例子:

@interface Teacher : NSObject

@property (nonatomic, strong) NSMutableArray *sampleArray;
//@property (nonatomic, copy) NSMutableArray *sampleArray;
@end

聲明Teacher有一個可變數組對象為strong
以上兩行屬性聲明我們分別是Copy類和Strong類,然后分別執行以下測試代碼:

    NSMutableArray *array = [@[@"a",@"b",@"c"]mutableCopy];
    Teacher *tea = [[Teacher alloc] init];
    tea.sampleArray= array;
    [array addObject:@"d"];
    NSLog(@"ArrayData:%@ \n teacherArrayPosition:%p  \n reallyArrayPostion:%p",tea.sampleArray,tea.sampleArray,array);

Strong的時候輸出如下:


Strong

Copy的時候輸出如下:


Copy

可以看到Strong知識做了一次指針的引用,而Copy是真正重新開辟了一塊內存空間去存儲變量。
所以使用Strong去修飾的缺點在于,對象可能會因為引用對象的改變而發生改變,就像代碼測試所示,而Copy不會。

  • assign和retain
    其實我覺得這兩個是寫法遺留的問題,其實assign也可以用于指針對象,作用相當于weak,retain相當于strong。
    assign和retain在Xcode4.3之前使用,4.3之后就還是使用weak和strong吧。

屬性與方法的統一

當我們在屬性中采用了特殊的修飾符的話,比如Copy,那我們可能需要在屬性的訪問方法中對其進行處理。
比如以下聲明:

@property (nonatomic, copy) NSString *name;

- (instancetype)initWithName:(NSString *)name;

在實現文件中我們就需要這么寫來保持和修飾的統一性:

- (void)setName:(NSString *)name
{
    _name = [name copy];
}

- (instancetype)initWithName:(NSString *)name
{
    if (self = [super init]) {
        _name = [name copy];
    }
    return self;
}

但實際上來說,我們并不需要對遵守NSCopying協議的對象(比如NSArray,NSString)做這么麻煩的處理,系統會自動幫我們處理好copy,所以以上只是舉個例子。如果想讓自定義對象支持copy,還是需要上面這么寫,還要讓對象遵守NSCopying協議并實現copy方法。

還有一個需要注意的地方在于,不要再init方法中調用自己的屬性訪問方法。
也就是上面代碼使用_name = [name copy]的原因。想要深究原因的話可以看看下面這篇文章:
為什么不要在init和dealloc函數中使用accessor

總結

  • 1 @property語法提供了一種數據的封裝方式(生成Setter和Getter)
  • 2 在寫屬性修飾之前仔細思考該如何更合適的去修飾它(內存方面循環引用等問題)
  • 3 屬性與方法統一
  • 4 在iOS端使用nonatomic
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容