歷史演進三階段
| 階段 | Xcode/Clang 版本 | 特徵 | 開發者工作 |
| MRC 手動時代 | Xcode < 4.4 | 需手寫 @synthesize、dealloc、retain/release | 聲明 ivar、寫 setter/getter、內存管理 |
| @synthesize 時代 | Xcode 4.4~ | @property 自動生成聲明,@synthesize 自動生成實現 + ivar | 寫 @property + @synthesize,ARC 管理內存 |
| 自動合成時代 (現代) | Xcode 4.5+ (LLVM 4.0+) | @property **默認自動**合成 ivar、setter、getter,**無需 @synthesize** | 僅寫 @property,專注業務邏輯 |
@property 屬性修飾符完整速查
@property (attributes) Type name;
| 屬性 | 含義 | 適用類型 | 默認值 |
| 原子性 | | | |
| atomic | 線程安全 setter/getter (鎖/自旋鎖) | 所有對象類型 | ✅ 默認 |
| nonatomic | 非線程安全,高性能 | 所有類型 | iOS 推薦 |
| 內存管理 (對象類型) | | | |
| strong | 持有強引用 (ARC 保留) | 對象指針 | ✅ 默認 |
| weak | 弱引用,對象釋放自動置 nil | 對象指針 | 打破循環引用 |
| copy | 複製一份 (不可變副本) | NSString, NSArray, NSDictionary, Block | 防止外部修改 |
| assign | 直接賦值,不管理內存 | 基本類型, 非對象指針 | 基本類型默認 |
| unsafe_unretained | 弱引用但不置 nil (危險) | 對象指針 | 極少用 |
| 讀寫權限 | | | |
| readwrite | 生成 setter + getter | | ✅ 默認 |
| readonly | 僅生成 getter | | 不可變屬性 |
| 方法重命名 | | | |
| getter=name | 自定義 getter 名稱 | BOOL 類型常用 isEnabled | 屬性名 |
| setter=name: | 自定義 setter 名稱 | | setPropertyName: |
@synthesize 手動控制 (歷史/特殊需求)
// 現代默認:@property 自動生成 _ivar (下劃線前綴)
// 手動指定 ivar 名稱 (舊代碼遷移/特殊命名)
@synthesize name = _name; // 綁定到現有 _name
@synthesize age = _age; // 若 _age 不存在,自動創建 @private ivar
// 手動實現 setter/getter 任一,編譯器不再自動生成另一側
// 若同時手動實現 setter+getter,編譯器**不**自動創建 ivar (需自行聲明)
現代最佳實踐 (Xcode 12+ / iOS 13+)
1. 接口聲明 (.h)
@interface User : NSObject
// 推薦:nonatomic + 強/弱/複製語義明確
@property (nonatomic, copy) NSString *username;
@property (nonatomic, strong) NSString *email;
@property (nonatomic, assign) NSInteger age; // 基本類型用 assign
@property (nonatomic, getter=isActive) BOOL active; // BOOL 推薦 is 前綴
@property (nonatomic, weak) id delegate; // 代理必須 weak
// 唯讀屬性 (初始化後不可變)
@property (nonatomic, readonly, copy) NSString *uuid;
@end
2. 實現 (.m)
@implementation User
// 無需 @synthesize,編譯器自動:
// @synthesize username = _username;
// @synthesize email = _email;
// ...
// 自定義初始化器設置 readonly 屬性
- (instancetype)initWithUsername:(NSString *)username email:(NSString *)email {
self = [super init];
if (self) {
_username = [username copy]; // 直接訪問 ivar
_email = [email copy];
_uuid = [[NSUUID UUID] UUIDString];
}
return self;
}
// 覆寫 setter (如需副作用)
- (void)setActive:(BOOL)active {
if (_active != active) {
_active = active;
[self updateUIForActiveState];
}
}
// 覆寫 getter (懶加載/計算屬性)
- (NSString *)displayName {
return _username.length ? _username : @"Guest";
}
@end
ivar 訪問規範:直接訪問 vs 存取器
| 場景 | 推薦方式 | 理由 |
| init / dealloc | 直接訪問 _ivar | 避免副作用、KVO 觸發、未完全構造時調用覆寫方法 |
| 性能極度敏感熱路徑 | 直接訪問 _ivar | 省去方法調用開銷 (極少見) |
| 其他所有情況 | 存取器 self.prop / [self setProp:] | 觸發 KVO、繼承覆寫、副作用、原子性保證、調試斷點 |
常見坑與避坑指南
- copy vs strong:
NSString *、NSArray *、NSDictionary *、Block **必須用 copy**,防止可變對象外部修改導致內部狀態不可預期變化。
- 循環引用:父→子 strong,子→父 **weak** (delegate, block 捕獲 self 用
__weak typeof(self) weakSelf = self;)。
- readonly 欺騙:外部不可寫,內部可通過
_ivar 或類擴展 @property (readwrite) 修改。
- atomic 性能陷阱:iOS 上 默認 atomic 但極慢**,**永遠顯式寫 nonatomic**,除非真正需要線程安全 (極少見)。
- Block 屬性:
@property (nonatomic, copy) void (^completion)(BOOL); Block 必須 copy 從棄移至堆。
屬性聲明的編譯器生成內容 (現代自動合成)
@property (nonatomic, strong) NSString *name;
// 編譯器自動生成等價於:
// 1. 實例變量 (ivar)
@private
NSString *_name;
// 2. Getter
- (NSString *)name {
return _name;
}
// 3. Setter
- (void)setName:(NSString *)name {
if (_name != name) {
[_name release]; // MRC 時代
_name = [name retain]; // MRC
// ARC 時代自動插入 retain/release
}
}
// 4. 類擴展中可重新聲明改變屬性
@interface User ()
@property (readwrite) NSString *uuid; // 擴展為 readwrite
@end
總結
從手動 @synthesize 到完全自動合成,Objective-C 屬性機制經歷了「減樣板、增安全、重約定」的演進。現代開發只需記住:聲明 @property 並明確非原子/內存語義、init/dealloc 訪問 ivar、其餘用 self.、delegate/block 用 weak/copy、readonly 內部可寫。理解底層生成機制有助於調試、性能分析與舊代碼維護,但日常開發請擁抱自動合成,專注於業務邏輯。