retain
或 release
代码。引用计数就像办公室的灯的照明c++
对照明设备所作的动做 | 对OC对象所作的动做 |
---|---|
开灯 | 生成对象 |
须要照明 | 持有对象 |
不须要照明 | 释放对象 |
关灯 | 废弃对象 |
其中,A生成对象时,引用计数为 1, 当多一我的须要照明,如B须要照明,则引用计数 +1, 以此类推。当A不须要对象,A释放对象,引用计数 -1.当最后一个持有对象的人都不要这个对象了,则引用计数变为 0,丢弃对象。objective-c
客观正确的思考方式:macos
对象操做 | OC方法 |
---|---|
生成并持有对象 | alloc/new/copy/mutableCopy等 |
持有对象 | retain |
释放对象 | release |
废弃对象 | dealloc |
本身生成的对象,本身所持有:持有对象编程
- (id) allocObject {
// 本身生成并持有对象
id obj = [[NSObject alloc] init];
return obj;
}
复制代码
须要注意的是: NSMutableArray 类的 array 方法取得的对象不是本身所持有的。其内部实现原理为:数组
- (id)object {
// 本身生成并持有对象
id obj = [[NSObject alloc] init];
// 将对象注册到 autoreleasepool 中, pool结束时会自动调用 release,这样的方法本身就不会持有对象。
[obj autorelease];
// 返回这个本身不持有的对象。
return obj;
}
复制代码
非本身生成的对象,本身也能持有:虽然一开始是不持有的,可是可使用 retain 使其变成被本身所持有的,而后也可使用 release 方法释放对象。缓存
// 取得非本身生成的对象
id obj = [NSMutableArray array];
// 取得的对象存在了,可是并不是本身所持有的,引用计数还为 0, 可是该对象被放到了autoreleasepool 中,能够自动释放
[obj retain];
// 此时,本身就持有了这个对象,引用计数为 1
[obj release];
// 此时释放了这个对象,引用计数变为 0 ,对象就不能够再被访问了,可是对象也没有被当即废弃
复制代码
没法释放非本身持有的对象:例如安全
// 取得非本身持有的对象
id obj = [NSMutableArray array];
[obj release];
// 会致使程序崩溃
复制代码
分析 GNU 源码来理解 NSObject 类中的方法。多线程
首先是 alloc id obj = [[NSObject alloc] init];
框架
+ (id)alloc {
// alloc 在内部调用 allocWithZone
return [self allocWithZone:NSDefaultMallocZone()];
}
+ (id)allocWithZone:(NSZone *)zone {
// allocWithZone 在内部调用 NSAllocateObject
return NSAllocateObject(self, 0, z);
}
struct obj_layout {
NSUInteger retained;
};
inline id NSAllocateObject (Class aClass, NSUInteger extreBytes, NSZone *zone) {
int size = 计算容纳对象所需内存的大小;
// 分配内存空间
id new = NSZoneMalloc(zone, size);
// 将该内存空间中的值初始化为 0
memset(new, 0, size);
// 返回做为对象而使用的指针
new = (id)&((struct obj_layout *) new)[1];
}
/** 其中, NSZoneMalloc, NSDefaultMallocZone() 等名称中包含的 Zone 是为了防止内存碎片化而引入的结构。对内存分配的区域自己进行多重化管理,根据对象使用的目的,大小,分配内存,从而提升内存管理的效率。 可是如今的运行时系统知识简单的忽略了区域的概念,运行时系统中的内存管理自己已经机具效率,再使用区域来管理内存反而会引发内存使用效率低下的问题。 */
复制代码
去掉NSZone后简化的代码函数
struct obj_layout {
NSUInteger retained;
};
+ (id)alloc {
int size = sizeof(struct obj_layout) + 对象大小;
// 这句的意思是,为 struct obj_layout 这个结构体分配一个 size 大小的内存空间,而且函数calloc()会将所分配的内存空间中的每一位都初始化为零,也就是这块内存中全部的值都为 0
struct obj_layout *p = (struct obj_layout *)calloc(1, size);
// 返回该对象指针
return (id)(p + 1);
}
复制代码
[obj retain];
的实现
- (id)retain {
NSIncrementExtraRefCount(self);
}
inline void NSIncrementExtraRefCount(id anObject) {
// 首先 (struct obj_layout *) anObject 找到的是这个对象的尾部, 因此须要 [-1] 减去该对象的大小,来寻址到该对象的头部,而后再判断该结构体中 retained 这个变量的值是否已经大于了系统最大值,若是没有,就 retained++, 使得引用计数 +1.
if (((struct obj_layout *) anObject)[-1].retained == UINT_MAX - 1) {
[NSException raise: NSInternalInconsistencyException format:@"NSIncrementExtraRefCount() asked to increment too far"];
((struct obj_layout *) anObject) [-1].retained++;
}
}
复制代码
[obj release]
的实现
- (void)release {
if (NSDecrementExtraRefCountWasZero(self)) {
[self delloc];
}
}
BOOL NSDecrementExtraRefCountWasZero(id anObject) {
if (((struct obj_layout *) anObject)[-1].retained == 0) {
return YES;
} else {
((struct obj_layout *) anObject)[-1].retained--;
return NO;
}
}
复制代码
[obj dealloc];
的实现
- (void)dealloc {
NSDeallocateObject(self);
}
inLine void NSDeallocateObject (id anObject) {
// 指针 o 指向 anObject 的内存地址,而后释放这个指针指向的内存
struct obj_layout *o = &((struct obj_layout *) anObject) [-1];
free(o);
}
复制代码
首先看 alloc 的实现:
// 依次调用这四个方法
+ alloc
+ allocWithZone:
class_Instance
calloc
复制代码
retainCount / retain / release 的实现
- retainCount
__CFDoExtrernRefOperation
CFBaseicHashGetCountOfKey
- retain
__CFDoExternRefOperation
CFBasicHashAddValue;
- release
__CFDoExternRefOperation
CFBasicHashRemoveValue
// 这些函数的前缀 CF 表示他们都包含于 Core Foundation 框架的源代码中
复制代码
因此其内部实现可能以下:
int __CFDoExternRefOperation(uintptr_r op, id obj) {
CFBasicHashRef table = 取得对象的散列表(obj);
int count;
switch (op) {
case OPERATION_retainCount:
count = CFBasicHashGetCountOfKey(table, obj);
return count;
case OPERATION_retain:
CFBasicHashAddValue(table, obj);
return obj;
case OPERATION_release:
count = CFBasicHashRmoveValue(table, obj);
// 若是count == 0, 返回 YES, 则会调用 dealloc
return 0 == count;
}
}
// 举例说明 retainCount
- (NSUInteger)retainCount {
return (NSUInteger)__CFDExternRefOperation(OPERATION_retainCount, self);
}
复制代码
由此可看出,苹果在计数内部大概是以散列表的方式来管理引用计数的。复习散列表
比较
经过内存块头部管理引用计数的好处:
少许代码便可完成
可以统一管理引用计数须要的内存块和对象所用的内存块
经过引用计数表管理引用计数的好处
autorelease 会像 C语言 的自动变量同样来对待对象实例。当其超出做用域时,就会对对象进行release 的调用。
autorelease 的具体使用方法:
生成并持有 NSAutoreleasePool 对象
调用已经分配对象的 autorelease 实例方法
废弃 NSAutoreleasePool 对象(对对象自动调用 release)
// 代码以下
NSAutoreleasePool pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain]; => 等价于 [obj release];
复制代码
咱们在编程中,并不须要显式的调用 pool 对象,由于在 RunLoop 中,这一切都为咱们处理好了。在一个 RunLoop 循环中,会进行 NSAutoreleasePool 对象的生成,应用程序的主线程进行处理,废弃 NSAutoreleasePool 对象。
尽管是这样,咱们有的时候也须要显式的调用 NSAutoreleasePool 对象,由于有时会产生大量的 autorelease 对象,只要不废弃 NSAutoreleasePool 对象,那么这些生成的对象就不能被释放,会致使内存疯长的现象。最典型的例子就是在读取大量图像的同时改变它的尺寸。
另外,在 Cocoa 框架中也有不少类方法用于返回 autorelease 对象。好比
id array = [NSMutableArray arrayWithCapasity:1];
// 等价于
id array = [[[NSMuatbleArray alloc] initWithCapasity:1] autorelease];
复制代码
首先来看 GNU 的源代码
首先看一下 autorelease 方法的实现
[obj autorelease];
// 表面上的实现方法
- (id)autorelease {
[NSAutoreleasePool addObject:self];
}
/**
实际上, autorelease 内部是用 Runtime 的 IMP Caching 方法实现的。在进行方法调用时,为了解决类名/方法名几区的方法运行是的函数指针,要在框架初始化时对他们进行缓存
*/
id autorelease_class = [NSAutoreleasePool class];
SEL autorelease_sel = @selector(addObject:);
IMP autorelease_imp = [autorelease_class methodForSelector:autorelease_sel];
// 实际的方法调用时使用缓存的结果值
- (id)autorelease {
(*autorelease_imp)(autorelease_class, autorelease_sel, self);
}
复制代码
再看 NSAutoreleasePool 的 addObject 类方法实现
+ (void)addObject:(id)obj {
NSAutoreleasePool *pool = 取得正在使用的 NSAutoreleasePool 对象;
if (pool) {
[pool addObject:anObj];
} else {
NSLog("不存在正在使用的 NSAutoreleasePool 对象");
}
}
复制代码
addObject 实例方法实现
// 当调用 NSObject类的 autorelease 实例方法时,这个对象就会被加到 NSAutoreleasePool 对象数组中
- (void)addObject:(id)obj {
[array addObject:obj];
}
复制代码
drain 实例方法废弃正在使用的 NSAutoreleasePool 对象的过程
// 执行顺序: drain() -> dealloc() -> emptyPool() -> [obj release] -> [emptyPool release]
- (void)drain {
[self dealloc];
}
- (void)dealloc {
[self emptyPool];
[array release];
}
- (void)emptyPool {
for (id obj in array) {
[obj release];
}
}
复制代码
C++的实现
class AutoreleasePoolPage {
static inline void *push() {
// 生成或持有 NSAutoreleasePool 对象
}
static inline id autorelease(id obj) {
// 对应 NSAutoreleasePool 类的 addObject 类方法
AutoreleasePoolPage *autoreleasePoolPage = 取得正在使用的 AutoreleasePoolPage 实例;
autoreleasePoolPage -> add(obj);
}
static inline void *pop(void *token) {
// 废弃 NSAutoreleasePool 对象
releaseAll();
}
id *add(id obj) {
// 添加对象到 AutoreleasePoolPage 的内部数组中
}
void releaseAll() {
// 调用内部数组对象的 release 类方法
}
};
// 具体调用
void *objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
}
void *objc_autoreleasePoolPop(void *ctxt) {
return AutoreleasePoolPage::push(ctxt);
}
id *objc_autorelease(void) {
return AutoreleasePoolPage::autorelease(obj);
}
复制代码
观察 NSAutoreleasePool 类方法和 autorelease 方法的运行过程
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// == objc_autoreleasePoolPush()
id obj = [[NSObject alloc] init];
[obj autorelease];
// == objc_autorelease(obj)
[pool drain];
// == objc_autoreleasePoolPop(pool);
复制代码
另外:[[NSAutoreleasePool showPools]];
能够用来确认已经被 autorelease 的对象的情况。
问题: 若是 autorelease NSAutoreleasePool
对象会如何?
ARC 有效时,id 类型和对象类型与 C语言 中的其余类型不一样,其类型必须附加 全部权修饰符 。共有如下四种
__strong 修饰符
__strong 修饰符是 id 类型和对象类型默认的全部权修饰符
// 在 ARC 有效的环境下
id obj = [[NSObject alloc] init] <==> id __strong obj = [[NSObject alloc] init];
// 在 ARC 无效的环境下
{
id obj = [[NSObject alloc] init]
[obj release];
}
复制代码
当被__strong 修饰符修饰的,本身生成的,对象在超过其做用域时:
{
// 本身生成并持有对象
id __strong obj = [[NSObject alloc] init];
/**
由于变量 obj 为强引用,因此本身持有对象
*/
}
// 由于变量超出做用域,强引用失效,因此释放对象,由于对象此时没有其余的全部者了,对象被废弃。
// 正好遵循内存管理的原则
复制代码
当对象的全部者和对象的生命周期是明确的,取得非本身生成并持有的对象时:
{
// 首先取得非本身生成的对象,可是因为__strong修饰符修饰着这个对象,因此本身持有这个对象
id __strong obj = [NSMutableArray array];
}
/**
当超出对象的做用域时,强引用失效,因此释放对象。
可是因为 [NSMutableArray array] 所生成的对象并不是本身所持有的,而是自动的加到 autoreleasePool 中,因此会在一个 RunLoop 周期结束后,自动废弃对象。
*/
复制代码
有 __strong修饰符的变量之间能够相互赋值
// 首先 obj0 强引用指向 对象A , obj1 强引用指向 对象B,表示 obj1 持有 B, obj2 不持有任何对象
id __strong obj0 = [[NSObject alloc] init]; // 对象A
id __strong obj1 = [[NSObject alloc] init]; // 对象B
id __strong obj2 = nil;
// 此时 obj0 与 obj1 强引用同一个对象 B, 没有人持有 对象A 了,因此 对象A 被废弃。
obj0 = obj1;
// 此时 obj2 指向 obj0 所持有的对象, 因此 对象B 如今被三个引用所持有。
obj2 = obj0;
// 如今 obj1 对 对象B 的强引用失效,因此如今持有 对象B 的强引用变量为 obj0,obj2
obj1 = nil;
// 同理,如今只有 obj2 持有对象B
obj0 = nil;
// 没有引用指向 对象B 了,废弃 对象B
obj2 = nil;
复制代码
__strong修饰符 也能够修饰 OC类成员变量,也能够在方法的参数上,使用附有 _strong 修饰符的变量
@interface Test : NSObject
{
id __strong obj_;
}
- (void)setObject:(id __strong)obj;
@end
@implementation Test
- (instancetype)init
{
self = [super init];
return self;
}
- (void)setObject:(id)obj {
obj_ = obj;
}
@end
// 调用函数
{
// 首先test 持有 Test 对象的强引用
id __strong test = [[Test alloc] init];
// Test对象 的 obj_ 成员,持有 NSObject 对象的强引用
[test setObject:[[NSObject alloc] init]];
}
/**
此时test强引用超出了其做用域,它失效了。
因此此时没有强引用指向 Test对象 了, Test对象会被废弃
废弃 Test 对象的同时, Test对象 的 obj_ 成员也被废弃。
因此它释放了指向 NSObject 的强引用
由于 NSObject 没有其余全部者了,因此 NSObject 对象也被废弃。
*/
复制代码
_strong修饰符 与 _weak, _autoreleasing 修饰符同样,初始化时,即便不明确指出,他们也都会自动将该引用指向nil。经过 _strong修饰符,完美的知足了 引用计数的思考方式
id类型和对象类型的全部权修饰符默认都为 __strong 因此不须要再显式的指明修饰对象的修饰符为 _strong
__weak 修饰符
_weak修饰符 的出现就是为了解决 _strong修饰符在内存管理中所带来的循环引用问题。如上例:
@interface Test : NSObject
{
id __strong obj_;
}
- (void)setObject:(id __strong)obj;
@end
@implementation Test
- (instancetype)init
{
self = [super init];
return self;
}
- (void)setObject:(id)obj {
obj_ = obj;
}
@end
// 调用函数,打印变量结果以下:
{
// 首先test1 持有 Test 对象的强引用, test2 持有 Test 对象的强引用
id __strong test1 = [[Test alloc] init]; // 对象A
id __strong test2 = [[Test alloc] init]; // 对象B
/**
TestA 对象中的 obj_ 成员变量持有着 test2指向的 对象B, 同时,test2指向的对象B中的 obj_又强引用着 对象A, 因此形成了循环引用。
*/
[test1 setObject:test2];
[test2 setObject:test1];
}
/**
当跳出做用域后,test1释放它对 对象A 的强引用
test2释放它对 对象B 的强引用
可是此时 对象A中的 obj_A 对 对象B 的强引用本应该被释放,可是因为在 对象B 中强引用了对象A,因此 obj_A 不会被释放,会一直强引用 对象B, 而同理,对象B 中的 obj_B 也不会被释放,因此它将一直强引用着 对象A, 因此此时外部没有谁引用着 对象A 和 对象B, 可是他们本身在互相引用着,这样就形成了内存泄漏!(所谓内存泄漏,指的就是应该被废弃的对象,却在超出其生存周期变量做用域时还继续存在着)
*/
/**
打印变量结果以下:
其中 test1 对象中强引用 test2 对象, test2对象 又强引用 test1 对象,形成无尽的循环。
*/
复制代码
而下面这种状况:
{
id test = [[Test alloc] init];
[test setObject:test];
}
/**
当强引用test 的做用域结束后,它释放了对 Test 对象的引用。
可是 Test对象 内部还保留着 对 Test对象 的强引用,因此 Test对象 被引用着,因此不会被回收
*/
// 也会发生内存泄漏!
复制代码
因此此时,就很是须要一个 __weak修饰符 来避免循环引用
// 弱引用与强引用正好相反,不可以持有对象实例。
// 这样写会发出警告:Assigning retained object to weak variable; object will be released after assignment
// 表示由于没有人持有着 NSObject 对象,因此该对象一旦被建立就会当即被销毁
id __weak obj = [[NSObject alloc] init];
// 正确的使用弱引用的方式
{
// 本身生成并持有 NSObject 对象
id obj = [[NSObject alloc] init];
// 由于 NSObject 对象已经被 obj 强引用着, 因此此时 obj1 对它使用弱引用也没有关系,
// 不会使它的引用计数 +1
id __weak obj1 = obj;
}
/**
当超出变量的做用域时, obj 对 NSObject对象 的强引用消失,
此时没有人持有 NSObject对象 了。
NSObject对象 被废弃
*/
复制代码
对上述循环引用的例子进行修改以下:
@interface Test : NSObject
{
id __weak obj_;
}
- (void)setObject:(id __strong)obj;
@end
@implementation Test
- (instancetype)init
{
self = [super init];
return self;
}
- (void)setObject:(id)obj {
obj_ = obj;
}
@end
// 调用函数,打印变量结果以下:
{
// 首先test1 持有 Test 对象的强引用, test2 持有 Test 对象的强引用
id __strong test1 = [[Test alloc] init]; // 对象A
id __strong test2 = [[Test alloc] init]; // 对象B
/**
TestA 对象中的 obj_ 成员变量弱引用着 test2指向的 对象B, 同时,test2指向的 对象B 中的 obj_又弱引用着 对象A。
*/
[test1 setObject:test2];
[test2 setObject:test1];
}
/**
当跳出做用域后,test1释放它对 对象A 的强引用
test2释放它对 对象B 的强引用
此时,因为 对象中的 obj_变量只拥有对对象的弱引用,因此 没有谁持有着 对象A,和对象B,他们被释放,没有形成循环引用!
*/
复制代码
__weak修饰符 的另外一优势:当持有某个对象的弱引用时,若是该对象被废弃,则弱引用将自动失效,而且会被置为 nil的状态(空弱引用)
id __weak obj1 = nil;
{
// 本身生成并持有对象
id __strong obj0 = [[NSObject alloc] init];
// obj1 如今也指向 NSObject对象
obj1 = obj0;
// 此时打印 obj1 有值
NSLog(@"A = %@", obj1);
}
/**
当变量 obj0 超出做用域,它再也不持有 NSObject对象,
因为 obj1 是弱引用,因此它也不持有 NSObject对象
因为没人持有 NSObject对象, NSObject对象被废弃
被废弃的同时, obj1 变量的弱引用失效, obj1 被从新赋值为 nil
*/
NSLog(@"B = %@", obj1);
/**
结果打印以下:
2017-12-14 15:16:39.859875+0800 littleTest[10071:1377629] A = <NSObject: 0x10054da70>
2017-12-14 15:16:39.860432+0800 littleTest[10071:1377629] B = (null)
*/
复制代码
__unsafe_unretained 修饰符
_unsafe_unretained 修饰符 是不安全的修饰符,在 iOS4 之前用来代替 _weak修饰符
id __unsafe__unretained obj1 = nil;
{
id __strong obj0 = [[NSObject alloc] init];
obj1 = obj0;
NSLog(@"A = %@", obj1);
}
NSLog(@"B = %@", obj1);
/**
该源码没法正确执行,由于 __unsafe_unretained修饰符 使变量既不强引用对象,也不弱引用对象。
当 obj0 超出做用域时, NSObject 无引用,因此被释放
在此同时, obj1 有时会错误访问对象,造成下面这种打印
2017-12-14 15:38:21.462724+0800 littleTest[10140:1399554] A = <NSObject: 0x10044eea0>
2017-12-14 15:38:21.463007+0800 littleTest[10140:1399554] B = <NSObject: 0x10044eea0>
有时会发生错误,直接使程序崩溃。
形成这两种状况的本质为: obj1 访问的对象已经被废弃了,形成了 垂悬指针!
*/
复制代码
因此,须要注意的是,当使用 _unsafe_unretained 修饰符 访问对象时,必需要确保该对象确实是真实存在的。
__autoreleasing 修饰符
在 ARC 有效时,咱们不可使用以下代码,由于这些代码是在非 ARC 环境下使用的:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
复制代码
做为替换,在 ARC 有效时, 咱们会使用
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
复制代码
他们的对应关系如图:
咱们能够非显式的使用 __autoreleasing 修饰符 。
状况一:当取得非本身生成并持有的对象时,虽然可使用 alloc/new/copy/mutableCopy 之外的方法来取得对象,可是该对象已经被注册到 autoreleasePool 中。这和在 ARC无效 时,调用 autorelease 方法取得的结果相同。这是由于编译器会自动检查方法名是否以alloc/new/copy/mutableCopy 开始,若是不是,则自动将对象注册到 autoreleasePool 中。
@autoreleasepool {
// 首先取得非本身生成的对象,由于 obj 的强引用,因此它持有这个对象
// 由于这个对象的方法名不是以 alloc/new/copy/mutableCopy 开头的,因此他被自动注册到 autoreleasePool中了。
{
id __strong obj = [NSMutableArray array];
}
/**
当变量超出其做用域时,他失去对这个对象的强引用。因此它会释放本身所持有的对象
可是此时 autoreleasePool 中还持有着对这个对象的引用,因此它不会当即被废弃
*/
}
/**
当 autoreleasePool 的做用域也结束后,没有人持有这个对象了,因此它被废弃了。
*/
复制代码
验证上述说法,首先:建立对象的方式为 [NSMutableArray array]
:
// 在 autoreleasepool 的做用域外定义一个 obj1 持有弱引用
id __weak obj1 = nil;
@autoreleasepool {
{
id __strong obj = [NSMutableArray array];
obj1 = obj;
NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);
/**
打印结果:
2017-12-14 16:32:07.118513+0800 littleTest[10242:1444479] (
)
2017-12-14 16:32:07.118819+0800 littleTest[10242:1444479] (
)
2017-12-14 16:32:07.118850+0800 littleTest[10242:1444479] (null)
结果代表:当obj0超出其做用域时,它失去了对对象的引用。可是因为该对象被自动注册到 autoreleasepool 中,使得第二个 NSLog 打印时 obj1 依旧弱引用着这个对象,当第三个 NSLog 打印时,因为 autoreleasepool 已经被清空,因此这个对象也被销毁了, obj1 又被重置为 nil
*/
复制代码
此时,建立对象的方式为:[[NSMutableArray alloc] init]
id __weak obj1 = nil;
@autoreleasepool {
{
id __strong obj = [[NSMutableArray alloc] init];
obj1 = obj;
NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);
/**
打印结果以下:
2017-12-14 16:36:09.584864+0800 littleTest[10257:1449554] (
)
2017-12-14 16:36:09.585131+0800 littleTest[10257:1449554] (null)
2017-12-14 16:36:09.585149+0800 littleTest[10257:1449554] (null)
这是由于,使用 alloc/new/copy/mutableCopy 方法建立对象时,不会将该对象自动的放入 autoreleasePool 中,这就使得当 obj0 超出其做用域后,就没有人强引用着 NSMutableArray 对象了,该对象也就被废弃了。
*/
复制代码
如下为 取得非本身生成并持有的对象 时所调用方法:
+ (id)array {
return [[NSMutableArray alloc] init];
}
/**
这段代码也没有使用 __autorelease修饰符,因此这个方法内部的对象不会被注册到 autoreleasePool 中。
*/
// 上述方法也能够写成以下形式:
+ (id)array {
id obj = [[NSMutableArray alloc] init];
return obj;
}
/**
由于 return 使得 obj 超出做用域,因此它所指向的对象 NSMutableArray 会被自动释放,可是由于 return 将这个对象做为函数的返回值返回给主调函数,因此这个对象不会被废弃。而且因为这个对象的生成方法是将其做为返回值,不是由alloc/new/copy/mutableCopy 方法建立的,因此 NSMutableArray 对象会被自动添加到 autoreleasePool 中
*/
复制代码
状况二: 在访问有 __weak修饰符 的变量时,实际上一定会访问注册到 autoreleasePool 中的对象
id __weak obj1 = obj0;
NSLog(@"class=%@", [obj1 class]);
// 等价于
id __weak obj1 = obj0;
id _autoreleasing temp = obj1;
NSLog(@"class=%@", [temp class]);
/**
出现这种状况的缘由是由于:__weak修饰符 只持有对象的弱引用,这样无法保证它访问对象的过程当中,对象不被废弃。因此咱们将他要访问的对象放到 autoreleasePool 中,这样就会使得 @autoreleasePool块 结束以前都能保证该对象的存在。
*/
复制代码
状况三:因为 id obj <==> id __strong obj
因此咱们但愿能推出 id *obj <==> id __strong *obj
可是实际上并不是如此,实际状况是 id *obj <==> id __autoreleasing obj
同理:NSObject **obj <==> NSObject * __autoreleasing *obj
,像这样的,id 的指针或对象的指针在没有显示的指定时,会被附加上 __autoreleasing修饰符
// 例如 NSString 中的这个方法
stringWithContentsOfFile:(nonnull NSString *) encoding:(NSStringEncoding) error:(NSError * _Nullable __autoreleasing * _Nullable)
// 使用这个方式的源代码以下:
NSError *error = nil;
BOOL result = [obj performOperationWithError:&error];
// 函数声明以下
- (BOOL)performOperationWithError:(NSError **)error;
// 等价于
- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
/**
之因此使用 __autoreleasing 做为修饰符,是由于咱们这个方法声明的参数的是 error 这个指针变量的指针,也就是 须要传递 error 的地址。而在这个方法内部的执行以下,它改变了 error 这个指针所指向的内容。使其指向了一个由 alloc 生成的对象。而咱们须要明白的内存管理的思考方式为:除了由 alloc/new/copy/mutableCopy 生成的对象外,其余方式生成的对象都必须要注册到 autoreleasePool 中。并取得非本身所持有的对象。因此将变量声明为 (NSError * __autoreleasing *)error 就能够实现这一目的。
*/
- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error {
*error = [[NSError alloc] initWithDomain:MyAppDomain code:errorCode userInfo:nil];
return NO;
}
/**
可能对于不熟悉 C语言 的小伙伴来讲,不是很明白为何这里非要将函数参数声明为 “指针的指针”,这是由于当咱们仅仅把参数声明为指针时,方法就变为以下,当咱们给函数传递指针时,默认会生成跟指针类型相同的实例变量。当咱们在这个方法中操做指针时,咱们觉得操做的是指针,实际上只是这复制的实例变量。也就是说,在这个例子中 error 就是这个复制的实例变量。当这个方法结束时,error 会被释放,其所指向的内容也会一并被释放。因此此时外部的 error 依旧指向 nil。没有任何改变。
而当咱们使用 (NSError * __autoreleasing *)error 做为参数时,虽然复制的实例变量状况仍是存在,可是此次复制的是“指针的指针”,也就是说,它指向跟参数指针相同指针地址, 在函数内部使用 *error 获取到了指针地址,使其指向了 NSError对象 。这样,虽然函数当出了其做用域时,那个复制的实例变量被销毁了,可是它改变了函数外部 error 指针所指向的对象,使其从 nil 变成了 NSError对象。
*/
- (BOOL)performOperationWithError:(NSError *)error {
error = [[NSError alloc] initWithDomain:MyAppDomain code:errorCode userInfo:nil];
return NO;
}
复制代码
对于函数传递指针及指针的指针 还不明白的请看这里
👇的代码会产生编译错误:
NSError *error = nil;
NSError **perror = &error;
//Pointer to non-const type 'NSError *' with no explicit ownership
复制代码
由于
// 须要改变perror的全部权修饰符
NSError *error = nil;
NSError *__strong *perror = &error;
// 对于其余类型的全部权修饰符也同样
NSError __weak *error = nil;
NSError *__weak *perror = &error;
/**
但是咱们刚刚在调用
- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error
方法时,并无将 NSError *error 转化为 __autoreleasing修饰符修饰的,这是为何?
*/
// 实际上,编译器自动帮咱们转化了修饰符
NSError *error = nil;
NSError *_autoreleasing *temp = nil;
BOOL result = [obj performOperationWithError:&temp];
error = temp;
复制代码
像 NSAutoreleasePool 同样, autoreleasepool 也能够嵌套使用。例如在 iOS 程序中,整个程序都被包含在 @autoreleasepool块 中。
NSRunLoop等实现不管 ARC 是否有效,都可以随时释放注册到 autoreleasepool 中的对象。
在 ARC 有效时编译代码,必须遵照如下规则:
不能使用 retain/release/retainCount/autorelease
不能使用 NSAllocateObject/NSDeallocateObject
必须遵照内存管理方法命名规则
在 ARC 无效时,用于对象生成/持有须要遵循以下规则:alloc/new/copy/mutableCopy。以上述名称开始的方法在返回对象时,必须得返回给调用方应当持有的对象。这点在 ARC 有效时也是同样。
在 ARC 有效时,追加的一条命名规则:init
以 init 开始的方法规则要比 alloc/new/copy/mutableCopy 更加严格。该方法必须得是实例方法。不容许是类方法。而且返回对象应该为 id 类型或者该方法声明类的对象类型,或者是该类的超类或子类。该返回对象并不注册到 autoreleasepool 中。基本上只是对 alloc 方法返回值的对象进行初始化操做并返回该对象。
// 如下为使用该方法的源代码: init 方法会初始化alloc 方法返回值,而且返回该对象
id obj = [[NSObject alloc] init];
// 下列是不容许的,由于它没有返回对象
- (void)initTheData:(id)data;
// 另外,👇方法虽然也有init, 但它不包含在命名规则里,由于他是一个单词 initialize
- (void)initialize;
复制代码
不能显示的调用 dealloc
[super dealloc];
使用 @autoreleasepool块 代替 NSAutoreleasePool
不能使用区域 NSZone
对象型变量不能做为 C语言 结构体的成员
C语言 的结构体中若是存在 OC对象型变量 会引发编译错误
若是非要将对象加入结构体,则可强制转化为 void * 或者附加 _unsafe_unretained修饰符 ,由于被 _unsafe_unretained修饰符所修饰的对象,已经不属于编译器的内存管理对象了。
struct Data {
NSMutableArray __unsafe__unretained *array
}
复制代码
显示的转换 id 和 void *
当 ARC 无效时,将 id变量 强制转化为 void *变量 不会出现问题
id obj = [[NSObject alloc] init];
void *p = obj;
// 将 void * 赋给 id变量 中,调用他的实例方法,运行时也不会出现问题
id o = p;
[o release];
复制代码
当 ARC 有效时,会引发编译错误。此时,id型 或 对象型变量 赋值给 void * 或者逆向赋值时都须要进行特定的转换,若是只是想单纯的赋值则可使用 bridge转换
id obj = [[NSObject alloc] init];
// id转化为 void *,它的安全性比 __unsafe__unretained 还要低,一不当心就会有垂悬指针
void *p = (__bridge void *)obj;
// void * 转换为 id
id o = (__bridge id)p;
复制代码
__bridge转换 中还包括 _bridge_retained转换, _bridge_transfer转换
id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)obj
复制代码
该代码在 非ARC 环境下
id obj = [[NSObject alloc] init];
void *p = obj;
// __bridge__retained转变为了 retain,使得 p 和 obj 都持有了这个对象
[(id)p retain];
复制代码
一个其余的例子:
void *p = 0;
{
id obj = [[NSObject alloc] init];
p = (__bridge_retained void *)obj;
}
NSLog(@"class = %@", [(__bridge_retained)p class]);
复制代码
该代码在 非ARC 环境下
void *p = 0;
{
id obj = [[NSObject alloc] init];
p = [obj retain];
[obj release];
}
/**
此时 p 依旧持有对 NSObject对象 的引用
*/
NSLog(@"class = %@", [(__bridge_retained)p class]);
复制代码
__bridge_transfer,被转换的变量所持有的对象在该变量被赋值给转换目标变量后释放
id obj = (__bridge_transfer id)p;
复制代码
非ARC 环境下
id obj = (id)p;
[obj retain];
[(id)p release];
复制代码
Objective-C 对象 与 Foundation对象
属性声明的属性 与 全部权修饰符 对应的关系
属性声明的属性 | 全部权修饰符 |
---|---|
assign | _unsafe_unretained |
copy | __strong(赋值的是被复制的对象) |
retain | __strong |
strong | __strong |
unsafe_unretained | _unsafe_unretained |
weak | __weak |
静态数组的状况:
// 将附有各类修饰符的变量做为静态数组的使用状况
// 好比
id __weak obj[10];
// 除了 __unsafe__unretained修饰符以外的其余修饰符都是会将数组元素的值默认初始化为nil
// 当数组超出其变量做用域时,内存管理也一样适用于他之中的各个对象
复制代码
动态数组:在这种状况下,根据不一样的目的选择使用 NSMutableArray, NSMutableDictionary, NSMutableSet 等 Foundation 框架中的容器,这些容器会恰当的持有追加的对象并会为咱们管理这些对象。
看一下动态数组在 C语言 中的实现
// 首先,声明一个动态数组须要使用指针。来表示指针的地址
id __strong *array = nil;
//这里是因为 id * 类型的指针默认修饰符为 id __autoreleasing * 类型, 因此有必要显示的指定为 __strong 修饰符。另外,虽然保证了附有 __strong修饰符 的 id 类型变量被初始化为 nil, 可是不保证 array变量, 也就是 id指针型变量 被初始化为 nil
// 当类型是其余类型时,以下:
NSObject * __strong *array = nil;
// 以后,使用 calloc函数 确保想分配的,附有 __strong修饰符变量 的容量占有的内存块
array = (id __strong *)calloc(entries, sizeof(id));
// 其中 entries 表示内存块的数量。而且 calloc 函数将数组中的每一个变量指向的对象都自动初始化为 nil
// 注意这里若是使用了 malloc函数 来分配内存, 则须要手动的将每一个变量所指向的对象都初始化为 0,注意这里只能使用 memset等函数 来进行初始化赋值
// 而后,经过 calloc函数 分配的动态数组就能彻底按照静态数组的方法使用
array[0] = [[NSObject alloc] init];
// 可是在动态数组中操做 __strong修饰符 的变量与静态数组有很大差别,须要本身手动释放数组,可是当它释放时,必须手动的先将数组的每一个变量都置为nil,此时不能使用 memset等函数 将数组中的元素值设为 0 。这也会内存泄漏
for (NSInteger i = 0; i < entries; ++i) {
array[i] = nil;
}
free(array);
复制代码
观察赋值给附有 __strong修饰符 的变量在实际程序中究竟是如何运行的,👇代码(首先是正常的会使引用计数 +1 的 alloc/new/copy/mutableCopy 方法):
{
id __strong obj = [[NSObject alloc] init];
}
复制代码
该段代码转化为汇编代码后,为(具体如何转化为汇编代码,请看个人另外一篇文章):
.section __TEXT,__text,regular,pure_instructions
.macosx_version_min 10, 13
.globl _main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## BB#0:
pushq %rbp
Lcfi0:
.cfi_def_cfa_offset 16
Lcfi1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Lcfi2:
.cfi_def_cfa_register %rbp
subq $16, %rsp
movl $0, -4(%rbp)
movq L_OBJC_CLASSLIST_REFERENCES_$_(%rip), %rax
movq L_OBJC_SELECTOR_REFERENCES_(%rip), %rsi
movq %rax, %rdi
callq _objc_msgSend
leaq -16(%rbp), %rdi
xorl %ecx, %ecx
movl %ecx, %esi
movq %rax, -16(%rbp)
callq _objc_storeStrong
xorl %eax, %eax
addq $16, %rsp
popq %rbp
retq
.cfi_endproc
.section __DATA,__objc_classrefs,regular,no_dead_strip
.p2align 3 ## @"OBJC_CLASSLIST_REFERENCES_$_"
L_OBJC_CLASSLIST_REFERENCES_$_:
.quad _OBJC_CLASS_$_NSObject
.section __TEXT,__objc_methname,cstring_literals
L_OBJC_METH_VAR_NAME_: ## @OBJC_METH_VAR_NAME_
.asciz "alloc"
.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip
.p2align 3 ## @OBJC_SELECTOR_REFERENCES_
L_OBJC_SELECTOR_REFERENCES_:
.quad L_OBJC_METH_VAR_NAME_
.section __TEXT,__objc_methname,cstring_literals
L_OBJC_METH_VAR_NAME_.1: ## @OBJC_METH_VAR_NAME_.1
.asciz "init"
.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip
.p2align 3 ## @OBJC_SELECTOR_REFERENCES_.2
L_OBJC_SELECTOR_REFERENCES_.2:
.quad L_OBJC_METH_VAR_NAME_.1
.section __DATA,__objc_imageinfo,regular,no_dead_strip
L_OBJC_IMAGE_INFO:
.long 0
.long 64
.subsections_via_symbols
复制代码
简化后的模拟代码为:
// 首先发送消息给 NSObject 类,消息内容为 alloc 指令,而后将结果赋值给 obj
id obj = objc_msgSend(NSObject, @selector(alloc));
// 而后将 init 消息发送给 obj
objc_msgSend(obj, @selector(init));
// 最后释放 obj
objc_release(obj);
// 由此可知,在 ARC 有效时,自动插入了 release 方法
复制代码
取得 非本身生成的,可是本身持有的 对象:
id __strong obj = [NSMutableArray array];
复制代码
转化成汇编语言:
.section __TEXT,__text,regular,pure_instructions
.macosx_version_min 10, 13
.globl _main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## BB#0:
pushq %rbp
Lcfi0:
.cfi_def_cfa_offset 16
Lcfi1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Lcfi2:
.cfi_def_cfa_register %rbp
subq $16, %rsp
movl $0, -4(%rbp)
movq L_OBJC_CLASSLIST_REFERENCES_$_(%rip), %rax
movq L_OBJC_SELECTOR_REFERENCES_(%rip), %rsi
movq %rax, %rdi
callq _objc_msgSend
movq %rax, %rdi
callq _objc_retainAutoreleasedReturnValue
leaq -16(%rbp), %rdi
xorl %ecx, %ecx
movl %ecx, %esi
movq %rax, -16(%rbp)
callq _objc_storeStrong
xorl %eax, %eax
addq $16, %rsp
popq %rbp
retq
.cfi_endproc
.section __DATA,__objc_classrefs,regular,no_dead_strip
.p2align 3 ## @"OBJC_CLASSLIST_REFERENCES_$_"
L_OBJC_CLASSLIST_REFERENCES_$_:
.quad _OBJC_CLASS_$_NSMutableArray
.section __TEXT,__objc_methname,cstring_literals
L_OBJC_METH_VAR_NAME_: ## @OBJC_METH_VAR_NAME_
.asciz "array"
.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip
.p2align 3 ## @OBJC_SELECTOR_REFERENCES_
L_OBJC_SELECTOR_REFERENCES_:
.quad L_OBJC_METH_VAR_NAME_
.section __DATA,__objc_imageinfo,regular,no_dead_strip
L_OBJC_IMAGE_INFO:
.long 0
.long 64
.subsections_via_symbols
复制代码
简化后的汇编语言:
// 首先发送 array 消息给接收者 NSMutableArray, 而后将结果的返回值赋给 obj
id obj = objc_msgSend(NSMutableArray, @selector(array));
/**
obj_retainAutoreleasedReturnValue 函数主要用于最优化程序运行,顾名思义 obj_retainAutoreleasedReturnValue 表示的是 “持有 Autorelease 的返回值”,表示的是,它是用于本身持有对象的函数,但他持有的对象应为返回注册在 autoreleasepool 中对象的方法,,或是函数的返回值。像这段源代码同样,也就是 obj 须要被 __strong 所修饰在调用 alloc/new/copy/mutableCopy 之外的方法时,由编译器插入该函数
*/
objc_retainAutoreleasedReturnValue(obj);
// 释放 obj
objc_release(obj);
复制代码
因为,objc_retainAutoreleasedReturnValue 函数老是成对出现的,因此实际上它还有一个姐妹:objc_autoreleaseReturnValue, 它主要用在 alloc/new/copy/mutableCopy 之外的方法生成对象时的返回对象上,也就是如👇所示
+ (id)array {
return [[NSMutableArray alloc] init];
}
// 转化成汇编后的简化代码
+ (id)array {
id obj = objc_msgSend(NSMutableArray, @selector(alloc));
objc_msgSend(obj, @selector(init));
// 此时,返回注册到 autoreleasepool 中对象的方法:使用了 obj_autoreleaseReturnValue 函数来返回注册到 autoreleasepool 中的对象,可是 obj_autoreleaseReturnValue 方法与 obj_autorelease 方法不一样,通常不只限于注册对象到 autoreleasepool 中
return objc_autoreleaseReturnValue(obj);
}
/**
objc_autoreleaseReturnValue 方法会检查使用该函数的方法或调用方的执行命令列表,
1.若是方法或函数的调用方在调用了方法或函数后紧接着调用了 objc_retainAutoreleasedReturnValue() 函数,那么就不会将返回的对象注册到 autoreleasepool 中,而是直接传递到方法或函数的调用方去。
2.若是方法或函数的调用方在调用了方法或函数后紧接着没调用objc_retainAutoreleasedReturnValue() 函数,那么就会将返回对象注册到 autoreleasepool 中。
而 objc_retainAutoreleasedReturnValue() 函数与 objc_retain 函数不一样,他即使不注册到 autoreleasepool 中,也能正确的获取对象。
经过 objc_autoreleaseReturnValue 和 objc_retainAutoreleasedReturnValue 方法的协做,能够不将对象注册到 autoreleasepool 中二直接传递,这一过程达到最优化
*/
复制代码
下面咱们来看看 __weak修饰符 原理实现:
{
id __weak obj1 = obj;
}
/**
编译器的模拟代码
*/
id obj1;
// 首先经过 obj_initWeak 函数初始化附有 __weak 修饰符的变量
objc_initWeak(&obj1, obj);
// 而后在变量做用域结束时,经过 obj_destroyWeak 函数释放该变量
objc_destroyWeak(&obj1);
/**
其中,objc_initWeak 函数的做用是:将附有 __weak修饰符 的变量初始化为 0 后,会将赋值的对象做为参数调用 objc_storeWeak 函数
obj_destroyWeak 函数的做用是:将 0 做为参数调用 obj_storeWeak 函数
*/
objc_initWeak(&obj1, obj); <==> obj1 = 0; objc_storeWeak(&obj1, obj);
objc_destroyWeak(&obj1) <==> objc_storeWeak(&obj1, 0);
/**
objc_storeWeak 函数把 第二个参数 的赋值对象的 地址 做为 "键值",将 第一个参数 的附有 __weak修饰符 的变量的"地址"注册到 weak 表 中。若是第二个参数为 0 ,则把变量的地址从 weak 表中删除
weak 表与引用计数表相同,实现方式都为"散列表"。若是使用 weak 表,将废弃对象的地址做为键值进行搜索,就能高速的获取对应的附有 weak修饰符 的变量的地址。另外,因为一个对象能够同时赋值给多个附有 weak修饰符 的变量中,因此对于一个键值,可注册多个变量的地址。
*/
复制代码
释放对象时,废弃没人持有的对象的同时,程序是如何操做的,下面咱们来跟踪观察,对象将经过 objc_release 方法释放
根据以上步骤可知:__weak修饰符 所修饰的变量所引用的对象被废弃,该变量被置为 nil 获得实现。可是由此可知,若是大量使用附有 _weak修饰符修饰变量,将会产生性能问题。
在使用 __weak修饰符 时, 若是以下方式,会引发警告
id __weak obj = [[NSObject alloc] init];
// 由于该对象刚被建立就会被释放
// 编译器的模拟代码
id obj;
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
// 虽然本身生成并持有的对象经过 objc_initWeak 函数被赋值给附有 __weak修饰符 的变量中,可是编译器判断它没有持有者,因此该对象当即经过 objc_release 方法释放
objc_initWeak(&obj, tmp);
objc_release(tmp);
objc_destroyWeak(&obj);
// 这样一来, nil 就会被赋值给引用废弃对象的附有 __weak修饰符 的变量
复制代码
关于当即释放对象的一些思考
// 已知如下代码会引发编译器的警告,这是由于编译器判断生成并持有的对象不能继续持有,由于没有强引用指向它
id __weak obj = [[NSObject alloc] init];
// ---------------------------------------------------------------------------------
// 附有 __unsafe_unretained 修饰符的变量会怎样? 也会产生警告
id __unsafe_unretained obj = [[NSObject alloc] init];
// 转换成编译器的模拟代码:
id obj = obj_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
// obj_release 函数马上释放了生成并持有的对象,这样该对象的垂悬指针被赋给 obj
objc_release(obj);
// ---------------------------------------------------------------------------------
// 若是在生成对象的时候不把它赋给变量会怎样?
// 在 非ARC 环境下,必然会发生内存泄漏
// 可是在 ARC 环境下,因为不能继续持有该对象,会当即调用 obj_release 函数,因为 ARC 的处理,这样的代码不会产生内存泄漏
[[NSObject alloc] init];
// ARC 下生成的代码
id tmp = obj_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_release(tmp);
// ---------------------------------------------------------------------------------
// 是否能够调用被当即释放掉的对象的实例方法?
(void)[[[NSObject alloc] init] hash];
// 该代码会变成以下形式:
id tmp = obj_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_msgSend(tmp, @selector(hash));
objc_release(tmp);
// 因此,obj_release 方法是在该对象实例方法调用完成后才会被调用,因此能够调用被当即释放的对象的实例方法
复制代码
看👇代码
{
id __weak obj1 = obj;
NSLog(@"%@", obj1);
}
// 该源码可转化为以下形式
id obj1;
objc_initWeak(&obj1, obj);
// objc_loadWeakRetained 取出附有 __weak修饰符 变量所引用的对象并 retain
id tmp = objc_loadWeakRetained(&obj1);
// 将对象注册到 autoreleasepool 中
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destroyWeak(&obj1);
复制代码
由此可知:由于附有 __weak修饰符 的变量所引用的对象像这样被注册到 autoreleasepool 中,因此在 @autoreleasepool 块结束以前均可以放心的使用 _weak修饰的变量。可是,不能大量的使用附有 _weak修饰符 修饰的变量,不然会引发注册到 autoreleasepool 中的对象大大增长,所以在使用附有 _weak修饰符 的变量时,最好先暂时赋给附有 _strong修饰符 的变量后再使用。若看不太懂则能够只看下面代码:
// 下面这段代码会使变量 o 所赋值的对象被注册到 autoreleasepool 中 5 次
{
id __weak o = obj;
for (int i = 0; i < 5; ++i) {
NSLog(@"%d -- %@", i, o);
}
}
// 而下面这段代码只会使变量 o 所赋值的对象被注册到 autoreleasepool 中 1 次
{
id __weak o = obj;
id tmp = o;
for (int i = 0; i < 5; ++i) {
NSLog(@"%d -- %@", i, tmp);
}
}
复制代码
allocWeakReference/retainWeakReference
实例方法返回 NO 的状况。(这种状况没有被写入 NSObject 类的接口说明文档中),也就是说,这两个方法咱们通常不会接触到。将对象赋值给附有 __autoreleasing修饰符 的变量等同于 ARC 无效时,调用对象的 autorelease 方法。
首先看一下使用 alloc/new/copy/mutableCopy 时的状况
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
// 模拟代码
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelease(obj);
objc_autoreleasPoolPop(pool);
复制代码
再看一下使用 alloc/new/copy/mutableCopy 之外的方法时的状况
@autoreleasepool {
id __autoreleasing obj = [NSMutableArray array];
}
// 模拟代码
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_autorelease(obj);
objc_autoreleasPoolPop(pool);
// 虽然 obj 持有对象的方法变为 objc_retainAutoreleasedReturnValue, 可是将 obj 所引用的对象注册到 autoreleasepool 中的方法并无改变
复制代码
引用计数数值自己究竟是什么?
// 这个函数为得到引用计数的函数数值
uintptr_t _objc_rootRetainCount(id obj);
{
id __strong obj = [[NSObject alloc] init];
NSLog(@"retain count = %d", _objc_rootRetainCount);
}
// 打印结果:retain count = 1
复制代码
咱们实际上并不能彻底信任 _objc_rootRetainCount 这个函数所取得的数值,由于有时对于已经释放的对象以及不正确的对象地址,有时也会返回 1 。 而且在多线程中使用对象的引用计数数值,由于有竞争状态的问题,因此取得的数值并不必定彻底可信