name: Objective-C ARC 模式 user-invocable: false description: 在 Objective-C 中使用自动引用计数时使用,包括强引用/弱引用、循环引用、所有权限定符、与 Core Foundation 的桥接,以及无需手动 retain/release 的内存安全代码模式。 allowed-tools: []
Objective-C ARC 模式
引言
自动引用计数 (ARC) 是 Objective-C 的内存管理系统,它在编译时自动插入 retain 和 release 调用。ARC 消除了大多数手动内存管理,同时提供确定性的内存行为,并防止常见的内存错误,如使用后释放和双重释放。
与垃圾回收不同,当引用计数达到零时,ARC 提供立即释放,使其适用于资源受限的环境,如 iOS。理解 ARC 的所有权规则、限定符和模式对于编写内存安全的 Objective-C 代码和避免循环引用至关重要。
本技能涵盖强引用和弱引用、所有权限定符、循环引用、Core Foundation 桥接以及基于 ARC 的内存管理最佳实践。
强引用和弱引用
强引用维护对象的所有权并防止释放,而弱引用观察对象而不阻止释放。
// 强引用(默认)
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSArray *friends;
@property (nonatomic, strong) UIImage *photo;
@end
@implementation Person
@end
// 弱引用
@interface ViewController : UIViewController
@property (nonatomic, weak) id<ViewControllerDelegate> delegate;
@property (nonatomic, weak) IBOutlet UILabel *nameLabel;
@end
@implementation ViewController
@end
@protocol ViewControllerDelegate <NSObject>
- (void)viewControllerDidFinish:(ViewController *)controller;
@end
// 使用强引用和弱引用
void strongWeakExample(void) {
Person *person = [[Person alloc] init];
person.name = @"Alice"; // 对 NSString 的强引用
__weak Person *weakPerson = person; // 弱引用
NSLog(@"Weak person: %@", weakPerson.name);
person = nil; // Person 被释放
NSLog(@"After nil: %@", weakPerson); // weakPerson 现在为 nil
}
// 无主引用(unsafe_unretained)
@interface NodeOld : NSObject
@property (nonatomic, unsafe_unretained) NodeOld *parent;
@property (nonatomic, strong) NSArray<NodeOld *> *children;
@end
@implementation NodeOld
@end
// 弱引用 vs 无主引用
@interface CommentOld : NSObject
@property (nonatomic, strong) NSString *text;
@property (nonatomic, weak) PostOld *post; // 弱引用:可以为 nil
@end
@interface PostOld : NSObject
@property (nonatomic, strong) NSArray<CommentOld *> *comments;
@end
@implementation CommentOld
@end
@implementation PostOld
@end
// 块中的强捕获
void blockCaptureExample(void) {
Person *person = [[Person alloc] init];
person.name = @"Bob";
// 强捕获
void (^strongBlock)(void) = ^{
NSLog(@"%@", person.name); // 强捕获 person
};
// 弱捕获以避免循环引用
__weak Person *weakPerson = person;
void (^weakBlock)(void) = ^{
NSLog(@"%@", weakPerson.name); // 弱捕获
};
strongBlock();
weakBlock();
}
强引用增加保留计数,而弱引用在对象释放时自动设置为 nil,防止悬空指针。
循环引用及其解决
循环引用发生在对象之间持有强引用时,阻止释放。解决循环引用需要使用弱引用或无主引用。
// 循环引用示例
@interface Parent : NSObject
@property (nonatomic, strong) NSArray<Child *> *children;
@end
@interface Child : NSObject
@property (nonatomic, weak) Parent *parent; // 弱引用以打破循环
@property (nonatomic, strong) NSString *name;
@end
@implementation Parent
- (void)dealloc {
NSLog(@"Parent deallocated");
}
@end
@implementation Child
- (void)dealloc {
NSLog(@"Child deallocated");
}
@end
void noCycleExample(void) {
Parent *parent = [[Parent alloc] init];
Child *child = [[Child alloc] init];
child.name = @"Alice";
child.parent = parent; // 弱引用
parent.children = @[child]; // 强引用
// 当 parent 超出作用域时,两者都被释放
}
// 无循环的委托模式
@protocol DataSourceDelegate <NSObject>
- (void)dataSourceDidUpdate:(id)source;
@end
@interface DataSource : NSObject
@property (nonatomic, weak) id<DataSourceDelegate> delegate;
- (void)fetchData;
@end
@implementation DataSource
- (void)fetchData {
// 获取数据
[self.delegate dataSourceDidUpdate:self];
}
- (void)dealloc {
NSLog(@"DataSource deallocated");
}
@end
// 块的循环引用
@interface NetworkManager : NSObject
@property (nonatomic, strong) NSString *baseURL;
- (void)fetchDataWithCompletion:(void (^)(NSData *data))completion;
@end
@implementation NetworkManager
- (void)fetchDataWithCompletion:(void (^)(NSData *))completion {
// 模拟异步工作
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [@"response" dataUsingEncoding:NSUTF8StringEncoding];
dispatch_async(dispatch_get_main_queue(), ^{
completion(data);
});
});
}
- (void)dealloc {
NSLog(@"NetworkManager deallocated");
}
@end
// 使用弱-强舞避免块循环
@interface ViewController2 : UIViewController
@property (nonatomic, strong) NetworkManager *networkManager;
@end
@implementation ViewController2
- (void)loadData {
__weak typeof(self) weakSelf = self;
[self.networkManager fetchDataWithCompletion:^(NSData *data) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return;
// 安全使用 strongSelf
NSLog(@"Data loaded: %@", strongSelf.view);
}];
}
- (void)dealloc {
NSLog(@"ViewController2 deallocated");
}
@end
// 观察者模式循环
@interface Observable : NSObject
@property (nonatomic, strong) NSMutableArray *observers;
- (void)addObserver:(id)observer;
- (void)removeObserver:(id)observer;
- (void)notifyObservers;
@end
@implementation Observable
- (instancetype)init {
self = [super init];
if (self) {
_observers = [NSMutableArray array];
}
return self;
}
- (void)addObserver:(id)observer {
// 使用 NSPointerArray 进行弱引用
[self.observers addObject:[NSValue valueWithPointer:(__bridge void *)observer]];
}
- (void)removeObserver:(id)observer {
[self.observers removeObject:[NSValue valueWithPointer:(__bridge void *)observer]];
}
- (void)notifyObservers {
for (NSValue *value in self.observers) {
id observer = (__bridge id)(void *)[value pointerValue];
if (observer) {
// 通知观察者
}
}
}
@end
始终对委托、父指针和观察者使用弱引用来打破循环引用。在块中使用弱-强舞以安全访问 self。
所有权限定符
ARC 提供所有权限定符,明确控制变量和属性的内存管理行为。
// 属性所有权限定符
@interface Container : NSObject
// Strong: 对象指针的默认值
@property (nonatomic, strong) id strongProperty;
// Weak: 不阻止释放
@property (nonatomic, weak) id weakProperty;
// Copy: 创建对象的副本
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSArray *items;
// Assign: 用于非对象类型
@property (nonatomic, assign) NSInteger count;
@property (nonatomic, assign) CGFloat value;
// Unsafe_unretained: 弱引用但不设为 nil
@property (nonatomic, unsafe_unretained) id unsafeProperty;
@end
@implementation Container
@end
// 变量限定符
void qualifierExamples(void) {
// __strong: 默认限定符
__strong NSString *strongString = @"Hello";
// __weak: 弱引用
__weak NSString *weakString = strongString;
// __unsafe_unretained: 非管理的弱引用
__unsafe_unretained NSString *unsafeString = strongString;
// __autoreleasing: 用于输出参数
NSError * __autoreleasing error;
}
// Copy 属性行为
@interface Message : NSObject
@property (nonatomic, copy) NSString *text;
@property (nonatomic, copy) NSArray *recipients;
@end
@implementation Message
@end
void copyPropertyExample(void) {
Message *message = [[Message alloc] init];
NSMutableString *mutableText = [NSMutableString stringWithString:@"Hello"];
message.text = mutableText; // 创建副本
[mutableText appendString:@" World"];
NSLog(@"Message text: %@", message.text); // 仍然是 "Hello"
NSLog(@"Mutable text: %@", mutableText); // "Hello World"
}
// Autoreleasing 参数
BOOL loadData(NSData **outData, NSError **outError) {
if (outData) {
*outData = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
}
return YES;
}
void autoreleaseExample(void) {
NSData *data;
NSError *error;
if (loadData(&data, &error)) {
NSLog(@"Loaded: %@", data);
} else {
NSLog(@"Error: %@", error);
}
}
// 属性属性组合
@interface ConfiguredObject : NSObject
// 只读强引用
@property (nonatomic, strong, readonly) NSString *identifier;
// 读写复制
@property (nonatomic, copy, readwrite) NSString *name;
// 弱可空
@property (nonatomic, weak, nullable) id<NSObject> delegate;
// 强非空
@property (nonatomic, strong, nonnull) NSArray *items;
@end
@implementation ConfiguredObject
@end
对于接受可变类型(如 NSMutableString 或 NSMutableArray)的属性,选择 copy 以防止意外突变。
Core Foundation 桥接
Core Foundation 对象需要显式内存管理,并与 ARC 管理的 Objective-C 对象桥接以正确工作。
// 使用 __bridge 进行免费桥接
void bridgingExample(void) {
NSString *nsString = @"Hello";
// 桥接到 CF 而不转移所有权
CFStringRef cfString = (__bridge CFStringRef)nsString;
// nsString 保留所有权
// 桥接回 NS
NSString *nsString2 = (__bridge NSString *)cfString;
}
// 将所有权转移到 CF
void bridgeRetainExample(void) {
NSString *nsString = @"Hello";
// 将所有权转移到 CF
CFStringRef cfString = (__bridge_retained CFStringRef)nsString;
// 必须手动 CFRelease
// 使用 cfString
CFIndex length = CFStringGetLength(cfString);
NSLog(@"Length: %ld", (long)length);
// 手动释放
CFRelease(cfString);
}
// 从 CF 转移所有权
void bridgeTransferExample(void) {
// 创建 CF 对象(拥有所有权)
CFMutableStringRef cfString = CFStringCreateMutable(NULL, 0);
CFStringAppend(cfString, CFSTR("Hello"));
// 转移到 ARC
NSMutableString *nsString = (__bridge_transfer NSMutableString *)cfString;
// ARC 现在拥有,无需 CFRelease
[nsString appendString:@" World"];
NSLog(@"%@", nsString);
}
// 处理 CF 集合
void cfCollectionExample(void) {
// 创建 CF 数组
CFArrayRef cfArray = CFArrayCreate(
NULL,
(const void *[]){@"A", @"B", @"C"},
3,
&kCFTypeArrayCallBacks
);
// 桥接到 NSArray
NSArray *nsArray = (__bridge_transfer NSArray *)cfArray;
NSLog(@"Array: %@", nsArray);
}
// CF 属性列表
void cfPropertyListExample(void) {
NSDictionary *dict = @{@"key": @"value"};
// 转换为 CF
CFPropertyListRef plist = (__bridge_retained CFPropertyListRef)dict;
// 写入文件
CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:@"/tmp/test.plist"];
CFPropertyListWrite(plist, url, kCFPropertyListXMLFormat_v1_0, 0, NULL);
CFRelease(plist);
}
// 处理 CF 回调
void myCFArrayApplierFunction(const void *value, void *context) {
NSString *string = (__bridge NSString *)value;
NSLog(@"Value: %@", string);
}
void cfCallbackExample(void) {
CFArrayRef cfArray = (__bridge CFArrayRef)@[@"A", @"B", @"C"];
CFArrayApplyFunction(
cfArray,
CFRangeMake(0, CFArrayGetCount(cfArray)),
myCFArrayApplierFunction,
NULL
);
}
使用 __bridge 进行临时桥接,__bridge_retained 当转移到 CF 时,__bridge_transfer 当从 CF 转移到 ARC 时。
ARC 和 C 结构
当将 ARC 对象与 C 结构混合时,需要显式管理结构中的对象指针。
// 带有对象指针的结构体
typedef struct {
__unsafe_unretained NSString *name;
NSInteger age;
} PersonStruct;
void structExample(void) {
PersonStruct person;
person.name = @"Alice"; // 必须使用 __unsafe_unretained
person.age = 30;
NSLog(@"Person: %@, %ld", person.name, (long)person.age);
}
// 更好:使用 Objective-C 类替代
@interface PersonData : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
@implementation PersonData
@end
// 带有手动内存管理的结构体
typedef struct PersonStructManual {
CFStringRef name; // 使用 CF 类型进行保留所有权
NSInteger age;
} PersonStructManual;
PersonStructManual createPerson(NSString *name, NSInteger age) {
PersonStructManual person;
person.name = CFBridgingRetain(name); // 手动保留
person.age = age;
return person;
}
void releasePerson(PersonStructManual person) {
CFRelease(person.name); // 手动释放
}
void manualStructExample(void) {
PersonStructManual person = createPerson(@"Bob", 25);
// 使用 person
releasePerson(person);
}
// 结构体中的对象数组
typedef struct {
__unsafe_unretained NSArray *items;
NSInteger count;
} ContainerStruct;
// 替代:使用指向对象的指针
typedef struct {
void *items; // 需要时桥接到 NSArray
NSInteger count;
} ContainerStructPointer;
void pointerStructExample(void) {
NSArray *array = @[@1, @2, @3];
ContainerStructPointer container;
container.items = (__bridge void *)array;
container.count = array.count;
// 检索
NSArray *retrieved = (__bridge NSArray *)container.items;
NSLog(@"Items: %@", retrieved);
}
避免将 ARC 管理的对象存储在 C 结构体中。使用 Objective-C 类或带有手动管理的 CF 类型替代。
自动释放池
自动释放池管理便捷方法创建的临时对象,并防止循环中内存积累。
// 主函数中的隐式自动释放池
int main(int argc, char *argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil,
NSStringFromClass([AppDelegate class]));
}
}
// 循环中的显式自动释放池
void processLargeDataset(void) {
NSArray *items = /* 大数组 */;
for (id item in items) {
@autoreleasepool {
// 每次迭代释放临时对象
NSString *processed = [item description];
NSData *data = [processed dataUsingEncoding:NSUTF8StringEncoding];
// data 和 processed 在迭代结束时释放
}
}
}
// 没有自动释放池(内存积累)
void inefficientLoop(void) {
for (NSInteger i = 0; i < 1000000; i++) {
NSString *string = [NSString stringWithFormat:@"Number %ld", (long)i];
// string 直到外层池结束才释放
}
}
// 有自动释放池(每次迭代释放内存)
void efficientLoop(void) {
for (NSInteger i = 0; i < 1000000; i++) {
@autoreleasepool {
NSString *string = [NSString stringWithFormat:@"Number %ld", (long)i];
// string 在每次迭代结束时释放
}
}
}
// 嵌套自动释放池
void nestedPools(void) {
@autoreleasepool {
NSString *outer = @"outer";
@autoreleasepool {
NSString *inner = [NSString stringWithFormat:@"%@ inner", outer];
// inner 在内层池排空时释放
}
// outer 在外层池排空时释放
}
}
// 后台线程池
void backgroundWork(void) {
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@autoreleasepool {
// 后台工作
NSString *result = [NSString stringWithFormat:@"Result"];
NSLog(@"%@", result);
}
});
}
在创建许多临时对象的紧密循环中使用显式自动释放池,以防止自动释放池排空之间的内存增长。
最佳实践
-
对委托和父引用使用弱引用 以打破常见委托和层次模式中的循环引用
-
在块中应用弱-强舞 当捕获 self 时,以防止循环引用,同时确保执行期间的安全访问
-
对可变类型属性选择复制 如 NSString 和 NSArray,以防止调用者意外突变
-
显式注释可空性 使用 nullable/nonnull 以提高 API 清晰度和 Swift 互操作性
-
在创建临时对象的循环中使用自动释放池 以防止长运行迭代中内存积累
-
显式桥接 CF 类型 使用适当的限定符来管理 ARC 和手动引用计数之间的所有权转移
-
避免将对象存储在 C 结构体中 因为 ARC 无法管理它们;使用 Objective-C 类或 CF 类型替代
-
在弱引用后检查 nil 因为它们在被引用对象释放时可能随时变为 nil
-
使用 NSPointerArray 进行弱集合 当维护观察者或委托集合时,以防止循环引用
-
使用 Instruments 分析 以检测循环引用、内存泄漏和过多的自动释放对象创建
常见陷阱
-
使用强委托创建循环引用 导致内存泄漏;始终对委托属性使用弱引用
-
在块中强捕获 self 当块存储在属性中时创建循环引用
-
在弱-强舞中忘记强引用 允许 self 在块执行期间释放
-
对父指针使用强引用 创建双向强引用并阻止释放
-
对 NSString 属性不使用复制 允许调用者传递可变字符串并在之后修改它们
-
错误桥接 CF 类型 根据所有权转移方向导致过度释放或泄漏
-
将 ARC 对象存储在 C 结构体中 导致过早释放或崩溃,因为 ARC 无法跟踪它们
-
创建 NSTimer 而不失效 强保留目标并阻止释放直到失效
-
在紧密循环中缺少自动释放池 导致内存增长和潜在的内存压力崩溃
-
使用 unsafe_unretained 替代 weak 创建悬空指针,在释放后访问时崩溃
何时使用此技能
在编写 iOS、macOS、watchOS 或 tvOS 的 Objective-C 代码时使用 ARC 模式,以确保内存安全的应用程序,无需手动 retain/release 调用。
在实现委托、观察者或可能创建循环引用的回调时应用弱引用和弱-强舞。
在与使用手动引用计数的 Core Foundation、Core Graphics 或其他 C 基框架接口时,采用适当的桥接。
在处理大型数据集、导入数据或在循环中执行其他创建许多临时对象的操作时,利用自动释放池。
在设计公共 API 时,使用适当的所有权限定符,以清晰地向客户端传达内存管理期望。