站内推广方案,怎样做网站的子网,网站开发注意,325建筑兼职网Effective Objective-C 2.0 读书笔记——内存管理#xff08;上#xff09; 文章目录 Effective Objective-C 2.0 读书笔记——内存管理#xff08;上#xff09;引用计数属性存取方法中的内存管理autorelease保留环 ARCARC必须遵循的方法命名原则ARC 的自动优化#xff1…Effective Objective-C 2.0 读书笔记——内存管理上 文章目录 Effective Objective-C 2.0 读书笔记——内存管理上引用计数属性存取方法中的内存管理autorelease保留环 ARCARC必须遵循的方法命名原则ARC 的自动优化消除冗余的 autorelease 与 retain变量的内存管理语义__strong默认__weak__unsafe_unretained__autoreleasing ARC清理实例变量覆写内存管理方法 在Objective-C中内存管理是程序开发中不可或缺的一部分而自引用计数
ARC是一种自动化的内存管理技术。本文是对自动引用计数的简单学习特此进行记录 引用计数
引用计数是一种技术用于管理对象的引用计数即对象被引用的次数。当一个对象的引用计数大于0时表示该对象被持有不可被释放当引用计数为0时表示对象需要被释放。
自引用计数是一种技术用于管理对象的引用计数即对象被引用的次数。当一个对象的引用计数大于0时表示该对象被持有不可被释放当引用计数为0时表示对象需要被释放。
文章用一个办公室关灯的例子十分贴切 第一个人进入办公室“需要照明的人数” 加1。计数值从0 变成了1因此要开灯。之后每当有人进入办公室“需要照明的人数” 就加1。如计数值从1变成2。每当有人下班离开办公室“需要照明的人数” 就减1。如计数值从2 变成1。最后一个人下班离开办公室时“需要照明的人数” 减1。计数值从1变成了0因此要 关灯。 // 生成并持有对象
id obj [[NSObject alloc] init];// 持有对象
[obj retain];// 释放对象
[obj release];// 废弃对象
[obj dealloc];
属性存取方法中的内存管理
在手动计数我们要使用以下方法对属性进行手动计数
- (void)setFoo:(id)foo {if (_foo ! foo) {[foo retain];//对传入的新对象 foo 调用 retain使其引用计数加 1[_foo release];//释放当前实例变量 _foo 中原来存储的对象_foo foo;//传入的新对象 foo 赋值给实例变量 _foo}
}
当执行完 _foo foo; 后_foo 和 foo 都指向同一个对象因此对 _foo 进行 release 就相当于对该对象也就是 foo 指向的对象进行 release。
autorelease
关于这个方法书中给出了这个例子
- (NSString *)stringValue {NSString *str [[NSString alloc] initWithFormat:I am this: %, self];return str;
}由于我们内存管理的原则——谁创建谁释放在这个方法之中被创建的对象str必须在stringValue之中得到release但是很明显如果在return语句执行之前进行release的话则没有返回值。在return之后写release语句则根本不会执行那么怎么办呢我们可以将程序修改成以下形式
- (NSString *)stringValue {NSString *str [[NSString alloc] initWithFormat:I am this: %, self];return [str autorelease]; // 放入自动释放池中
}它会在稍后释放对象从而给调用者留下了足够长的时间 使其可以在需要时先保留返回值。实际上释放操作会在清空最外层的自动释放池 (参见第 34 条)时 执行除非你有自己的自动释放池 否则这个时机指的就是当前线程的下一次事件循环
保留环 保留环顾名思义——就是呈环状相互引用的多个对象这将导致内存泄漏因为循环中的对象其保留计数不会降为0。对于循环中的每个对象来说至少还有另外 一个对象引用着它 。图里的每个对象都引用了 另外两个对象之中的一个。在这个循环里所有对象的保留计数都是1。
在垃圾收集环境中通常将这种情况认定为孤岛 (island of isolation)通常我们使用弱引用解决这个问题
ARC
由于ARC 会自动执行retain、release、autorelease等操作所以直接在ARC下调用这些内存管理方法是非法的。具体来说不能调用下列方法 • retain • release • autorelease • dealloc 直接调用上述任何方法都会产生编译错误因为ARC要分析何处应该自动调用内存管理方法所以如果手工调用的话就会干扰其工作。
ARC必须遵循的方法命名原则
拥有retained返回值 如果方法名以 alloc、new、copy 或 mutableCopy 开头ARC 默认认为该方法返回的对象是“拥有的”即调用者获得一个 1 的所有权调用者负责在不需要时释放它。
示例
EOCPerson *person [[EOCPerson alloc] init];
// person 的 retain count 1调用者需要 release
[person release];如果方法名 不以 这些前缀开头如 somePerson那么返回的对象会被 autorelease调用者不需要手动 release否则可能会导致程序崩溃过度释放。 2. 示例代码解析
(1) newPerson 方法
objc复制编辑(EOCPerson*) newPerson {EOCPerson *person [[EOCPerson alloc] init];return person;
}该方法的名字 以 new 开头所以它返回的对象是 归调用者所有的。alloc 使 person 的 retain count 1但调用者仍然负责在适当的时候释放 person。
调用方式
EOCPerson *personOne [EOCPerson newPerson];
// 由于 newPerson 返回的是 owned 对象调用者需要释放
[personOne release];(2) somePerson 方法 (EOCPerson*) somePerson {EOCPerson *person [[EOCPerson alloc] init];return person;
}该方法的名字 没有以 new、alloc、copy 或 mutableCopy 开头所以它返回的对象 不归调用者所有。ARC 会自动在返回对象上调用 autorelease确保对象在方法返回后仍然有效但在适当的时候自动释放。
在 MRC手动引用计数 下它等价于
return [person autorelease];调用方式
EOCPerson *personTwo [EOCPerson somePerson];
// personTwo 被自动 autorelease调用者不需要手动 release如果手动调用 [personTwo release]可能会导致程序崩溃
但是如果调用此类方法想要获取一个长时间持有的对象的话例如用这类方法赋值给一个属性我们还是需要对这个进行retain操作
EOCPerson *tmp [EOCPerson personWithName: Bob Smith];
_myperson [tmp retain];3. 调用代码分析
-(void) doSomething {EOCPerson *personOne [EOCPerson newPerson];EOCPerson *personTwo [EOCPerson somePerson];
}在 doSomething 方法里
personOne 通过 newPerson 方法创建归调用者所有所以 doSomething 结束时ARC 需要释放它如果是 MRC调用者需要手动 release。personTwo 通过 somePerson 方法创建它是 autorelease 对象不需要手动释放ARC 会自动管理它。
在 ARC 下这段代码执行后
personOne 在作用域结束后被 ARC 释放。personTwo 在合适的时间点自动释放。
ARC 的自动优化消除冗余的 autorelease 与 retain
除了自动调用 retain 与 release 之外ARC 还能进行一些手工难以实现的优化。书中举了这样一个例子
假设有一个方法 personWithName:它内部是这样写的 (EOCPerson*) personWithName:(NSString*) name {EOCPerson *person [[EOCPerson alloc] init];person.name name;return objc_autoreleaseReturnValue(person);
}关于objc_autoreleaseReturnValue(person)这个函数是一个用于优化的函数其具体作用如下
检测调用者是否马上会对返回的对象调用 retain 在某些情形下例如调用方在赋值时因为属性是 strong所以会自动执行一次 retain 操作实际上调用者会对返回的对象立即执行 retain。这种情况下原先的 autorelease 操作就显得“多余”了。
优化过程 如果检测到调用者马上会执行 retain那么 objc_autoreleaseReturnValue 会设置一个标志位并不真正执行 autorelease这样就避免了不必要的 autorelease 和随后的 retain 操作。
那么对于这个方法的调用者来说我们在调用者侧的代码如下
EOCPerson *tmp [EOCPerson personWithName:Mat Galloway];
_myPerson objc_retainAutoreleasedReturnValue(tmp);这里 objc_retainAutoreleasedReturnValue 的作用与前面的函数相对应
检测返回对象上是否设置了标志表明前面已经发现“将被 retain”的情况如果标志已置位则直接返回对象而不执行额外的 retain 操作否则就调用普通的 retain。
对于这两个特殊的优化方法书中给出他们的伪代码实现
id objc_autoreleaseReturnValue(id object) {if (/* caller will retain object */) {set_flag(object); // 标记此对象表明将会被 retainreturn object;} else {return [object autorelease];}
}id objc_retainAutoreleasedReturnValue(id object) {if (get_flag(object)) {clear_flag(object);return object; // 已经标记直接返回而不需要额外的 retain} else {return [object retain];}
}
设置与检测一个标志位的操作通常比调用 autorelease 和 retain 更高效。ARC 利用这种技术可以使得内存管理的开销降低从而提升程序整体性能。
变量的内存管理语义
ARC也会处理局部变量与实例变量的内存管理。默认情况下每个变量都是指向对象的强引用。一定要理解这个问题尤其要注意实例变量的语义因为对于某些代码来说其语义和手动管理引用计数时不同。例如有下面这段代码:
interface EOCClass : NSObject (
id _object;
}
implementation EOCClass
- (void) setup (_object [EOCotherClass new]; }
end在手动管理引用计数时实例变量_object 并不会自动保留其值而在ARC环境下则会 这样做。也就是说若在ARC 下编译setup 方法则其代码会变为:
-(void) setup {id tmp [EOCOtherClass new];_object [tmp retain];[tmp release];
}如果不用ARC那么需要像下面这样来写:
-(void) setobject: (id) object {[_object release];_object [object retain];
} 在 ARC 下Objective‑C 引入了几个修饰符来标识变量对对象的所有权这些修饰符直接影响编译器如何管理变量所引用对象的内存。常见的修饰符包括
__strong默认 语义 对象变量默认是 __strong 的也就是说当一个对象被赋值给一个 __strong 变量时该变量会持有对象使得对象的引用计数增加。 作用 保证只要变量存在对象不会被销毁。当该变量离开作用域或被赋予新值时原先引用的对象会自动释放编译器会自动插入 release 操作。 示例 NSObject *obj [[NSObject alloc] init]; // obj 是 __strong 的retain count 自动 1
// 当 obj 离开作用域后编译器会自动调用 release__weak 语义 声明为 __weak 的变量不会对所引用的对象进行所有权保持即不会增加对象的引用计数。 作用 主要用于打破循环引用例如在 delegate 或块block中当所引用的对象被释放时__weak 变量会自动置为 nil防止野指针问题。 示例 NSURL *url [NSURL URLWithString:http://www.example.com/];
EOCNetworkFetcher *fetcher [[EOCNetworkFetcher alloc] initWithURL:url];
__weak EOCNetworkFetcher *weakFetcher fetcher;
[fetcher startWithCompletion:^(BOOL success) {NSLog(Finished fetching from %, weakFetcher.url);
}];__unsafe_unretained 语义 与 __weak 类似也不增加引用计数但不同的是 __unsafe_unretained 变量不会在所引用对象被销毁时自动置为 nil因此存在野指针风险。 作用 主要用于兼容旧代码或在性能上要求极高且确信生命周期管理正确的场景中。 示例 __unsafe_unretained NSObject *unsafeObj someStrongObj;
// 如果 someStrongObj 被释放unsafeObj 不会自动置 nil继续访问会导致崩溃__autoreleasing 语义 这种修饰符通常用于方法参数表示传入的对象在方法返回时将被放入自动释放池。 作用 用于处理输出参数使得返回给调用者的对象不必立即释放而是在当前自动释放池清空时被释放。 示例 - (BOOL)error:(NSError * __autoreleasing *)error;ARC清理实例变量
在使用MRC时我们会在- (void)dealloc之中手动释放所有被持有的实例变量
- (void)dealloc {[_foo release];[_bar release];[super dealloc];
}
尽管 ARC 可以自动管理所有 Objective‑C 对象的内存但对于非 Objective‑C 对象仍需要开发者手动清理。例如
Core Foundation 对象这些对象不受 ARC 管理需要在 dealloc 中调用 CFRelease。由 malloc 分配的内存这类内存同样需要手动调用 free 来释放。
在 ARC 环境下如果你需要清理这些资源你可以自己实现 dealloc 方法但注意不要调用 [super dealloc]因为 ARC 会自动为你调用超类的 dealloc。示例代码可能如下
- (void)dealloc {CFRelease(_coreFoundationObject);free(_heapAllocatedMemoryBlob);// 不要调用 [super dealloc]ARC 会自动调用超类的 dealloc
}
覆写内存管理方法
在 MRC 下有时我们会覆写 release 方法例如在单例中为了防止对象被释放将 release 改成空操作但在 ARC 下我们不被允许重写或者是直接调用内存管理方法因为我们前面有说到ARC会执行各项的相关优化重写或者直接调用会产生问题。ARC 通过特殊函数如 objc_autoreleaseReturnValue 和 objc_retainAutoreleasedReturnValue来优化那些成对出现的 autorelease 与 retain 操作以减少不必要的调用和提升性能。