Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2676372
  • 博文数量: 877
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 5921
  • 用 户 组: 普通用户
  • 注册时间: 2013-12-05 12:25
个人简介

技术的乐趣在于分享,欢迎多多交流,多多沟通。

文章分类

全部博文(877)

文章存档

2021年(2)

2016年(20)

2015年(471)

2014年(358)

2013年(26)

分类: iOS平台

2015-09-18 07:20:08

[IOS学习]之六、ARC规则
/> 引用计数式的内存管理 在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时的比较:
?
1
2
3
4
5
6
7
8
{
     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修饰符的变量。
?
1
2
3
4
5
@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: 在引用计数的时候会产生 循环引用 的问题。 如图: \
来看循环引用代码:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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一样,因为自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即被释放。 代码:
?
1
2
3
4
5
6
7
8
9
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的。
如下会发生错误:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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中也可以做到:
?
1
2
3
4
5
6
7
8
@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
阅读(597) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~