OC屬性,類型修飾符

修飾符是編譯器的行為:

類型 類型關鍵字
原子性 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:

  1. 修飾值類型
  2. 如果修飾對象類型不會報錯,使用的時候會編譯器會警告,警告信息如下:
    ??Assigning retained object to unsafe property; object will be released after assignment
    對象創建完立刻會被系統自動回收,會變成懸垂指針,奔潰

weak:

  1. ARC模式下使用
  2. 修飾對象類型
  3. 修飾非對象類型報錯,報錯信息如下:
    ??Property with 'weak' attribute must be of object type
  4. 修飾block屬性,編譯器警告,警告信息如下:
    ??Assigning block literal to a weak property; object will be released after assignment

retain:

  1. MRC下使用
  2. 修飾對象類型
  3. 引用計數+1
  4. 修飾非對象類型報錯,報錯信息如下:
    ??Property with 'retain (or strong)' attribute must be of object type
  5. 修飾block,警告如下:
    ??Retain'ed block property does not copy the block - use copy attribute instead

strong:

  1. ARC模式下使用(代替retain)
  2. 修飾對象類型,block
  3. 引用計數+1
  4. 修飾非對象類型報錯,報錯信息如下:
    ??Property with 'retain (or strong)' attribute must be of object type

copy:

  1. 修飾字符串,block 引用計數不會+1
  2. 修飾,集合類型會有不同的結果:

詳情參考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中避免循環引用

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。

參考文檔

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容