單例核心要求
- 全程序僅一個實例
- 提供全域存取點
- 線程安全初始化
- 防止 `alloc`/`init`、`copy`、`retain`/`release` 破壞單一性
標準實現 (ARC 相容)
// UserContext.h
#import
@interface UserContext : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *email;
// 單例存取點
+ (instancetype)sharedInstance;
@end
// UserContext.m
#import "UserContext.h"
static UserContext *_sharedInstance = nil;
static dispatch_once_t _onceToken;
@implementation UserContext
+ (instancetype)sharedInstance {
dispatch_once(&_onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
// 防止 alloc/init 建立新實例
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [super allocWithZone:zone];
});
return _sharedInstance;
}
// 防止複製產生新實例
- (id)copyWithZone:(struct _NSZone *)zone {
return _sharedInstance;
}
// 非 ARC 環境下防止釋放 (ARC 下編譯器忽略)
- (instancetype)retain { return _sharedInstance; }
- (void)release {}
- (instancetype)autorelease { return _sharedInstance; }
- (NSUInteger)retainCount { return NSUIntegerMax; }
@end
關鍵技術解析
| 技術 | 作用 |
| `dispatch_once` | GCD 保證初始化代碼僅執行一次,原子性、無鎖、高效能 |
| `allocWithZone:` | 攔截 `[[UserContext alloc] init]` 直接返回共享實例 |
| `copyWithZone:` | 實現 `NSCopying`,複製返回同一實例 |
| `retain`/`release`/`autorelease`/`retainCount` | 非 ARC 時阻止參考計數變化,ARC 下編譯器優化移除 |
| `instancetype` | 返回型別為接收者類別,支援子類別繼承單例模式 |
測試代碼
int main() {
@autoreleasepool {
UserContext *a = [UserContext sharedInstance];
UserContext *b = [UserContext sharedInstance];
UserContext *c = [[UserContext alloc] init];
UserContext *d = [c copy];
NSLog(@"a=%p b=%p c=%p d=%p", a, b, c, d); // 四個指標相同
NSLog(@"name=%@", a.name); // 初始為 nil
}
return 0;
}
輸出結果
a=0x100100000 b=0x100100000 c=0x100100000 d=0x100100000
name=(null)
進階:支援子類別的單例基類
@interface SingletonBase : NSObject
+ (instancetype)sharedInstance;
@end
@implementation SingletonBase
+ (instancetype)sharedInstance {
static id _instance = nil;
static dispatch_once_t _token;
dispatch_once(&_token, ^{
_instance = [[self alloc] init]; // self 為子類別
});
return _instance;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static dispatch_once_t _token;
dispatch_once(&_token, ^{
// 僅首次 alloc 建立實例
});
return _instance ?: [super allocWithZone:zone];
}
@end
// 子類別自動獲得單例能力
@interface MyManager : SingletonBase
@property (nonatomic, strong) NSData *cache;
@end
常見誤區
- 只在 `sharedInstance` 用 `dispatch_once`,未覆寫 `allocWithZone:` → `[[MyClass alloc] init]` 仍建立新物件
- 非 ARC 專案未實作 `retain`/`release` 覆寫 → 釋放導致野指標
- 單例持有大量資源未釋放 → 記憶體洩漏 (單例生命週期 = 進程生命週期,通常可接受)