【iOS基礎篇】---@property 後面的修飾符們

雖然寫了很多年的iOS代碼,但是很多東西沒有深入理解,或者當時理解了,後來不用又慢慢又忘了。所以抽空整理一份資料,以備自己以後查找。也希望看到的小夥伴批評指正。
這篇文章主要寫@property後面的修飾符。

1、assign、unsafe_unretained和weak

assign:既可以修飾對象,也可以修飾基本類型。unsafe_unretained與assign同義。assign修飾對象會產生野指針的問題,修飾的對象釋放後,指針不會自動置成nil,此時再向對象發消息程序會崩潰。

weak:只能修飾對象,如果修飾基本數據類型會報錯:Property with 'weak' attribute must be of object type. weak修飾的對象釋放後(引用計數變爲0),指針會被自動置爲nil,之後再向該對象發消息也不會崩潰(向nil發送任何消息都不會崩潰)。weak適用於delegate和block等引用類型,不會導致野指針問題,也不會循環引用,非常安全。

總結:assign、weak和unsafe_unretained,修飾的屬性,再調用set方法時是不會增加引用計數的。可以理解爲set方法只是簡單的賦值。也就是棧上的變量的賦值。驗證代碼如下:

@property(nonatomic,assign) NSObject *property;
//在代碼實現中
NSObject *object = [[NSObject alloc] init];
NSLog(@"\n 引用計數 = %lu \n ", (unsigned long)[object retainCount]);
self.property = object;
NSLog(@"\n 引用計數 = %lu \n ", (unsigned long)[object retainCount]);

輸出結果
2019-08-05 02:02:08.475192+0800 test4[89384:3597709]
引用計數 = 1
2019-08-05 02:02:08.475320+0800 test4[89384:3597709]
引用計數 = 1

所以assign、weak和unsafe_unretained修飾的對象,默認生成的set方法,應該如下方法一所示:
-(void)setProperty:(NSObject *)property{

_property = property;
//weak可能還有其他一些操作

}

而retain和strong修飾的對象,大概生成的set方法如下方法二所示:
-(void)setProperty:(NSObject *)property{

[property retain];
[_property release];
_property = property;

}

注意:如果自己實現了set方法,則這些關於存取方法的修飾符就不再起作用了。比如,你雖然用assign修飾了對象,但是自己實現了set方法二,則assign會被忽略。

2、retain和strong

在蘋果的文檔裏有這樣一句話:
// The following declaration is a synonym for: @property(retain) MyClass *myObject;
@property(strong) MyClass *myObject;

所以在ARC下他們是等價的。

通常在ARC環境裏,不會用assign修飾對象,因爲有了weak,再用assign就是自找麻煩。總之在ARC環境裏用strong代替retain強引用對象,用weak弱引用對象,用assign修飾基本數據類型。

3、atomic和nonatomic

atomic和nonatomic的區別在於:系統自動生成的 getter/setter 方法不一樣。(如果你自己寫 getter/setter,那 atomic/nonatomic/retain/assign/copy 這些關鍵字只起提示作用,寫不寫都一樣)

對於atomic的屬性,系統生成的 getter/setter 會保證 get、set 操作的完整性,不受其他線程影響。比如,線程 A 的 getter 方法運行到一半,線程 B 調用了 setter:那麼線程 A 的 getter 還是能得到一個完好無損的對象。

而nonatomic就沒有這個保證了。所以,nonatomic的速度要比atomic快。
nonatomic系統合成的方法大概如下:

@property(nonatomic, retain) UITextField *userName;
//系統生成的代碼如下:

- (UITextField *) userName {
    return userName;
}

- (void) setUserName:(UITextField *)userName_ {
    [userName_ retain];
    [userName release];
    userName = userName_;
}

atomic系統合成的方法大概如下:

//@property(retain) UITextField *userName;
//系統生成的代碼如下:

- (UITextField *) userName {
    UITextField *retval = nil;
    @synchronized(self) {
        retval = [[userName retain] autorelease];
    }
    return retval;
}

- (void) setUserName:(UITextField *)userName_ {
    @synchronized(self) {
      [userName release];
      userName = [userName_ retain];
    }
}

假設有一個 atomic 的屬性 "userName",如果線程 A 調[self setUserName:@"A"],線程 B 調[self setUserName:@"B"],線程 C 調[self userName],那麼所有這些不同線程上的操作都將依次順序執行——也就是說,如果一個線程正在執行 getter/setter,其他線程就得等待。因此,屬性 setUserName 是讀/寫安全的。
但是,如果有另一個線程 D 同時在調[userName release],那可能就會crash,因爲 release 不受 getter/setter 操作的限制。也就是說,這個屬性只能說是讀/寫安全的,但並不是線程安全的,因爲別的線程還能進行讀寫之外的其他操作。線程安全需要開發者自己來保證。
如果 userName 屬性是 nonatomic 的,那麼上面例子裏的所有線程 A、B、C、D 都可以同時執行,可能導致無法預料的結果。如果是 atomic 的,那麼 A、B、C 會串行,而 D 還是並行的。

4、readwrite和readonly

readwrite:默認的屬性修飾符。會合成get和set方法。

readonly:只會生成get方法。所以如果obj用readonly修飾,則self.obj = xxx;編譯時會報錯提示:“Assignment to readonly property”。

5、copy屬性修飾符

copy:只能修飾對象類型,不能修飾基礎數據類型(用了會報編譯時錯誤)。用copy修飾的對象,必須實現
NSCopying協議也就是實現方法-(id)copyWithZone:(nullable NSZone *)zone。合成的set方法中會調用這個方法。這裏也能看出copy,和retain不一樣的地方。

至於copy是深拷貝還是淺拷貝完全是看copyWithZone的實現方式。也就是說copy和深拷貝和淺拷貝沒有關係!
另外對象的mutableCopy方法,也是要對象滿足NSMutableCopying協議,實現- (id)mutableCopyWithZone:(nullable NSZone *)zone方法。如果不實現次方法會運行時報錯!

總結

修飾符其實就是告訴編譯器怎麼來合成存取方法的。有些修飾符需要自己另外實現一些方法,比如copy,這裏就能定製一些自己的東西,比如實現真正的多層深拷貝等等。

參考:
https://www.jianshu.com/p/728...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章