文章目錄
- 前言
- strong和copy的區別
- 為什么要用copy?
- 什么時候用什么修飾?
- strong(ARC自動管理)
- strong修飾變量的底層流程圖
- 底層代碼核心實現
- 小結
- copy
- 底層流程圖
- 對比與strong的關鍵不同之處
- 內部調用關系(偽代碼)
- 小結
- OC中的屬性關鍵字
- 原子性(Atomicity):控制操作的原子性
- atomic(原子性,默認值)
- nonatomic(非原子性)
- 小結
- 讀寫權限:控制屬性的訪問方式
- readwrite(可讀可寫,默認值)
- readonly(只讀)
- 小結
- 內存管理:控制對象的內存生命周期
- strong(強引用,默認值)
- weak(弱引用)
- copy(拷貝)
- assign(直接賦值)
- 小結
- 空值約束(Nullability):標記屬性是否允許為 nil
- nonnull(非空)
- nullable(可空)
- null_unspecified(未指定)
- 集合宏(簡化聲明)
- 小結
- 總結
前言
??我們編寫OC代碼在.h文件里聲明變量時會用到strong和copy這兩個修飾符,那么這兩者到底有什么區別,什么時候該用strong,什么時候該用copy?今天我們通過這兩者的底層實現邏輯來探究一下這里面的奧秘。
strong和copy的區別
首先,先看一下這段測試代碼:
代碼運行結果如下:
??從上述結果我們可以看出來,copy修飾的字符串strCopy的值沒有隨著newStr的改變而改變,而strong修飾的字符串strStrong因為newStr的修改而隨著改變了。
??在我們前面學習的過程中,我們知道copy修飾的變量,編譯器會自動生成自定義的 setter方法,其核心邏輯是:==賦值時不直接保留原對象,而是先調用對象的 copy方法生成副本,再保留該副本。==也就是說,copy修飾的變量對象地址是一個新的地址,這個指針變量指向的是一個新的內存區域(相當于深拷貝),所以修改原本的newStr不會對其產生影響。
??所以,使用 copy和 strong修飾變量,在 OC中的最大區別在于 賦值時是否進行對象拷貝(復制內存)。這在處理==可變對象(如 NSMutableString)==時尤為關鍵。
關鍵字 | 作用 |
---|---|
strong | 引用傳遞:只增加引用計數,指向原對象 |
copy | 拷貝傳遞:生成副本,指向新對象 |
為什么要用copy?
防止外部可變對象的改變影響內部狀態
保證對象的不可變性,尤其是對 NSString、NSArray、NSDictionary 等常用于聲明為不可變對象的屬性
推薦使用 copy
修飾 NSString
/ NSArray
/ NSDictionary
(防御性編程)
什么時候用什么修飾?
類型 | 建議修飾符 |
---|---|
NSString | copy |
NSMutableString | copy |
NSArray/NSDictionary | copy |
自定義對象 | strong(實現copy時才用copy) |
strong(ARC自動管理)
??使用 strong修飾的變量,在 ARC(Automatic Reference Counting)下,其底層原理和邏輯可以歸結為對 objc_storeStrong() 函數的封裝,它等價于:
id oldValue = _ivar;
_ivar = [newValue retain]; // 引用計數 +1
[oldValue release]; // 引用計數 -1
strong修飾變量的底層流程圖
因為strong在底層是對objc_storeStrong()函數的封裝,所以其底層流程圖其實就等效 objc_storeStrong()
+----------------------+| objc_storeStrong |+----------------------+|+----------------------+| 檢查目標地址是否為 nil |+----------------------+|v+----------------------+| old = *object || *object = value |+----------------------+|+-----------------------------------+| 如果新舊對象不同: || - retain(value) → 引用計數 +1 || - release(old) → 引用計數 -1 |+-----------------------------------+|v+--------------------+| 引用更新完成返回 |+--------------------+
底層代碼核心實現
在 Apple Runtime 源碼中,objc_storeStrong
類似于這樣實現(簡化版偽代碼):
void objc_storeStrong(id *object, id value) {id old = *object;if (old != value) {objc_retain(value); // 引用計數 +1*object = value;objc_release(old); // 引用計數 -1}
}
小結
步驟 | 動作 |
---|---|
新值 retain | 增加新對象引用計數,確保它不會被過早釋放 |
舊值 release | 減少舊對象引用計數,若為0則銷毀 |
設置 _ivar | 將指針指向新的對象地址 |
安全性 | ARC 生成的代碼是線程安全的,并避免循環引用等問題 |
copy
??當我們使用 copy修飾屬性時,其背后的行為與 strong非常不同,關鍵在于對象賦值時會生成副本(調用 copy 方法),而不是直接引用原對象。
當你使用 copy
修飾屬性時,其背后的行為與 strong
非常不同,關鍵在于 對象賦值時會生成副本(調用 copy
方法),而不是直接引用原對象。
底層流程圖
使用copy修飾符在底層的調用邏輯等價于 objc_storeStrong(&ivar, [value copy])
):
+----------------------+| objc_storeStrong |+----------------------+^|+----------------------+| 調用 [value copy] |+----------------------+|+-----------------------------+| copy 調用 -copyWithZone: || -> 生成新的對象 || -> 返回新對象引用 |+-----------------------------+|+-----------------------------+| retain(copyValue) || release(oldValue) |+-----------------------------+|+----------------------+| ivar 指向新副本對象 |+----------------------+
對比與strong的關鍵不同之處
行為 | strong | copy |
---|---|---|
是否復制對象 | ? 不復制,直接引用原對象 | ? 調用 copy 創建新對象 |
內部處理方式 | objc_storeStrong(&ivar, value) | objc_storeStrong(&ivar, [value copy]) |
所需協議支持 | 無需 | 對象需實現 NSCopying 協議 |
適用場景 | 一般對象引用 | NSString / NSArray / 自定義不可變類等 |
對可變對象是否防御 | ? 外部修改影響內部 | ? 內部得到的是獨立副本,不受外部影響 |
內部調用關系(偽代碼)
// ARC 編譯器生成代碼
- (void)setName:(NSString *)name {id copyValue = [name copy]; // 關鍵步驟:生成副本objc_storeStrong(&_name, copyValue); // 然后存儲副本對象
}
小結
copy修飾的屬性會在賦值時復制對象副本,而不是保留原始引用。
如果傳入的是 NSMutableString,copy 后變成 NSString(不可變),從而防止外部修改影響內部狀態。
最終仍通過 objc_storeStrong 來管理引用計數,但傳入的是 [x copy] 的返回值。
OC中的屬性關鍵字
在 Objective-C 中,屬性(Property)是封裝對象狀態的核心機制,通過 @property
聲明并結合不同的屬性關鍵字,可以精確控制屬性的行為(如內存管理、訪問權限、線程安全等)。
原子性(Atomicity):控制操作的原子性
原子性關鍵字用于修飾屬性的 setter 和 getter 方法,決定其操作是否為“原子操作”(不可分割的操作)。
atomic(原子性,默認值)
保證 setter
和 getter
操作的原子性(即操作要么完全執行,要么完全不執行),避免多線程并發訪問時的數據不一致問題。
特點:
- 線程安全,但不保證業務邏輯的絕對安全(例如,復合操作如
_count++
仍需額外同步)。 - 性能開銷較大(因需要加鎖/解鎖機制)。
@property (atomic, assign) NSInteger count; // 默認 atomic,線程安全但性能一般
nonatomic(非原子性)
不保證 setter
和 getter
的原子性,多線程并發訪問時可能導致數據不一致。
特點:
- 無鎖機制,性能更高(適合高頻讀寫的場景)。
- 需開發者自行處理線程安全(如通過
@synchronized
、GCD 隊列等)。
@property (nonatomic, strong) NSString *name; // 非原子性,性能更優
小結
- 優先選
nonatomic
:大多數場景下(尤其是移動端),性能比絕對線程安全更重要。 - 僅當選中
atomic
:需要框架級線程安全(如系統底層庫),或配合其他同步機制使用。
讀寫權限:控制屬性的訪問方式
讀寫權限關鍵字決定屬性是否生成 setter
方法,從而控制屬性的可寫性。
readwrite(可讀可寫,默認值)
自動生成 setter
(寫方法)和 getter
(讀方法),屬性可讀可寫。
@property (readwrite, copy) NSString *title; // 可讀可寫(默認)
readonly(只讀)
僅生成 getter
方法,屬性不可寫(外部只能讀取,不能直接修改)。
- 常用于接口設計,隱藏內部實現細節(如通過類擴展在
.m
文件中重新聲明為readwrite
,允許內部修改)。
// .h 文件(對外接口)
@interface User : NSObject
@property (readonly, copy) NSString *userId; // 外部只讀
@end// .m 文件(內部實現)
@interface User ()
@property (readwrite, copy) NSString *userId; // 內部可寫
@end
小結
readwrite
:常規可修改屬性。readonly
:常用于封裝(外部只讀,內部可寫)。
內存管理:控制對象的內存生命周期
內存管理關鍵字用于告訴編譯器如何管理屬性所引用對象的內存(僅適用于對象類型,基本數據類型如 int
、CGFloat
不適用)。
strong(強引用,默認值)
屬性對對象持有強引用(增加對象的引用計數),確保對象在屬性作用域內不會被釋放。
-
適用于大多數對象類型(如自定義對象、系統容器類)。
-
循環引用風險:若兩個對象相互
strong
引用,會導致內存泄漏(需配合weak
打破)。@property (strong, nonatomic) NSArray *dataList; // 強引用數組,數組內對象也會被保留
weak(弱引用)
屬性對對象持有弱引用(不增加引用計數),對象釋放后,屬性自動置為 nil
(避免野指針)。
特點:
- 解決循環引用(如
delegate
模式、視圖控制器與子視圖的相互引用)。 - 無法持有對象(對象可能因無強引用被提前釋放)。
// 視圖控制器的 delegate 通常用 weak,避免循環引用
@property (weak, nonatomic) id<MyDelegate> delegate;
copy(拷貝)
賦值時調用對象的 copy
方法生成副本,屬性持有副本的強引用(原對象不受后續修改影響)。
適用場景:
- 不可變對象(如
NSString
、NSArray
):防止外部傳入可變對象(如NSMutableString
)后被修改。 - 需要“快照”的場景(如配置參數、緩存數據)。
@property (copy, nonatomic) NSString *username; // 外部傳入 NSMutableString 會被拷貝為 NSString
assign(直接賦值)
直接賦值(不涉及引用計數管理),適用于基本數據類型或非對象類型(如 int
、CGFloat
、指針)。
特點:對對象類型使用 assign
會導致野指針(對象釋放后屬性仍指向無效內存)。
@property (assign, nonatomic) NSInteger age; // 基本數據類型用 assign
@property (assign, nonatomic) CGPoint position; // 結構體用 assign
小結
關鍵字 | 適用類型 | 內存行為 | 典型場景 | 注意事項 |
---|---|---|---|---|
strong | 對象 | 強引用(+1 RC) | 常規對象屬性 | 避免循環引用 |
weak | 對象 | 弱引用(不+RC,釋放后置nil) | delegate、IBOutlets | 僅適用于對象 |
copy | 對象(需實現 NSCopying ) | 生成副本并強引用 | 字符串、集合、防止外部修改 | 不可變對象 copy 是淺拷貝 |
assign | 基本類型/非對象 | 直接賦值(無引用計數) | 數值、結構體 | 對象類型會導致野指針 |
空值約束(Nullability):標記屬性是否允許為 nil
空值約束關鍵字用于明確屬性是否允許為 nil,幫助編譯器進行靜態檢查,減少運行時崩潰(需 Xcode 7+ 支持)。
nonnull(非空)
屬性必須非空(不能為 nil),編譯器會對可能的 nil賦值發出警告。
@property (nonatomic, copy, nonnull) NSString *userId; // 必須賦值非空字符串
nullable(可空)
屬性可以為空(允許為 nil),無編譯器警告。
@property (nonatomic, strong, nullable) UIImage *avatar; // 頭像可能為空
null_unspecified(未指定)
屬性是否為空未明確聲明(編譯器不做強制檢查),用于兼容舊代碼或無法確定的情況。
@property (nonatomic, copy, null_unspecified) NSString *legacyData; // 未指定是否可空
集合宏(簡化聲明)
為避免重復寫 nonnull/nullable,可使用以下宏包裹屬性列表:
-
NS_ASSUME_NONNULL_BEGIN/NS_ASSUME_NONNULL_END:區間內默認 nonnull,顯式 nullable除外。
-
示例:
NS_ASSUME_NONNULL_BEGIN @interface User : NSObject @property (nonatomic, copy) NSString *name; // 默認 nonnull @property (nonatomic, strong, nullable) NSNumber *age; // 顯式 nullable @end NS_ASSUME_NONNULL_END
小結
- nonnull:強制屬性非空,提升代碼健壯性。
- nullable:明確屬性可空,避免無意義檢查。
- null_unspecified:兼容過渡場景。
總結
場景 | 推薦關鍵字組合 |
---|---|
常規對象屬性(如自定義模型) | nonatomic, strong |
字符串/集合(防外部修改) | nonatomic, copy |
避免循環引用(如 delegate ) | nonatomic, weak |
基本數據類型/結構體 | nonatomic, assign |
接口只讀,內部可寫 | .h 中 readonly ,.m 中 readwrite |
強制非空 | nonatomic, strong, nonnull |