接着昨天的来:
16.weak修饰符另一个优点:在持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效且处于nil被赋值状态(空弱引用)。
所以这个功能的作用就是:通过检查附有__weak修饰符的变量是否为nil可以判断被赋值的对象是否已废弃。
__autorelease修饰符:
17.ARC有效时,要通过将对象赋值给附加了_autoreleasing修饰符的变量等价于在ARC无效时,对象被注册到autorelease
也可以理解为ARC有效时,用__autorelease修饰符的变量替代autorelease方法
18.又差点忘的一个知识点:
strong修饰符会自动进行release操作!所以alloc/new/copy/mutableCopy方法来取得对象 这些方法都是强引用,相当于strong,不会自动将返回值的对象注册到autoreleasepool.
所以这里就引申出编译器的另一个功能:编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasepool 是的话就是上面说的
19.下面有一个很有意思的调用:
@autoreleasepool {
//取得非自己生成并持有的对象
id __strong obj = [NSMutableArray array];
//因为变量obj为强引用,所以自己持有对象
//由编译器判断其方法名后,自动注册到autoreleasepool中
}
离开大括号后:
obj超出其作用域,强引用失效 所以自动释放自己持有的对象
同时,随着@autoreleasepool块的结束 注册到@autoreleasepool块中的所有对象都被自动释放
但是!!!因为对象的所有者不存在 所以废弃对象
20.一个疑问:
- (id)array
{
id obj = [[NSMutableArray alloc] init];
return obj;
}
因为该对象作为函数的返回值,编译器会自动将其注册到autoreleasepool。
那么注册进去之后呢?是用的时候取出来还是?
是会一直在pool中吗?什么时候被销毁啊
emmm答案就是 假如在@autoreleasepool 里面这样使用该方法:
id __strong obj = [NSMutableArray array];
那么在执行完array方法后 现在obj被注入自动释放池了,然后程序返回
id __strong obj = [NSMutableArray array];
这一行 现在现在obj已被注入自动释放池 那么什么时候释放呢?那当然是随着autoreleasepool被销毁后一起释放啊!
21.在访问附有__weak修饰符的变量时必须访问注册到autoreleasepool中的对象,比如:
id __weak obj1 = obj0;
id __autoreleaseing tmp = obj1;
//就是想要访问 obj1 就得用有__autoreleaseing属性的对象来访问
原因是:因为__weak修饰符只持有对象的弱引用,而在访问引用对象的过程中,该对象有可能被废弃。如果把要访问的对象注册到autoreleasepool中(id __autoreleaseing tmp = obj1;)
那么在@autoreleasepool块结束之前都能确保该对象存在
22.所以对__autoreleaseing属性总结下来就是:
使用附有__autoreleaseing修饰符的变量作为对象取得参数,与除alloc/new/copy/mutableCopy之外其他方法的返回值取得对象完全一样,都会注册到autoreleasepool,并取得非自己生成并持有的对象
23.对象指针必须附加__strong修饰符,保证其所有权修饰符必须一致:
NSError * error = nil;
NSError * __strong * pError = &error;
//这里用strong修饰是因为 什么修饰符都不添加 默认error也用strong来修饰
24.调用非公开函数_objc_autoreleasePoolPrint()函数可以打印出自动释放池中的内容,有效地帮我们调试 注册到autoreleasepool上的对象
25.在编写程序的过程中,在ARC有效的情况下,它会遵守一定的规则
1、不能使用retain/release/retainCount/autorelease。
2、不能使用NSAllocateObject和NSDeallocateObject
的。
3、须遵守内存管理的方法命名规则
4、不要显式调用dealloc
5、使用@autoreleasepool块替代NSAutoreleasePool
6、不能使用区域NSZone
7、对象型变量不能作为C语言结构体(struct/union)的成员
8、显式转换“id”和“void”
第一项:"不能使用retain/release/retainCount/autorelease。" 很明显,当程序使用了ARC进行编程的话,是禁止键入retain/release代码的,因为ARC有效时,调用了retain/release会造成编译器错误。
使用retain/release/retainCount/autorelease方法只有在ARC无效且手动进行内存管理的情况下使用
第二项:"不能使用NSAllocateObject和NSDeallocateObject的" 其实
id objc = [[NSObject alloc]init];
苹果内部是直接调用NSAllocateObject来生成并持有对象的,他同retain一样,只有在没有使用ARC的情况下才可以使用。如果在ARC有效时使用了的话,会造成编译器错误,提示:not available in automatic reference mode.
第三项:"须遵守内存管理的方法命名规则" 在ARC无效的时候,用于对象生成/持有的方法必须遵守以下的命名规则
alloc
new
copy
mutableCopy
以上述名称开始的方法,应当返回调用方所应当持有的对象,这在ARC有效时也同样有效,只不过在ARC有效时,会更加的严格。并且,在ARC有效时,还需要再添加一条命名规则:
init
以init开始的方法的规则,要比alloc\new/copy/mutableCopy更加的严格,该方法必须是实例方法,而且必须是返回对象。返回的类型必须是id类型或者该方法声明类的对象类型,亦或者是该类的超类型或者子类型。该返回对象并不注册到autoreleasepool上,基本上只是对alloc方法返回值的对象进行初始化处理并返回对象。
例如:
id objc = [[NSObject alloc]init];
方法的命名规则可以是:
-(id)initWithObject:(NSObject *)obj;
像下面这种命名规则是错误的,因为他没有返回对象:
-(void)initWithObject:(NSObject *)obj
第四项:"不要显式调用dealloc" 无论ARC是否有效,只要是对象的所有者不在持有该对象,该对象就会被废弃,对象被废弃时,无论ARC是否有效,都会调用dealloc方法
-(void)dealloc
{
/*
*此处是对象被废弃的时候,要实现的代码
*/
}
就如使用C语言库的时候,需要使用如下方法释放内存
-(void)dealloc
{
free(buffer_);
}
其实dealloc方法,在大部分情况下还适用于删除已注册的代理或者观察者
-(void)dealloc
{
[[NSNotificationCenter defaultCenter]removeObserver:self];
}
在ARC无效的时候,还应该注意下面一点,
/*
*在ARC无效的时候,必须调用[super dealloc]
*/
-(void)dealloc
{
[super dealloc];
}
需要注意的是:ARC有效时 无法显式调用dealloc这一规则,如果使用就会同release方法一样 引起编译错误
第五项:"使用@autoreleasepool块替代NSAutoreleasePool" 在ARC有效的时候,调用NSAutoreleasePool会造成编译器出错
第六项:"不能使用区域NSZone" "NSZone"无论ARC是否有效,区域在现在的运行时系统(编译器宏OBJC2)中已经单纯的被忽略;
第七项:"对象型变量不能作为C语言结构体(struct/union)的成员" 这句话其实就是,ARC有效时,在结构体struct内部不能出现对象,如下编译器就会报错
struct data{
NSArray * arr;
};
报错如下:ARC forbids Objective-C objects in struct
26._unsafe_unretained修饰符以外的 __strong/ __weak/ __autoreleasing修饰符保证其指定的变量初始化为nil.
同样的,附有__strong/ __weak/ __autoreleasing修饰变量 的数组也能保证其初始化为nil
27.数组:
像下面这样将nil代入到malloc函数所分配的数组各元素中来初始化是非常危险的:
array = (id __strong *)malloc(sizeof(id) * entries);
for (NSUInteger i = 0; i < entries; ++i)
array[i] = nil;
原因是:比较难懂的一句话:
这是因为由malloc函数分配的内存区域没有被初始化为0,因此nil会被赋值给附有__strong修饰符的并被赋值了随机地址的变量中,从而释放一个不存在的对象。
28.数组:
在静态数组中,编译器能够根据变量的作用域自动插入释放赋值对象的代码,而在动态数组中,编译器不能确定数组的生存周期,所以无从处理。
如下所示:一定要将nil赋值给所有元素中,使得元素所赋值对象的强引用失效,从而释放那些对象。在此之后,使用free函数废弃内存块
for (NSUInteger i = 0; i < entries; ++i)
array[i] = nil;
free(array);
ARC的实现:
29.__strong修饰符
赋值给附有__strong修饰符的变量在实际的程序中到底如何运行?
{
id __strong obj = [[NSObject alloc] init];
}
该源代码实际上可转换为调用以下函数:
//编译器的模拟代码
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);
注意两次调用objc_msgSend方法(alloc 和 init)
30.weak修饰符:
若附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量
使用附有__weak修饰符的变量,即是使用注册到autoreleasepool中的对象(我们通过下面这段代码来确定这个功能)
id __weak obj1 = obj;
NSLog(@"%@"m obj1);
将被分解成以下代码 (模拟代码)
/* 编译器模拟代码 */
id obj1;
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destroyWeak(&obj1);
__weak同引用计数一样通过散列表(哈希表)实现,大致流程如下:
1.objc_initWeak(&obj1, obj)函数初始化__weak修饰的变量,通过执行objc_storeWeak(&obj1, obj)函数,以第一个参数(变量的地址)作为key,把第二个参数(赋值对象)作为value存入哈希表。
2.由于弱引用不能持有对象,函数objc_loadWeakRetained(&obj1)取出所引用的对象并retain。
3.objc_autorelease(tmp)函数将对象注册到autoreleasepool中。
4.objc_destroyWeak(&obj1)函数释放__weak修饰的变量,通过过程执行objc_store(&obj1, 0)函数,在weak表中查到变量地址并删除。废弃对象调用objc_clear_deallocating函数,这个过程会将weak表记录中__weak修饰的变量地址赋值为nil。
同时,前面的源代码与下列源代码相同
id obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0);
objc_storeWeak函数把第二个参数赋值对象的地址作为键值,将第一参数的附有__weak修饰符的变量的地址注册到weak表中(哈希表),如果第二参数为, 则把变量的地址从weak表中删除。由于一个对象可同时赋值给多个附有__weak修饰符的变量中,所以对于一个键,可注册多个变量的地址。
对象的释放过程:
objc_release
引用计数为0, 执行dealloc
_objc_rootDealloc
object_dispose
objc_destructInstance
objc_clear_deallocating
objc_clear_deallocating执行动作:
从weak表中获取废弃对象的地直为键的记录
2.将包含在记录中的所有附有__weak修饰符变是的地址,赋为nil
从weak表中删除该记录
从引用计数表中删除废弃对象的地址为键的记录
如果有大量使用附有__weak修饰符的变量,则会消耗相应的CPU资源,良策是只在需要避免循环引用的时候使用__weak,可先将__weak修饰的变量赋值给__strong修饰的变量
独自实现引用计数的类无法使用__weak,如NSMachPort,另外当allowsWeakReference/retainWeakReference方法返回No的时候也无法使用
31.很有意思的实现:
{
id __weak obj = [[NSObject alloc] init];
}
该源代码将自己生成并持有的对象赋值给附有__weak修饰符的变量中,所以自己不能持有该对象,这时会被释放并被废弃,引起编译警告。
那么编译器是怎么处理这行代码的呢?
//编译器的模拟代码
id obj;
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp,@selector(init));
objc_initWeak(&obj, tmp);
objc_release(tmp);//tmp自己不能持有对象 需要被释放并废弃
objc_destoryWeak(&object);
虽然自己生成并持有的对象通过objc_initWeak函数被赋值给附有__weak修饰符的变量中,但weak不能持有,编译器判断其没有持有者,故该对象立即通过objc_release函数被释放和废弃
这样一来,nil就会被赋值给引用废弃对象的附有__weak修饰符的变量中
32.一个总结:
因为附有 __weak修饰符变量所引用的对象被注册到autoreleasepool中,所以在@autoreleasepool块结束前都可以放心使用。但是,如果大量的使用附有__weak修饰符的变量,注册到autoreleasepool的对象数量也会大大的增加,因此在使用附有__weak修饰符的变量时,最好先暂时赋值给附有__strong修饰符的变量后再使用。
如下所示:
{
id __weak o = obj;
id tmp = 0;
NSLog(@"1 %@", tmp);
NSLog(@"2 %@", tmp);
NSLog(@"3 %@", tmp);
NSLog(@"4 %@", tmp);
NSLog(@"5 %@", tmp);
}
在 tmp = 0 时 对象仅登录到autoreleasepool一次
但如果不加 tmp这个变量而是直接打印 o 那么o所赋值的对象就会被注册到autoreleasepool五次
33.__autoreleasing的实现
_autoreleasing修饰变量,等同于ARC无效时对象调用autorelease方法。
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
/* 编译器的模拟代码 */
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelese(obj);
objc_autoreleasePoolPop();
34.不使用_autoreleasing修饰符,仅使用附有__weak声明的变量也能将引用对象注册到autoreleasepool中
网友评论