【IOS学习】之六、ARC规则

来源:互联网 发布:淘宝等级排行 编辑:程序博客网 时间:2024/05/22 03:49
引用计数式的内存管理   在arc中是没有改变的。
在arc中,有有效和无效两种方式,我们可以在一个app中混合使用。

使用clang(LLVM编译器)或者以上版本,指定编译器属性为:"-fobjc-arc"就可以使用arc;

在oc中,引用计数式的内存管理的思考方式就是思考arc所引起的变化:
1、自己生成的对象,自己持有。
2、非自己生成的对象,自己也能持有。
3、自己持有的对象不再需要时释放。
4、非自己持有的对象无法释放。

所有权修饰符:
oc中,为了处理对象,可以将变量类型定义为id类型或各种对象类型。
对象类型就是指向NSObject这样的oc类的指针。 id类型用于隐藏对象类型的类名部分,相当于c中的void *;
arc中,需要加上修饰符:
__strong 
__weak
__unsafe_unretained
__autoreleasing

1、__strong
     他是id类型和对象类型默认的所有权修饰符。  也就是说  代码中的id变量实际上被附加了所有权修饰符。
id和对象类型在没有明确指定所有权修饰符的时候,默认是strong类型的:
id obj = [[NSObject alloc]init];
id __strong obj = [[NSObject alloc]init];
上面两行代码时等效的。   

非arc时的比较:
{     id __strong obj = [[NSObject alloc] init];}{     id obj = [[NSObejct alloc] init];     [obj release];}


为了释放生成并持有的对象,增加了调用release方法的代码。

__strong 修饰符表示对对象的“强引用”, 持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。
来分析一下此段代码:
取得自己持有的对象。
{
     //自己生成并持有对象
     id __strong obj = [[NSObject alloc] init];
     //因为变量obj为强引用
     //所以自己持有对象
}
     //因为变量obj超出其作用域,强引用失效
     //所以自动释放自己持有的对象,    对象的多有者不存在,因此废弃该对象。

取得非自己持有的对象:
{
     //取得非自己生成并持有的对象
     id __strong obj = [NSMutableArray array];
     //因为变量obj为强引用,  所以自己持有对象
}
     //因为变量obj超出其作用域,强引用失效。
     //所以自动地释放自己持有的对象。

在这,对象的所有者 和 对象的生存周期也是明确的。
__strong修饰符的变量之间可以相互赋值。
__strong修饰符的变量不仅在变量作用域中,在赋值上也能够正确地管理其对象的所有者。

、在oc中,我们可以使用赋有__strong修饰符的变量。
@interface Test: NSObject {     id __strong obj_;}- (void)setObject:(id __strong)obj;@end



通过__strong修饰符,无需再键入retain或者release,他们完美地满足了“引用计数式内存管理的思考方式”:
1、自己生成的对象,自己持有。
2、非自己生成的对象,自己也能持有。
3、不再需要自己持有的对象时释放。
4、非自己持有的对象无法释放。

其中1,2 通过带有__strong修饰符的变量赋值就可以完成。  废弃带__strong修饰符的变量或者对变量赋值,都可以做到:”不再需要自己持有的对象释放“。
第四项的 不是自己持有的对象释放,  由于我们不用再次键入 release  所以原本就不会执行。      他们都满足于引用计数式的内存管理的思考方式。

__strong属于默认的修饰符,所以我们不需要键入。  arc有效并简单的编程  遵循了oc的内存管理的思考方式。


2、__weak:
在引用计数的时候会产生  循环引用   的问题。
如图:

来看循环引用代码:
@interface Text : NSObject {     id __strong obj_;}- (void)setObject:(id __strong) obj;@end@implementation Text- (id) init {     self = [super init];     return self;}- (void) setObject:(id __strong)obj {     obj_ = obj;}@end{     id text0 = [[Text alloc] init];  //对象a     id text1 = [[Text alloc] init];  //对象b     [text0 setObject:text1];  //对象a的obj_持有对象b的强引用,  此时持有对象b的强引用变量为 a的obj_和text1.     [text1 setObject:text0];  //对象b的obj_持有对象a的强引用,  此时持有对象b的强引用变量为b的obj_和text0}//因为text0 变量超出作用域,强引用失效。自动释放对象a


//因为text1  变量超出作用域,强引用失效,自动释放对象b
//此时持有对象a的强引用变量为对象b的obj_      持有对象b的强引用的变量为 对象a的obj_    发生内存泄露。


还有就是当你只有一个对象,该对象持有其自身的时候也会发生泄露。



__weak与strong相反,提供弱引用,他不能持有对象实例。
我们为了不以自己持有的状态来保存自己生成并持有的对象,生成的对象会立即被释放。
{
     id __strong obj1 = [[NSObject alloc] init];
     id __weak obj2 = obj1;
}

__weak修饰符的变量不持有对象,所以在超出其变量作用域时,对象即被释放。  如果像下面这样将先前可能发生循环引用的类成员变量改成附有__weak修饰的成员变量的话,就会避免循环引用。
@interface Text : NSObject {
     id __weak obj_;
}
- (void)setObject:(id __strong) obj;
@end


__weak还有一个优点,在持有某对象的弱引用时,若该对象被抛弃,则此弱引用将自动失效,并处于nil被赋值的状态(空弱应用)。


在ios5 以上版本使用的是__weak,  而在ios4中使用的是__unsafe_unretained修饰符。   

3、__unsafe_unretained
     他是不安全的所有权修饰符。附有__unsafe_unretained修饰符的变量不属于编译器内存管理对象。
他跟__weak一样,因为自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即被释放。
代码:
id __unsafe_unretianed obj1 = nil;{     id __strong obj0 = [[NSObject alloc] init];     //obj0为强引用。   自己持有对象     obj1 = obj0;     //obj1变量即不持有对象的强引用也不持有弱引用。}//obj0 超出作用域,强引用失效。  自己释放自己持有的对象,   因为对象没有持有者,所以废弃对象。//obj1 变量表示的对象,已经被废弃了,  悬垂指针。


4、__autoreleasing修饰符
arc有效的时候,autorelease 和 NSAutoreleasePool 都是不能直接使用的。
我们应该写成:
@autoreleasepool{
     id __autoreleasing obj = [[NSObject alloc] init];
}
@autoreleasepool来替代NSAutoreleasePool类对象的生成,持有以及废弃。
在arc有效的时候,要通过对象赋值给附加了__autoreleasing修饰符的变量来替代调用autorelease方法。

@autoreleasepool{
     //取得非自己生成并持有的对象
     id __strong obj = [NSMutableArray array];
     //因为变量obj为强引用,所以自己持有对象,   并且该对象 由编译器判断其方法名后,自动注册到autoreleasepool
}
     //因为变量obj超出作用域,强引用失效,自动释放持有的对象,  同事随着@autoreleasepool的结束,注册到其中的所有对象被释放。因为对象的所有者不存在,所以废弃。

还有就是__weak修饰的变量,他在被访问的时候,必定会访问注册到autoreleasepool的对象,  因为__weak修饰符只持有对象的弱引用, 而在访问引用对象的过程中,该对象有可能被废弃,如果把要反问的对象注册到autoreleasepool中,那么在autoreleasepool块结束之前都能确保该对象存在;

来说一下__autoreleasing   如下:
NSError **error  是等同于:  NSError *__autoreleasing*error的。

如下会发生错误:
NSError *error = nil;NSError **pError = &error;要修改加上__strong修饰符。NSError *error = nil;NSError *__strong*pError = &error;NSError __weak*error = nil;NSError *__weak*pError = &error;下面的例子也是正确的:NSError __strong *error = nil;NSError **pError = &error;其实他是被改写了:NSError __strong*error = nil;NSError _autoreleasing *tem = error;NSError **pError = &tem;



在NSAutoreleasePool中,他可以嵌套使用,在@autoreleasepool中也可以做到:
@autoreleasepool{     @autoreleasepool{          @autoreleasepool{               id __autoreleasing obj = [[NSObject alloc] init];          }     }}


不论在arc还是非arc中,我们可以使用调试用的非公开函数:_objc_autoreleasePoolPrint()  函数。利用它可以有效的帮我们调试注册到autoreleasepool上的对象。

__strong和__weak修饰符的变量类似与c++中的职能指针std::shared_ptr和std::weak_ptr。  shared_ptr通过引用计数来持有c++类实例,weak_ptr可避免循环引用。  在不得不使用没有__strong和__weak修饰符的c++时,强烈推荐这两种指针。



ARC新规则:
1、不能使用retain/release/retainCount/autorelease
2、不能使用NSAllocateObject/NSDeallocateObject
3、必须遵守内存管理的方法命名规则
4、不要显式调用dealloc
5、使用@autoreleasepool块替代NSAutoreleasePool
6、不能使用区域NSZone
7、对象型变量不能作为c语言结构体的成员
8、显式转换id和void*

官方文档说:  “设置arc有效时,无需再次键入retain和release代码。”

不知道还记得说过的alloc的实现没,在gnustep中,alloc是通过调用NSAllocateObject来实现的。  其实他跟retain是一样的,这样在arc状态下就会引起错误。NSDeallocateObject也是同样的道理。

对象型变量不能作为c语言结构体的成员:
     这是什么意思呢?  因为c语言不能管理结构体成员的生存周期。而arc却把内存管理交给了编译器,  所以编译器要知道并管理对象的生存周期。 所以不能作为结构体成员。
     如果你很想把对象型变量加入到结构体成员中,你需要强制转换为void* 或者加上__unsafe_unretained修饰符。

显式转换id和void*
     单纯赋值的情况: 使用__bridge 转换
     id obj = ------
     void *p = (__bridge void*)obj;
     id o = (__bridge id)p;
这样赋值 有个缺点,就是安全性问题,他会比__unsafe_unretained安全性更低,如果不注意管理对象,很容易造成内存泄露。

__bridge有两种类型,分别是:__bridge_retianed  和  __bridge_transfer。  他们类似于 retian和release;
     void *p = 0;
     {    
          id obj = [[NSObject alloc] init];
          p = (__bridge_retained void *)obj;
     }
     //此时作用域结束,obj释放。  但是由于__bridge_retianed转换使变量p看上与处于持有对象的状态。所以对象不会被丢弃。

     id obj = (__bridge_transfer id)p;
     //ob持有对象, p被释放。




在写代码的时候,当arc有效的时候,oc类的属性也会发生变化。
如:@property (nonatomic, strong) NSString *name;
下面是属性列表:



上面的copy属性,它的赋值是通过NSCopying接口和copyWithZone方法复制赋值源所生成的对象。
这里的nonatomic来解释一下: 他是禁止多线程,可以进行变量保护, 能提高效率。
还有一个 atomic  这是oc的线程保护机制,防止发生信息未写完而被读取。

 这里不得不说一下内存分配的事情:
NSObject * __strong *array = nil;

如下分配:
array = (id __strong *)calloc(***, sizeof(id));
这里是分配的***个所需要的内存块,由于我们使用了__strong修饰符, 所以必须先初始化为nil;  calloc会给你的区域初始化为0;

但是我们使用malloc会怎样呢 ?
array = (id __strong*)malloc(sizeof(id) *  ***);
这里是没有初始化的,也就是说这里的内存是随机分配的。
如果你这样来初始化:
for (NSInteger i = 0; i < ***; ++i) {
     array[i] = nil;
}
这样很危险,因为我们是强引用对象,  当你nil赋值的时候,array[i]持有的对象会被释放掉,但是此时的对象时随机分配的地址,是无意义的,也就是说我们释放了一个不存在的对象。
此时我们需要使用memset来初始化。
memset(array, 0, *** * sizeof(id));


ARC的实现:
1、__strong
     最优化问题。
     id __strong obj = [NSMutableArray array];
     他在编译器中会出现模拟代码 如下:
     id obj = objc_msgSend(NSMutableArray, @selector(array));
     objc_retainAutoreleasedValue(obj);
     objc_release(obj);
     这里有个函数 objc_retainAutoreleasedValue。 还存在一个函数 objc_autoreleasepoolReturnValue。

     看一下array函数的转换:
     +(id)array {
          return [[NSMutableArray alloc]init];
     }
     转换模拟代码:
     id obj = objc_msgSend(NSMutableArray, @selector(alloc));
     objc_msgSend(obj, @selector(init));
     return objc_autoreleaseReturnValue(obj);

     objc_autoreleaseReturnValue函数返回注册对象到autoreleasepool中。
     objc_autoreleaseReturnValue会检查使用该函数的方法或者函数调用方的执行命令列表,如果方法或者函数的调用方在调用了方法或函数后紧接着调用objc_retainAutorelwasedReturnValue函数, 那么将不会将返回的对象注册到autoreleasepool中,而是直接传递到方法或者函数的调用方。
如图协作:



2、__weak
     1)、若附有__weak修饰符的变量所引用的对象被抛弃,则将nil赋值给该变量。
     2)、若用附有__weak修饰符的变量,即是试用注册到autoreleasepool中的对象。

     id __weak obj1 = obj;
     他做了什么呢?
来看:id obj1;
          objc_initWeak(&obj, obj);
          objc_destroyWeak(&obj1);
     这里就是初始化和结束的过程。
  其实就像这样:
          id obj1;
          obj1 = 0;
          objc_storeWeak(&obj1, obj);
          objc_storeWeak(&obj1, 0);
解释一下:  这里第一个storeWeak函数把第二个参数的赋值对象的地址作为键值,将第一个参数的附有__weak修饰符的变量的地址注册到weak表中。
          可以注意到,第二次调用的时候第二个参数为0, 也就是说第二个参数为-0的时候,会把变量的地址从weak表中删除。

     weak表是什么呢? 此时联想到引用计数表, 他们都是利用散列来实现的。  这里的一个键值可以注册多个变量的地址。 就跟一个对象可以同时付给多个附有__weak修饰符的变量。         也就是说,如果你用一个废弃对象的地址作为键值来检索,你能够告诉的获取对应的附有__weak修饰符的变量的地址。

     当释放对象的时候, 废弃掉谁都不持有的对象,程序后续还会出现动作:
     1、objc_release
     2、因为引用计数为0, 所以执行dealloc
     3、_objc_rootDealloc
     4、object_dispose
     5、objc_destructInstance
     6、objc_clear_deallocating
最后的第六个步骤的函数会出现如下动作:
     1、从weak表中获取废弃对象的地址为键值的记录。
     2、将包含在记录中的所有附有__weak修饰符变量的地址,赋值为nil。
     3、从weak表中删除该记录。
     4、从引用计数表中删除废弃对象的地址为键值的记录。

当我们使用注册到autoreleasepool中的对象的时候:
id __weak obj1 = obj;
此时的模拟代码为:
id obj1;
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
objc_destroyWeak(&obj1);

这里增加了objc_loadWeakRetained和objc_autorelease的调用。
1、objc_loadWeakRetained:函数取出附有__weak修饰符变量所引用的对象,并retain。
2、objc_autorelease函数将对象注册到autoreleasepool中。

这样就可以安全的使用附有__weak修饰符的变量了。   
但是如果对象很多呢 ?
最好的方法就是先暂时赋值给 __strong修饰符的变量。 这样对象就仅登录到autoreleasepool中一次。

最后提醒的是:id __weak obj = [[NSObject alloc] init];  和  id __unsafe_unretained obj = [[NSObject alloc] init]; 
               这样是不可以的, 前者是因为不能持有对象,后者是obj被赋予的是 悬垂指针。   虽然在arc中不会造成内存泄露,但是还是不要这样使用的好。


3、引用计数
     获取引用计数值的函数:  uintptr_t  _objc_rootRetainCount(id obj)  这个函数可以获取指定对象的引用计数值(ARC中,retainCount已经不能用了);
     函数 _objc_autoreleasePoolPrint函数会观察注册到autoreleasepool中的引用对象。

                                                                                -----2014/3/18  Beijing
0 0
原创粉丝点击