修飾符是編譯器的行為:
類型 | 類型關鍵字 |
---|---|
原子性 | atomic , nonatomic |
可讀寫 | readwrite、readonly、setter、getter |
內存語義 | assign、weak、unsafe_unretained、retain、strong、copy |
允許空值 | (nullable、_Nullable 、__nullable)、(nonnull、_Nonnull、__nonnull)、(null_unspecified、_Null_unspecified 、__null_unspecified)、null_resettable |
允許空值 | (nullable,nonnull) ,( _Nullable, _Nonnull) ,(__nullable, __nonnull) |
原子性:
atomic : 原子性,線程安全的,但不包括操作和訪問(比如集合的相關操作) 性能不好
nonatomic : 非原子性,不寫默認也是非原子性線程不安全,性能好 |
例如:
@property(atomic) NSString *name
@property(nonatomic) NSString *name
集合類型的增刪不是線程安全的,因為被atomic修飾的屬性只是對get/set方法加鎖,可參考源碼:
setter:
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
//...
//源碼太長,刪掉了不相關的代碼
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
//...
getter:
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
- 可讀寫:
readwrite : 可讀寫,默認行為,編譯器會生成對應的get/set方法
readonly : 只讀,編譯器只會生成get方法
setter : 用指定的方法名替換系統生成的set方法
getter : 用指定的方法名替換系統生成的set方法
例如:
@property(readwrite) NSString *name;
@property(nonatomic,setter=setWithName:) NSString *str
@property(atomic,getter=isAppStr)NSString *appStr
可以通過clang -rewrite-objc xxx.m 查看編譯后的的cpp文件
內存語義:
assign : 修飾值類型,如果用來修飾對象類型在使用處編譯器警告,該對象的空間釋被放了,會變成懸垂指針|
weak : ARC下才能使用,修飾對象類型,不會使引用計數+1,如果修飾值類型編譯器會報錯??
retain : 修飾對象類型,會使引用計數+1,如果修飾值類型編譯器會報錯??
strong : ARC下才能使用,修飾對象類型
copy : 修飾block,字符串,集合類型
例如:@property(copy) NSString *name
assign:
- 修飾值類型
- 如果修飾對象類型不會報錯,使用的時候會編譯器會警告,警告信息如下:
??
Assigning retained object to unsafe property; object will be released after assignment
對象創建完立刻會被系統自動回收,會變成懸垂指針,奔潰
weak:
- ARC模式下使用
- 修飾對象類型
- 修飾非對象類型報錯,報錯信息如下:
??Property with 'weak' attribute must be of object type - 修飾block屬性,編譯器警告,警告信息如下:
??Assigning block literal to a weak property; object will be released after assignment
retain:
- MRC下使用
- 修飾對象類型
- 引用計數+1
- 修飾非對象類型報錯,報錯信息如下:
??Property with 'retain (or strong)' attribute must be of object type - 修飾block,警告如下:
??Retain'ed block property does not copy the block - use copy attribute instead
strong:
- ARC模式下使用(代替retain)
- 修飾對象類型,block
- 引用計數+1
- 修飾非對象類型報錯,報錯信息如下:
??Property with 'retain (or strong)' attribute must be of object type
copy:
- 修飾字符串,block 引用計數不會+1
- 修飾,集合類型會有不同的結果:
詳情參考iOS淺拷貝和深拷貝
歷史原因: MRC模式下,retain修飾block 相當于assign,所以必須用copy修飾block
ARC模式下:strong修飾block沒有問題,相當于用copy修飾
你應該用copy來修飾block屬性。為了追蹤原始作用域捕獲的狀態,block需要被拷貝。當使用Automatic Reference Counting你就不需要擔心這些,因為這會自動發生,但是屬性attribute命名的最佳實踐應該是能夠說明它的必然行為。
是否允許空值
以下三組修飾詞效果都一樣,區別是放的位置不一樣 |
---|
nullable,nonnull |
_Nullable, _Nonnull |
__nullable, __nonnull |
例如,修飾屬性:
@property(nonatomic,copy,nullable)NSString * stringA;
@property(nonatomic,copy)NSString * _Nullable stringB;
@property(nonatomic,copy)NSString * __nullable stringC;
修飾方法參數:
-(nullable NSString *)stringWithApplist:(nonnull NSString *)string;
-(NSString * _Nullable)stringWithAppInfo:(NSString * _Nullable)string;
-(NSString * __nullable)stringWithAppMemory:(NSString * __nullable)string;
- 以下情況不能使用nullable,nonnull
二級指針(id*),C類型的指針(char *) 只能用__nullable, _Nullable
-(nullable NSError *)error:( NSError * __nullable *)error;
char *__nullable appNameWithCString(char * _Nullable name);// C 函數
局部作用域聲明變量:
NSString * _Nullable name;//函數中聲明的局部變量
成員變量:
@interface APPList (){
NSString * _Nonnull _appName;//成員變量
}
@end
為了方便起見,建議都用_Nullable, _Nonnull
- 區域審核:
NS_ASSUME_NONNULL_BEGIN
...
在這些區域內,任何簡單的指針類型都將被假定為nonnull,您只需要具體指定那些允許空值
NS_ASSUME_NONNULL_END`
文檔中的例子:
NS_ASSUME_NONNULL_BEGIN
@interface AAPLList : NSObject <NSCoding, NSCopying>
// ...
- (nullable AAPLListItem *)itemWithName:(NSString *)name;
- (NSInteger)indexOfItem:(AAPLListItem *)item;
@property (copy, nullable) NSString *name;
@property (copy, readonly) NSArray *allItems;
// ...
@end
NS_ASSUME_NONNULL_END
// --------------
self.list.name = nil; // okay
AAPLListItem *matchingItem = [self.list itemWithName:nil]; // warning!
如果設置了, nonnull, _Nonnull, __nonnull當我們設置nil值的時候編譯器會警告:
??Null passed to a callee that requires a non-null argument
至于__nullable, __nonnull歷史原因,詳情參考文檔
此功能最初是在Xcode 6.3中使用關鍵字__nullable和__nonnull發行的。由于與第三方庫的潛在沖突,我們已在Xcode 7中將它們更改為您在此處看到的_Nullable和_Nonnull。但是,為了與Xcode 6.3兼容,我們預定義了宏__nullable和__nonnull來擴展為新名稱。
變量限定符
__strong , __weak, __unsafe_unretained, __autoreleasing |
---|
在ARC模式下,對象默認是strong類型,以下用于修飾變量(不帶下劃線的strong用在屬性中)
__strong:聲明一個變量時默認是__strong類型,當對象被強引用時對象會一直存在
__weak:當對象沒有強引用時,弱引用被設置為nil。
__unsafe_unretained:指向一個引用,該引用不會使被強引用,并且該對象沒有強引用時不會設置為nil。如果它引用的對象被釋放,指針將懸空。通常在MRC環境下使用
__autoreleasing:用于表示通過引用(id*)傳遞并在返回時自動釋放的參數。
__autoreleasing 修飾的變量指向的對象相當于調用 autorelease,會注冊到自動釋放池。
例如,聲明變量(局部變量,成員變量,屬性均可)時這樣寫:
@interface APPList : NSObject{
id __weak delegate;
NSObject * __strong object;
}
//...
@property(nonatomic)APPList * __strong list;//編譯也沒問題
@property(nonatomic,nullable)id __weak obj;// nonnull 和 weak互斥
//...
NSObject * __strong strongObj;
NSObject * __weak weakObj;
NSObject * __unsafe_unretained unsafeObj;
由于strong是默認的,所以通常不用寫__strong:
NSObject * strongObj1
這種形式,編譯器會轉換成下面這種形式
NSObject * __strong strongObj2;
__strong, __weak一般成對出現在block中避免循環引用
__autoreleasing:通常,方法的聲明是這樣的:
-(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
而error的聲明是隱式的:
NSError * __strong e;
因此編譯器會重寫代碼:
NSError * __strong error;
NSError * __autoreleasing tmp = error;
BOOL OK = [myObject performOperationWithError:&tmp];
error = tmp;
if (!OK) {
// Report the error.
// ...
本地變量聲明( __strong)和參數( __autoreleasing)之間的區別導致編譯器創建臨時變量。在獲取__strong變量的地址時你可以通過將參數聲明為 id __storng*來獲得其原始指針。或者你可以將變量聲明為 __autoreleasing。