ObjectiveC之屬性

NO IMAGE

什麼是屬性

  • 屬性指的是一個對象的屬性或特性。
  • 蘋果公司在 Objective-C 2.0 中引入了屬性(property),它組合了新的預編譯指令和新的屬性訪問器語法。

自動合成

  • @ 符號標誌著“這是 Objective-C 語法”。

@property

  • @property 是一種新的編譯器功能,它意味著聲明瞭一個新對象的屬性。
  • @property 預編譯指令的作用是自動聲明屬性的 setter 和 getter 方法。

用代碼表示為:

@property UIView *view;

等同於:

- (UIView *)view;
- (void)setView:(UIView *)view;

@synthesize

  • @synthesize 也是一種新的編譯器功能,它表示“創建了該屬性的訪問代碼”。
  • 所有的屬性都是基於變量的,所以在你合成(synthesize)getter 和 setter 方法的時候,編譯器會自動創建與屬性名稱相同的實例變量。

用代碼表示為:

@synthesize view;

等同於:

- (UIView *)view {
return view;
}
- (void)setView:(UIView *)view {
view = view;
}

@dynamic

  • @dynamic 關鍵字告訴編譯器自動合成無效,不要生成任何代碼或創建相應的實例變量,用戶會自己生成屬性的 getter 和 setter 方法。
  • @dynamic 通常用於子類中告訴編譯器使用父類的屬性實現。

Xcode4.4 以後

  • Xcode4.4 以後定義 @property 時可以省去 @synthesize。也就意味著 @property 可以自動聲明和實現屬性的 setter 和 getter 方法,並且自動創建名稱為屬性名前帶有下劃線(_)的實例變量。

用一個公式表示為:

@property = ivar + setter + getter

用代碼表示為:

@property UIView *view;

等同於:

- (UIView *)view;
- (void)setView:(UIView *)view;
- (UIView *)view {
return _view;
}
- (void)setView:(UIView *)view {
_view = view;
}

小結

  • @property 是一種編譯器指令,它意味著聲明瞭一個新對象的屬性。屬性聲明等同於聲明瞭 setter 和 getter 方法。
  • @synthesize 也是一種編譯器指令,會讓編譯器為類的實例變量自動生成訪問方法。所有的屬性都是基於變量的,所以在你合成(synthesize)getter 和 setter 方法的時候,編譯器會自動創建與屬性名稱相同的實例變量。
  • @dynamic 關鍵字告訴編譯器自動合成無效,不要生成任何代碼或創建相應的實例變量,用戶會自己生成屬性的 getter 和 setter 方法。
  • Xcode4.4 以後定義 @property 時可以省去 @synthesize。如果你不使用 @synthesize,那麼編譯器生成的實例變量會以下劃線(_)字符作為其名稱的第一個字符。

關鍵字

  • 關鍵字也叫屬性可選項,也叫屬性特性。

讀寫性

關鍵字說明
readwrite讀寫
readonly只讀

方法名

關鍵字說明
getter指定 getter 方法的名字
setter指定 setter 方法的名字

原子性

關鍵字說明
atomic原子性
nonatomic非原子性
  • 原子性是多線程中的一個概念,如果說訪問方法是原子的,那就意味著多線程環境下訪問屬性是安全的,在執行的過程中不可被打斷。
  • 在併發編程中,如果某操作具備整體性,也就是說,系統其他部分無法觀察到其中間步驟所生成的臨時結果,而只能看到操作前與後的結構,那麼該操作就是“原子的”,或者說,該操作具備“原子性”。

內存管理

關鍵字基本數據類型MRCARC
assign直接賦值直接賦值直接賦值
unsafe_unretained直接賦值直接賦值直接賦值
retain出錯賦值並對新值進行 retain 操作賦值並對新值進行 retain 操作
strong出錯賦值並對新值進行 retain 操作賦值並對新值進行 retain 操作
weak出錯直接賦值弱引用
copy出錯賦值時建立傳入值的一份副本賦值時建立傳入值的一份副本
  • assign “設置方法”只會執行鍼對“純量類型”的簡單賦值操作。
  • unsafe_unretained 此特質的語義和 assign 相同,但是它適用於“對象類型”,該特質表達一種“非擁有關係”,當目標對象遭到摧毀時,屬性值不會自動清空,這一點與 weak 有區別。
  • retain 此特質表明該屬性定義了一種“擁有關係”。為這種屬性設置新值時,設置方法會先保留新值,並釋放舊值,然後再將新值設置上去。
  • strong 此特質與 retain 等價。但 strong 是隨著 ARC 時加入的新特性,所以在 MRC 環境下只能用 retain 來修飾。在 ARC 環境下,為了與 MRC 進行區分,也為了和 weak 對應,最好使用 strong。
  • weak 此特質表明該屬性定義了一種“非擁有關係”。為這種屬性設置新值時,設置方法既不保留新值,也不釋放舊值。此特質同 assign 類似,然而在屬性所指的對象遭到摧毀時,屬性值也會清空。
  • copy 此特質所表達的所屬關係與 strong 類似。然而設置方法並不保留新值,而是將其“拷貝”。

小結

  • assign/unsafe_unretained 這兩個關鍵字在實際使用時完全等價,表示一種“非擁有關係”,雖然允許修飾“對象類型”,但在使用時要用來修改“基本數據類型”。
  • strong/retain 這兩個關鍵字在實際使用時完全等價,表示一種“擁有關係”,不能用來修飾“基本數據類型”。
  • weak/assign 這兩個關鍵字都表示一種“非擁有關係”,但 weak 在所指的對象摧毀時,會清空屬性值。
  • copy/strong 這兩個關鍵字都表示一種“擁有關係”,但 copy 不保留新值,而是拷貝新值。

可空性

關鍵字說明
nullable可以為空
nonnull不能為空
null_resettablegetter 不能返回空,setter 可以傳空
null_unspecified不確定是否為空
  • 可空性關鍵字只能用來修飾“對象類型”的屬性。
  • 如果使用了 null_resettable 關鍵字,必須重寫 getter 或者 setter,保證 getter 返回值不為空。

類屬性

關鍵字說明
Class類屬性

類屬性需要自己手寫變量、getter 和 setter:

@property (nonatomic, strong, class) NSObject *classObject;
static NSObject *_classObject;
+ (NSObject *)classObject {
return _classObject;
}
+ (void)setClassObject:(NSObject *)classObject {
_classObject = classObject;
}

默認關鍵字

  • 基本數據類型: atomic,readwrite,assign
  • 對象類型: atomic,readwrite,strong

Protocol 和 Category 中的屬性

Protocol 中的屬性

  • 在 Protocol 中使用 @property 只會自動生成 setter 和 getter 方法聲明,不會自動生成其實現。
  • 我們在 Protocol 中使用屬性的目的,是希望遵守我協議的對象能實現該屬性。

Category 中的屬性

  • 在 Category 使用 @property 也是隻會自動生成 setter 和 getter 方法的聲明,不會自動生成其實現。
  • 如果我們真的需要給 category 增加屬性的實現,需要藉助於運行時的兩個函數:objc_setAssociatedObjectobjc_getAssociatedObject

getter 和 setter 的源碼實現

getter

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
// 如果傳入的偏移量為 0 就把屬性所屬的類的實例對象返回
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);
}

setter

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
// 如果傳入的偏移量為 0 就把值設置給屬性所屬的類的實例對象
if (offset == 0) {
object_setClass(self, newValue);
return;
}
// 獲取舊值
id oldValue;
id *slot = (id*) ((char*)self + offset);
// 根據拷貝性質獲取新值
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
// 如果是非原子操作就直接賦值
oldValue = *slot;
*slot = newValue;
} else {
// 如果是原子操作就加鎖保護
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;        
slotlock.unlock();
}
// 釋放舊值
objc_release(oldValue);
}

MRC 下手動實現 getter 和 setter

getter

-(NSString *)object {
return [[_object retain] autorelease];
}

setter

-(void)setObject:(NSObject *)object {
if (_object != object) {
[_object release];
_object = [object retain];
}
}

常見問題

什麼情況下使用 weak 關鍵字,相比 assign 有什麼不同?

  • 在 ARC 中,有可能出現循環引用的時候,往往要通過讓其中一端的屬性使用 weak 來解決,比如: delegate 代理屬性

  • 自身已經對它進行一次強引用,沒有必要再強引用一次,此時也會使用 weak,自定義 IBOutlet 控件屬性一般也使用 weak;比如:xid 和 storyboard 中控件

  • weak 此特質表明該屬性定義了一種“非擁有關係” (nonowning relationship)。為這種屬性設置新值時,設置方法既不保留新值,也不釋放舊值。此特質同assign類似, 然而在屬性所指的對象遭到摧毀時,屬性值也會清空(nil out)。 而 assign 的“設置方法”只會執行鍼對“純量類型” (scalar type,例如 CGFloat 或 NSlnteger 等)的簡單賦值操作。

  • assign 可以用非 OC 對象,而 weak 必須用於 OC 對象。

怎麼用 copy 關鍵字?

  • NSString、NSArray、NSDictionary 等等經常使用 copy 關鍵字,是因為他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary;
  • block 也經常使用 copy 關鍵字,block 使用 copy 是從 MRC 遺留下來的“傳統”,在 MRC 中,方法內部的 block 是在棧區的,使用 copy 可以把它放到堆區,在 ARC 中寫不寫都行。

這個寫法會出什麼問題: @property (copy) NSMutableArray *array;

  • 兩個問題:
  1. 添加,刪除,修改數組內的元素的時候,程序會因為找不到對應的方法而崩潰,因為 copy 就是複製一個不可變 NSArray 的對象;
  2. 使用了 atomic 屬性會嚴重影響性能,並且無法保證多線程下的線程安全;

如何讓自己的類用 copy 修飾符?如何重寫帶 copy 關鍵字的 setter?

  • 若想令自己所寫的對象具有拷貝功能,則需實現 NSCopying 協議。如果自定義的對象分為可變版本與不可變版本,那麼就要同時實現 NSCopying 與 NSMutableCopying 協議。
  • 實現 copyWithZone: 方法

@property 的本質是什麼?ivar、getter、setter 是如何生成並添加到這個類中的

  • @property = ivar + getter + setter;
  • “屬性” (property)有兩大概念:ivar(實例變量)、存取方法(access method = getter + setter)。
  • 完成屬性定義後,編譯器會自動編寫訪問這些屬性所需的方法,此過程叫做“自動合成”(autosynthesis)。這個過程由編譯器在編譯期執行
  • 編譯後大致生成了五個東西:
    • OBJC_IVAR_ObjectiveC之屬性屬性名稱 :該屬性的“偏移量” (offset),這個偏移量是“硬編碼” (hardcode),表示該變量距離存放對象的內存區域的起始地址有多遠。
    • setter 與 getter 方法對應的實現函數
    • ivar_list :成員變量列表
    • method_list :方法列表
    • prop_list :屬性列表
  • 也就是說我們每次在增加一個屬性,系統都會在 ivar_list 中添加一個成員變量的描述,在 method_list 中增加 setter 與 getter 方法的描述,在屬性列表中增加一個屬性的描述,然後計算該屬性在對象中的偏移量,然後給出 setter 與 getter 方法對應的實現,在 setter 方法中從偏移量的位置開始賦值,在 getter 方法中從偏移量開始取值,為了能夠讀取正確字節數,系統對象偏移量的指針類型進行了類型強轉.

runtime 如何實現 weak 屬性

  • 對於 weak 對象會放入一個 hash 表中。 用 weak 指向的對象內存地址作為 key,當此對象的引用計數為 0 的時候會 dealloc,假如 weak 指向的對象內存地址是 a,那麼就會以 a 為鍵, 在這個 weak 表中搜索,找到所有以a為鍵的 weak 對象,從而設置為 nil。

weak 屬性需要在 dealloc 中置 nil 麼?

  • 不需要。在ARC環境無論是強指針還是弱指針都無需在 dealloc 設置為 nil , ARC 會自動幫我們處理。

@synthesize和@dynamic分別有什麼作用?

  • @property有兩個對應的詞,一個是 @synthesize,一個是 @dynamic。如果 @synthesize 和 @dynamic 都沒寫,那麼默認的就是 @syntheszie var = _var;
  • @synthesize 的語義是如果你沒有手動實現 setter 方法和 getter 方法,那麼編譯器會自動為你加上這兩個方法。
  • @dynamic 告訴編譯器:屬性的 setter 與 getter 方法由用戶自己實現,不自動生成。(當然對於 readonly 的屬性只需提供 getter 即可)。假如一個屬性被聲明為 @dynamic var,然後你沒有提供 @setter 方法和 @getter 方法,編譯的時候沒問題,但是當程序運行到 instance.var = someVar,由於缺 setter 方法會導致程序崩潰;或者當運行到 someVar = var 時,由於缺 getter 方法同樣會導致崩潰。編譯時沒問題,運行時才執行相應的方法,這就是所謂的動態綁定。

用 @property 聲明的 NSString(或 NSArray,NSDictionary)經常使用 copy 關鍵字,為什麼?如果改用 strong 關鍵字,可能造成什麼問題?

  • 使用 copy 的目的是為了讓本對象的屬性不受外界影響,使用 copy 無論給我傳入是一個可變對象還是不可對象,我本身持有的就是一個不可變的副本。
  • 如果我們使用是 strong,那麼這個屬性就有可能指向一個可變對象,如果這個可變對象在外部被修改了,那麼會影響該屬性.
  • 說白了,就是如果對有可變類型的屬性設置了 strong ,再賦值給它一個可變類型的對象,修改該對象時也會修改屬性保存的值。

@synthesize 合成實例變量的規則是什麼?假如 property 名為 foo,存在一個名為 _foo 的實例變量,那麼還會自動合成新變量麼?

  • 如果指定了成員變量的名稱,會生成一個指定的名稱的成員變量。
  • 如果這個成員已經存在了就不再生成了。
  • 如果是 @synthesize foo; 還會生成一個名稱為 foo 的成員變量,也就是說:如果沒有指定成員變量的名稱會自動生成一個屬性同名的成員變量。
  • 不會合成。

在有了自動合成屬性實例變量之後,@synthesize 還有哪些使用場景?

  • 當你在子類中重載了父類中的屬性,你必須 使用 @synthesize 來手動合成 ivar。
  • 可以在類的實現代碼裡通過 @synthesize 語法來指定實例變量的名字。
  • 同時重寫了 setter 和 getter 時,系統就不會生成 ivar,這時候有兩種選擇:
    • 手動創建 ivar
    • 使用@synthesize foo = _foo; ,關聯 @property 與 ivar

使用atomic一定是線程安全的嗎?

  • 對於 atomic 的屬性,系統生成的 getter/setter 會保證 get、set 操作的完整性,不受其他線程影響。比如,線程 A 的 getter 方法運行到一半,線程 B 調用了 setter:那麼線程 A 的 getter 還是能得到一個完好無損的對象,不會有髒數據。
  • 但是 atomic 可並不能保證線程安全。如果線程 A 調了 getter,與此同時線程 B 、線程 C 都調了 setter——那最後線程 A get 到的值,3種都有可能:可能是 B、C set 之前原始的值,也可能是 B set 的值,也可能是 C set 的值。同時,最終這個屬性的值,可能是 B set 的值,也有可能是 C set 的值。
  • 給屬性加上 atomic 修飾,可以保證屬性的 setter 和 getter 都是原子性操作,也就是保證 setter 和 getter 內部是線程同步的
  • 它並不能保證使用屬性的過程是線程安全的,比如 self.intA 是原子操作,但是 self.intA = self.intA + 1 這個表達式並不是原子操作。又比如對可變數組或字典的操作。

相關文章

iOS持續集成(四)——Jenkins

iOS持續集成(三)——fastlane自定義插件

iOS持續集成(二)——證書管理神器match

iOS持續集成(一)——fastlane使用