實例變量
@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的時候輸出如下:
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