iOS - 從 @property 開始
核心概念
本質:@property 是一組訪問器方法的聲明 (setter/getter) ,編譯器可以自動“合成”「訪問器」以及「底層存儲(ivar)」,并且允許用點語法調用。
- 例如:
@property (nonatomic) NSInteger age; - 編譯器等價(自動合成):
{ NSInteger _age; // 可選的“底層存儲” (backing ivar) } - (NSInteger)age { return _age; } // getter - (void)setAge:(NSInteger)age { _age = age; } // setter
好處:
- 減少樣板代碼
- 明確所有權
- 性能與并發控制
- KVC/KVO 友好
- 易讀易維護
常見屬性修飾符
- 讀寫性
- readwrite:可讀可寫(默認)
- readonly:只讀
- 原子性
- atomic:保證“單次訪問器調用”的原子性,速度慢。(默認)
- 注意:atomic慢,且不保證你“對該對象做的一系列操作”是線程安全的;也不保證順序、事務或對象內部的并發安全,實際場景還是需要顯式同步。
- nonatomic:不做同步,速度快。
- atomic:保證“單次訪問器調用”的原子性,速度慢。(默認)
- 內存語義修飾符
- strong:持有關系,引用計數+1,新值
retain,舊值release.- 場景:一般對象所有權、父持子
- weak:不持有,引用計數不變,對象釋放時指針置空。
- 場景:避免循環引用,如
delegate,子持父、IBOutlet。 - 注意:訪問時可能已經變 nil。
- 場景:避免循環引用,如
- copy:生成不可變副本,setter 執行
-(id)copy方法。- 場景:阻止賦值可變對象的后續修改,
block入堆。
- 場景:阻止賦值可變對象的后續修改,
- assign:位拷貝,引用計數不變。
- 場景:用于標量和結構體
- 注意:對象指針使用 assign 會產生懸垂指針
- unsafe_unretained:不持有,引用計數不變,對象釋放不會置空。
- 場景:以往無
weak可用時使用的。
- 場景:以往無
- strong:持有關系,引用計數+1,新值
- 其他
- getter=isEnabled/setter=setFoo:指定自定義 setter/getter。
- class:類屬性。
copy相關延伸
strong或copy修飾的 可變/不可變 對象,對其 賦值/拷貝 會發生什么?
| 屬性 | -copy | -mutableCopy | 賦值 NSString | 賦值 NSMutableString |
|---|---|---|---|---|
| (copy) NSString | 淺拷貝 | 深拷貝 |
淺拷貝 | 深拷貝 |
| (strong) NSString | 淺拷貝 | 深拷貝 |
淺拷貝 | 淺拷貝 |
| (copy) NSMutableString | 淺拷貝 | 深拷貝 |
淺拷貝 | 深拷貝 |
| (strong) NSMutableString | 深拷貝 |
深拷貝 |
淺拷貝 | 淺拷貝 |
拷貝:
-copy一定返回不可變對象調用對象實際為「不可變類型」,產生淺拷貝。調用對象實際為「可變類型」,產生深拷貝,得到「不可變類型」的對象。
-mutableCopy一定返回可變對象(一定會深拷貝)
賦值:
- 對
strong修飾的屬性賦值,只會產生淺拷貝,屬性與賦值對象「可變性」一致。 - 對
copy修飾的屬性賦值,可能深拷貝可能淺拷貝,但是結果一定「不可變」的。賦值對象是「不可變類型」,產生淺拷貝。賦值對象是「可變類型」,產生深拷貝,得到「不可變類型」的對象。
提問:那為什么
[(copy)NSMutableString copy]會是淺拷貝?
- 答案: 基于上述結論,我們可以將答案拆分成 2 步。
- 賦值:
copy修飾的NSMutableString類型屬性,在賦值時會將目標對象“深拷貝”,變為不可變的NSString。因此,我們的屬性self.pStr此時實際指向的是一個NSString(不可變對象)。 - 拷貝:在對「不可變對象」進行
copy操作時,返回“指針級別”的同一對象。 - 綜上,
[(copy)NSMutableString copy]會是一個淺拷貝操作。
- 賦值:
何時存儲(背后存儲backing ivar的規則)
- 會有存儲
- 在類的
@interface或類擴展里聲明@property。 - 沒有顯式使用
@dynamic,且沒有同時手寫 setter + getter 的。
- 在類的
- 不會有存儲
- 在
category里聲明的@property。 - 使用
@dynamic的, 承諾運行時提供訪問器的。 - 已經實現了 getter + setter 的。
- 協議
@protocol里的@property。 - 類屬性。
- 在
- 例外和細節
readonly若你實現了 getter,則不會再自動合成ivar。- “類屬性”沒有
ivar實例,通常用static或者其他存儲來實現存儲。@interface Config : NSObject @property (class, nonatomic, copy) NSString *build; @end @implementation Config static NSString *_build; + (NSString *)build { return _build; } + (void)setBuild:(NSString *)b { _build = [b copy]; } @end - 分類里的屬性如何有“存儲”?
- 分類里的屬性需要通過關聯對象實現存儲。
#import <objc/runtime.h> @interface UIView (Badge) @property (nonatomic, copy) NSString *badgeText; // 分類里不會有 ivar @end @implementation UIView (Badge) static const void *kBadgeKey = &kBadgeKey; - (void)setBadgeText:(NSString *)badgeText { objc_setAssociatedObject(self, kBadgeKey, badgeText, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (NSString *)badgeText { return objc_getAssociatedObject(self, kBadgeKey); } @end
@dynamic 與 @synthesize 與 計算屬性
-
@dynamic
- 作用:告訴編譯器,不需要生成訪問器和
ivar,也不要因為找不到方法而告警。 - 場景:Core Data 的
NSManagedObject子類:@interface Book : NSManagedObject @property (nonatomic, copy) NSString *title; @end @implementation Book @dynamic title; // 訪問器由運行時(Core Data)注入;編譯器不生成也不報缺實現 @end
- 作用:告訴編譯器,不需要生成訪問器和
-
@synthesize
- 作用:讓編譯器為
@property生成 getter/setter 以及背后存儲ivar,并把屬性名映射到自定義ivar名。
- 作用:讓編譯器為
-
計算屬性
- 作用:不依賴存儲,按需計算。
property 和 ivar 的區別
ivar== 純存儲property== 訪問這個存儲的“方法接口”- 大多數情況使用
self.age,在init/dealloc/自定義訪問器內部常用_age直接訪問,避免遞歸等問題。

浙公網安備 33010602011771號