Objective-C内存管理
2016-02-18 21:32
666 查看
一、引用内存计数器思考方式
1.自己生成的对象,自己所持有
2.非自己生成的对象,自己也能持有
3.不再需要自己持有的对象时释放
4.非自己持有的对象无法释放
二、alloc/retain/release/dealloc在GNUsetp中的实现
typedef struct obj_layout{
NSUInteger retained;
}OBJ_Layout;
inline id NSAllocatedObject(Class aClass,NSUInteger extraBytes,NSZone *zone){
int size = 0;
id new = (NSZoneMalloc(zone, size));
memset((new),0, size);
//返回作为对象而使用的指针
new = (id)&(( OBJ_Layout *)new)[1];
return
new;
}
inline void NSDeallocateObject(id anObject){
OBJ_Layout *o = &((OBJ_Layout *)anObject)[-1];
free(o);
}
inline NSUInteger NSExtraRefCount(id anObject){
return ((OBJ_Layout *)anObject)[-1].retained;
}
inline void NSIncrementExtraRefCount(id anObject){
if (((OBJ_Layout *)anObject)[-1].retained ==UINT_MAX -1) {
[NSExceptionraise:NSInternalInconsistencyExceptionformat:@"NSIncrementExtraRefCount() asked to increment too far"];
}
(( OBJ_Layout *)anObject)[-1].retained++;
}
BOOL NSDecrementExtraRefCountWasZero(id anObject){
if (((OBJ_Layout *)anObject)[-1].retained ==0) {
return YES;
}else{
((OBJ_Layout *)anObject)[-1].retained --;
return NO;
}
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
// do your override
+ (id)alloc{
int size = sizeof( OBJ_Layout);
OBJ_Layout *p = (OBJ_Layout *)calloc(1,size);
return (id)(p+1);
}
- (NSUInteger)retainCount{
return NSExtraRefCount(self) +1;
}
-(id)retain{
NSIncrementExtraRefCount(self);
return
self;
}
- (void)release{
if (NSDecrementExtraRefCountWasZero(self)) {
[self dealloc];
}
}
- (void)dealloc{
NSDeallocateObject(self);
}
- (void)autorelease{
[NSAutoreleasePool addObject:self];
id autorelease_class = [NSAutoreleasePoolclass];
SEL autorelease_sel =
@selector(addObject:);
IMP autorelease_imp = [autorelease_class methodForSelector:autorelease_sel];
// (*autorelease_imp)(autorelease_class,autorelease_sel,self);
}
#pragma clang diagnostic pop
1.在objective-c的对象中存有引用计数这一整数值
2.调用alloc或是retain方法后,引用计数值加1
3.调用release后,引用计数值减1
4.引用计数值为0时,调用dealloc方法废弃对象
三、
1.苹果的实现大概是采用散列表(引用计数表)来管理引用计数(表键值为内存块地址的散列值)
2.引用计数表各记录中存有内存块地址,可从各个记录追溯到各对象的内存标志,即使出现故障导致对象占用的内存
块损坏,但只要引用计数表没有被破坏,就能够确认各内存块的位置
3.利用工具检测内存泄漏时,引用计数表和各记录也有助于检测和对象的持有者是否存在
4.autorelese类似于C语言中自动变量的特性,在cocoa框架中,相当于主循环的NSRunLoop或者其他程序可以
运行的地方,对NSAutoreleasePool对像进行生成、持有和废弃处理,NSRunLoop每次循环过程中NSAutorelease
对像被生成或废弃
四、ARC
”引用计数式内存管理“本质部分在ARC中并没有改变,只是自动处理“引用计数“的相关部分
1.使用ARC有效的编译方法:
使用clang(LLVM编译器)3.0或以上版本
指定编译器属性为"-fobjc-arc"
2.类型上必须加所有权修饰符四种:
__strong,
__weak,
__unsafe_unretained,
__autoreleasing
下面分别说明:
__strong修饰符
1>__strong强引用,是id类型和对象类型默认的所有权修饰符,持有的变量在超出其作用域时被废弃,强引用失效,引用对象会释放
eg:{id __strong obj = [[NSObject alloc]init];}
相当于ARC无效
{
id obj = [[NSObject alloc]init];
[obj release];
}
2>__strong修饰符变量,不仅只在变量作用域中,在赋值上也能够正确管理对象的所有者:
{//obj0持有对象A的强引用
id __strong obj0 = [[NSObject alloc]init];//对象A
//obj1持有对象B的强引用
id __strong obj1 = [[NSObject alloc]init];//对象B
//obj2不持有任何对象
id __strong obj2 =nil;
//obj0持有obj1赋值的对象B强引用
因为obj0被赋值,所以原先持有对像A的强引用失效,对象A的所有者不存在,因此废弃对象A。
此时,持有对象B的强引用的变量为obj0,obj1;
obj0 = obj1;
//obj2持有由obj0赋值的对象B的强引用,此时,持有对象B的强引用的变量为obj0,obj1,obj2
obj2 = obj0;
NSLog(@"%ld",CFGetRetainCount((__bridge void *)obj2));//3
//因为nil被赋予了obj1,所以对对象B的强引用失效,此时,持有对象B的强引用的变量为obj0,obj2
obj1 = nil;
//因为nil被赋予了obj0,所以对对象B的强引用失效,此时,持有对象B的强引用的变量为obj2
obj0 = nil;
//因为nil被赋予了obj2,所以对对象B的强引用失效,对象B的所有者不存在,因此废弃对像B
obj2 = nil;
}
3> __strong,__weak,__autoreleasing修饰符和自动变量初始化为nil,
id __strong obj0;等同于id__strong obj0 =nil;
4>通过__strong修饰符,不必再次键入retain或者release,就会完美满足“引用计数式内存管理思考方式"自己生成的对象,自己所持有,非自己生成的对象,自己也能持有:通过对带__strong修饰符的变量赋值即可不再需要自己持有的对象时释放:通过废弃带__strong修饰符的变量(变量作用域结束或是成员变量所属对象废弃)或对变量赋值。非自己持有的对象无法释放:由于不必再次键入relase,所以原本不会执行
__weak修饰符
1.生成对象不能继续为自己所有,所以生成对象会立即被释
2.使用__weak修饰符避免循环引用
3.在持有某对象时,若该对象被废弃,则此弱引用将自动失效且处于nil被赋值的状态。
4.通过检查附有__weak修饰符的变量时是否为nil,可以叛断被赋值的对象是否已废弃。
@interface Test:NSObject{
id __strong obj_;
}
- (void)setObject:(id__strong)obj;
@end;
@implementtaion Test
-(instancetype)init{
self = [super init];
return
self;
}
- (void)setObject:(id__strong)obj{
obj_ = obj;
}
@end
{
//test0
持有Test对象A的强引用
id test0 = [[Test alloc]init];//对象A
//test1
持有Test对象B的强引用
id test1 = [[Test alloc]init];//对象B
//Test对象A的obj_成员变量持有Test对象B的强引用,此时,持有Test对象B强引用的变量为Test对象A和obj_和
test1
[test0 setObject:test1];
//Test对象B的obj_成员变量持有Test对象A的强引用,此时,持有Test对象A强引用的变量为Test对象B和obj_和
test0
[test1 setObject:test0];
}
//因为test0变量超出其作用域,强引用失效,所有自动释放Test的对象A
因为test1变量超出其作用域,强引用失效,所有自动释放Test的对象B,此时,持有Test对象A的强引用变量为Test对象B的obj_;持有Test对象B的强引用变量为Test对象A的obj_,发行内存泄漏
对象持有自身时出会发生循环引用:
{
id test = [[Test alloc]init];//对象A
[test setObject:test]
}
{
id test = [[Test alloc]init];//对象A
[test setObject:[[NSObject alloc]init]]];
}
因为test变量超出其作用域,强引用失效,所以自动释放Test对Test对象不存在,因此废弃该对象,废弃Test对象的同时,Test对象的obj_成员也被废弃,NSObject对象的强引用失效,自动释放NSObject对象NSObject对象的所有者不存在,因此废弃该对象
__unsafe_unretained修饰符(现在估计很少用到)
1.__weak只能用于iOS5以上及OS X Lion以上版本,在iOS4及OS
X Snow Leopard应用程序中可用
__unsafe_unretained来代替__weak
2.赋值给附__unsafe_unretained修饰符变量的对象在通过该变量使用时,如果没有确保其确实存在,那么应用程序就会崩溃
3.尽管ARC式的内存管理是编译器的工作,但__unsafe_unretained修饰符的变量不属于编译器的内存管理对象
__autoreleasing修饰符
1.ARC有效时,要通过将象赋值给附加了__autoreleasing修饰的变量来替代调用autorelease方法。
对象赋值给附有__autoreleasing修饰符的变量
等价在ARC无效时调用对象的autorelease方法,即对象被注册到autoreleasepool eg:
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init] ;--> @autoreleasepool{
[obj autorelease]; -->id__autoreleasing obj2;obj2 = obj;
[pool drain]; -->}
}
2.显示附加__autoreleasing修饰符同显示地附加__strong修饰符一样罕见取得非自己生成并持有的对象时,虽然可以使用alloc/new/copy/mutableCopy以外的方法来取得对象,但该对象已被注册到了autoreleaspool.这同在ARC无效时取得调用了autorelease方法的对象是一样的,这是由于编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasepool.
@autoreleasepool{
//取得非自己生成并持有的对象
id __strong obj = [NSMutableArray array];
//因为变量obj为强引用,所以自己持有对象,并且该对象由编译器判断其方法后,自动注册到autoreleasepool
}
变量obj超出其作用域,强引用失效,自动释放自己持有的对象,同时随着@autoreleasepool块的结束,注册到autoreleasepool中的
所有对象被释放。因为对象的所有者不存在,所以废弃对象
3.不使用__autoreleasing修饰符也能使对象注册到autoreleasepool
+(id)array{
id obj = [[NSMutableArray alloc]init];
return obj;
}
由于return使得对象超出其作用域,所以该强引用对应的自己持有的对象会被自动释放,但对象作为函数的返回值,
编译器会自动将其注册到autoreleasepool
4.__weak修饰符是虽是为了避免循环引用而使用的,但在访问附有__weak修饰的变量时,实际上必定要访问注册到autoreleasepool的对象。__weak修饰对象只持有对象的弱引用,而在访问引用对象的过程中,该对象有可能被废弃,如果要把访问的对象注册到autoreleasepool中
那么@autoreleasepool块结束之前都能确保该对象存在。因此__weak修饰的变量时就必定要使用注册到autorelepool中的对象
id __weak obj1 = obj0;
NSLog([obj1 class]);
等价于
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog([tmp class]);
5.id指针或对象的指针在没有显式指定时会被附加上__autoreleasing修饰符
id __strong obj --> id__autoreleasing *obj
NSObject **obj --> NSObject * __autoreleasing *obj;
- (BOOL)performOperationWithError:(NSError **)error; -->(等价于)
- (BOOL)performOperationWithError:(NSError *__autoreleasing *)error;
6.ARC规则
1>不能使用retain/release/retainCount/autorelease
2>不能使用NSAllocateObject/NSDeallocateObject
3>须遵守内存管理的方法命名规则
4>不要显式调用dealloc
5>使用@autoreleasepool块替代NSAutoreleasePool
6>不能使用区域(NSZone)
7>对象型变量不能作为C语方结构体(struct/union)的成员
(struct Data{NSMutableArray *array};)
8>显式转换id和void*
7.指针转换
//ARC无效
id obj = NSObject.new;
void *p = obj;
id o = p;
[o release];
//ARC
有效 ,用 __bridge转换,id和void *就能够相互转换
id obj = NSObject.new;
void *p = (__bridgevoid*)obj;
id o =(__bridgeid) p;
但是转换为void *的__bridge转换,其安全性与赋值给__unsafe_unretained修饰符相近,甚至会更低,如果管理
时不注意赋值对象的所有者,就会悬垂指针而导致程序崩溃
__bridge转换分两种:
__bridge_retained使要转换赋值的变量也持有的赋值的对象,变量作用域结束时,__bridge_retained持有该对象不会被废弃
__brideg_transfer被转换的变量所持有的对象在该变时被赋值给转换目标变量后随之释放
void *p = 0;
{
id obj = [[NSObject alloc]init];
p = (__bridge_retained void*)obj;
}
NSLog([(__bridge id)p class]);
变量作用域结束时,虽然随着持有强引用的变量obj失效,对象随之释放,但由于__bridge_retained转换使变量p
看上去处于持有该对象的状态,因此该对象不会被废弃
ARC无效
{
id obj = (__bridge_transfer id)p;
}
相当于:
{
id obj = (id)p;
[obj retain];
[id)p release];
}
同__bridge_retained转换与retain类似,__bridge_transfer转换与release相似.
在给id ojb赋值时retain即相当于__strong修饰符的变量
//将生成并持有的NSMutableArray对象作为CoreFoundation对象来处理
CFMutableArrayRef cfObject = NULL;
{
/*
obj持有生成并持有对象的强引用
*/
id obj = [[NSMutableArray alloc]init];
/*
通过CFBridgingRetain,将对像CFRetain,赋值给变量cfObject
*/
cfObject = (void *)CFBridgingRetain(obj);
CFShow(cfObject);
/*
通过变量obj的强引用和通过CFBridgingRetain,引用计数为2
*/
printf("retain count = %ld\n",CFGetRetainCount(cfObject));
}
//变量obj超出其作用域,所以其强引用失效,引用计数为1
printf("retain count after the scope = %ld\n",CFGetRetainCount(cfObject));
//将对象CFRelease,其引用计数为0,该对象被废弃
CFRelease(cfObject);
//用__bridge转换来替代CFBridgingRetain或__bridge_retained转换时
CFMutableArrayRef cfObj = NULL;
{
// obj持有生成并持有对象的强引用
id obj = NSMutableArray .new;
//__bridge转换不改变对象的持有状况,只有通过变量obj的强引用,引用计数为1
cfObj = (__bridge CFMutableArrayRef)obj;
CFShow(cfObj);
printf("retain count = %ld\n",CFGetRetainCount(cfObj));
}
//变量obj超出其作用域,其强用失效,对象得到释放,无持有者的对象被废弃
printf("retain count after the scope = %ld\n",CFGetRetainCount(cfObj));
//此后对对象的访问出错,悬垂指针
CFRelease(cfObj);
//由此可知,CFBridgingRetain或者__brideg_retained转换是不可或缺的
//使用Core Foundation的API生成并持有对象,将该对象作为NSMutalbeArray对象来处理
{
//Core Foundation
框架的API生成并持有对象,之后的对象引用计数为1
CFMutableArrayRef cfObj = CFArrayCreateMutable(kCFAllocatorDefault,
0, NULL);
printf("retain count = %ld\n",CFGetRetainCount(cfObj));
//通过CFBridgingRelease赋值,变量obj持有对象强引用同时,对象通过CFRelease释放
//id obj = CFBridgingRelease(cfObj);
id obj = (__bridge_transfer id)(cfObj);
//因为只有变量obj,持有对生成并持有对象的强引用,故引用计数为1.另外,因为经由CFBridgingRelease
//转换后,赋值给变量cfObj中的指针也指向仍然存在的对象,所有可以正常使用
printf("retain count after the cast = %ld\n",CFGetRetainCount(cfObj));
NSLog(@"class = %@",obj);
}
//ojb超出其作用域,所以其强引用失效,对象得到释放,无所者的对象随之被废弃
// 用__bridge转换替代CFBridgingRelease或__bridge_transfer转换的情形
{
//Core Foundation
框架的API生成并持有对象,之后的对象引用计数为1
CFMutableArrayRef cfObj = CFArrayCreateMutable(kCFAllocatorDefault,
0, NULL);
printf("retain count = %ld\n",CFGetRetainCount(cfObj));
//通过赋值给__strong修饰的变量中,所以发生强引用
id obj = (__bridge id)cfObj;
//因为只有变量obj,持有对生成并持有对象的强引用,对象没有进行CFRelease,所以引用计数为2
printf("retain count after the cast = %ld\n",CFGetRetainCount(cfObj));
NSLog(@"class = %@",obj);
}//ojb超出其作用域,所以其强引用失效,对象得到释放
//因为引用计数为1,所以对象仍然存在,发生内存泄漏
/*因此,必须恰当使用CFBridgingRetain/CFBridgingRelease或者__bridge_retained/__bridge_transer转换在将OBjective-C变量赋值给C语言变量,即没有附加所有权修饰符的void*等指针型变量时,
伴随着一定的风险,在实现代码时要高度重视
*/
四、 ARC属性
属性声明的属性与所有权修饰符的对应关系
属性声明的属性 所有权修饰符
assign __unsafe_unretained修饰符
copy __strong修饰符(赋值的是被复制的对象)
retain __strong修饰符
strong __strong修饰符
unsafe_unretained __unsafe_unretained修饰符
weak __weak修饰符
五、数组中附有__strong修饰符变量,数组超出其变量作用域时,数组中各个附有__strong修饰符的变量也随之失效,
其强引用消失,所赋值的对象也随之释放。
//静态数组
{
id objs[2];
objs[0] = [[NSObject alloc]init];
objs[1] = [NSMutable array];
}
动态数组
Foundation框架的容器也可为我们管理对象,但在C语言的动态数组中也可以使用附有__strong修饰符的变量。id指针或对象的指针在没有显式指定时会被附加上__autoreleasing
*类型,所以有必要显式指定为__strong修饰符虽然保证了附有__strong修饰符的id型变量被初始化为nil,但并不保证附有__strong修饰符的id指针型变量被初始化为nil
NSObject *__strong *array =
nil;
// id *__strong *array = nil;
array = (id __strong *)calloc(1024,sizeof(NSObject
*));
// 或
array = (id __strong *)malloc(sizeof(NSObject *)*1024);
for (NSUInteger i =
0; i < 1024; ++i) {
array[i] = nil;
}
可以完全像静态数组一样使用,但动态数组中操作附有__strong修饰符的变量与静态数组有很大差异,需要自己释放所有的元素因为在静态数组中,编译器能够根据变量的作用域自动插入释放赋值对象的代码,而在动态数组中,编译器不能确定数组的生存周期,所以无从处理
array[0] = [NSObject
new];
//释放
for (NSUInteger i =
0; i < 1024; ++i) {
array[i] = nil;
}
free(array);
使用 memcpy函数拷贝数组元素以及realloc函数重新分配内存块也会有危险,由于数组元素所赋值的对象可以被保留在内存中或是重复被废弃,所以这两个函数也禁止。
六、ARC的实现
苹果官方说明称ARC是“由编译器进行内存管理"的,但实际上只有编译器是无法完全胜任的,在此基础上还需要Objective-C运行库的协助,也就是说,ARC由以上工具、库来实现:
clang(LLVM编译器)3.0以上
objc4 Objective-C运行时库493.9以上
// __strong修饰符
{
id __strong obj = NSObject.new;
}
新建一个clang.m文件,输入面内容。在终端命令中用clang -S clang.m可取得程序汇编输出。
根据objc4库源码和汇编输出就能知道程序是如何工作的。
/*
编译器模拟代码
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj , @selector(init));
objc_release(obj);
*/
看看除alloc/new/copy/mutalbleCopy以外的方法是什么情况
{
id __strong obj = [NSMutableArray array];
}
/*
编译器模拟代码
id obj = objc_msgSend(NSMutableArray,@selector(alloc));
objc_retainAutoreleaseReturnValue(obj);
objc_release(obj);
*/
+(id)array{
return [[NSMutableArray alloc]init];
}
//编译器模拟代码
+(id)array
{
id obj = objc_msgSend(NSMutableArray,@selector(alloc));
objc_msgSend(obj,@selector(init));
return objc_autoreleaseReturnValue(obj);
}
像源代码这样,返回注册到autoreleasepool中对象的方法使用了objc_autoreleaseReturnValue函数返回注册到autoreleasepool中的对象。但是objc_autoreleaseReturnValue函数objc_autorelease函数不同,一般不仅限于注册对象到autoreleasepool中objc_autoreleaseReurnValue函数会检查使用该函数的方法或函数调用方的执行命令列表,如果方法或函数的调用方在调用了方法或函数后紧接着调用objc_retainAutoreleasedReturnValue()函数,那么就不将返回的对象注册到autorreleasepool中,而是直接传到方法或函数的调用方。objc_retainAutoreleaseReturnValue函数与objc_retain函数不同,它即便不注册到autoreleasepool中而返回对象,也能够正确地获取对象。通过objc_autoreleaseReturnValue和objc_retainAutoreleasedReuturnValue函数的协作,可以不将对象注册到autoreleasepool中而直接传递,这一过程达到了最优化。
__weak修饰符
1.若附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量
2.使用附有__weak修饰符的变量,即是使用注册到autoreleasepool中的对象
{
id__weak obj1 = obj;// obj附加__strong修饰且对象被赋值
}
编译器模拟代码
id obj1;
objc_initWeak(&obj1,obj);//__初始化附有__weak修饰符的变量,在变量作用域结束时通过objc_destoryWeak释放
objc_destoryWeak(&obj1);
//objc_initWeak函数将附有__weak修饰符的变量初始化为0后,会将赋值的对象作为参数调用objc_storeWeak函数
obj1 = 0;
objc_storeWeak(&ojb1,obj)
//objc_destoryWeak函数将0作为参数调用objc_storeWeak函数
objc_destoryWeak(&ojb1,0)
前面源代码与下面源代码相同
id obj1;
obj1 = 0;
objc_storeWeak(&obj1,ojb);
objc_storeWeak(&obj1,0);
objc_storeWeak函数把第二参数的赋值对象的地址作为键值,将第一参数的附有__weak修饰符的变量的地址注册到weak表中,如果第二参数为0,则表变量的地址从weak表中删除weak表与引用计数表相同,如果使用weak表,将废弃的对象的地址作为键值进行检索,就能高速的获取对应的附有__weak修饰符的变量的地址。另外,由于一个对象可同时赋值给多个附有__weak修饰的变量中,所以对于一个键值,可注册多个变量的地址。释放对象时,废弃不持有的对象,对象将通过objc_release函数释放
1>objc_release
2>计数为0,执行dealloc
3>_objc_rootDealloc
4>objc)dispose
5>objc_destructInstance
6>objc_clear_deallocating
对象被废弃时最后调用objc_clear_deallocating函数如下:
1>从weak表中获取废弃对象的地址为键值的记录
2>将包含在记录中的所有附有__weak修饰符变量的地址,赋值为nil
3>从weak表中删除该记录
4>从引用计数表中删除废弃对象的地址为键值的记录
根据以上步骤,前面说的如果附有_weak修饰符的变量所有引用的对象被废弃,则将nil赋值给该变量这一功能取被实现由此可知,如果大量使用附有__weak修饰的变量,则会消耗相应的CPU资源,良策是只在需要避免循环引用时使用__weak修饰符自己生成并持有的对象赋值给附有__weak修饰的变量中,自己不能持有该对象,这时会被释放并被废弃
{
id __weak obj = [[NSObject alloc]init];
//id __unsafe_unretained unsafeObj = [[NSObject alloc]init];
NSLog(@"obj = %@ ",obj);//obj = (null)
}
/*编译器模拟代码
id obj;
id tmp = objc_msgSend(NSOBject,@selector(alloc));
objc_msgSend(tmp,@selector(init));
objc_initWeak(&obj,tmp);
objc_release(tmp);
objc_destoryWeak(&object);
虽然自己生成并持有的对象通过objc_initWeak()被赋值给附有__weak修饰的变量中,但编译器判断其没有持有者,帮该对象立即通过objc_release函数被释放和废弃,这样,nil就会赋值引用废弃对象了附有__weak修饰符的变量中
*/
__weak修饰的变量,即是使用注册到autoreleasepool中的对象
{
id __weak obj1 = obj;
NSLog(@"%@",obj1);
}
//编译器模拟代码
id obj1;
objc_initWeak(&obj1,obj);
id tmp = objc_loadWeakRetained(&obj1);//取出附有__weak修饰变量所引用的对象并retain
objc_autorelase(tmp);//将对象注册到autoreleasepool中
NSLog(@"%@",tmp);
objc_destoryWeak(&object);
由此可知,因为附有__weak修饰符变量所引用的对象像这样被注册到autoreleasepool中,所以在@autoreleasepool块结束之前都可以放心使用。但是,如果大量地使用附有__weak修饰符的变量,注册到autoreleasepool的对象也会大量地增加,因此在使用附有__weak修饰符的变量时,最好先暂时赋值给附有__strong修饰符的变时后再使用,如下代码使用了5次附有__weak修饰符的变量o
{
id __weak o = obj;
NSLog(@"1 = %@",o);
NSLog(@"2 = %@",o);
NSLog(@"3 = %@",o);
NSLog(@"4 = %@",o);
NSLog(@"5 = %@",o);
}
将附有__weak修饰符的变量o赋赋值给附有__strongk修饰符变量后再使用可以避免此类问题,在tmp=0时对象仅加到autoreleasepool中1次
{
id __weak o = obj;
id tmp = o
NSLog(@"1 = %@",tmp);
NSLog(@"2 = %@",tmp);
NSLog(@"3 = %@",tmp);
NSLog(@"4 = %@",tmp);
NSLog(@"5 = %@",tmp);
}
//allowsWeakReference/retainWeakReference
不能使用__weak修饰符
allowsWeakReference/retainWeakReference实例方法返回NO的情况,在赋值给_weak修饰符的变量时,如果赋值对象allowsWeakReference方法返回NO,程序将异常终止。即对于所有allowsWeakReference返回NO的类绝不能使用__weak悠饰符,另外,在使用__weak修饰符的变量时,当被赋值对象的retainWeakReference方法返回NO的情况下,该变量将使用nil.
@interface MyObject : NSObject{
NSUInteger count;
}
@end
@implementation MyObject
-(instancetype)init{
self = [super init];
return
self;
}
- (BOOL)retainWeakReference{
if (++count >
3) {
return NO;
}
return [super retainWeakReference];
}
@end
{
id __strong obj = [MyObject alloc].init;
id __weak o = obj;
NSLog(@"1 = %@",o);
NSLog(@"2 = %@",o);
NSLog(@"3 = %@",o);
NSLog(@"4 = %@",o);
NSLog(@"5 = %@",o);
}
/*
1 = <MyObject: 0x100203810>
2 = <MyObject: 0x100203810>
3 = <MyObject: 0x100203810>
4 = (null)
5 = (null)
*/
//__autoreleasing修饰符
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc]init];
}
/*编译器模拟代码
id pool = objc_autoreleasePoolPush();
id tmp = objc_msgSend(NSOBject,@selector(alloc));
objc_msgSend(tmp,@selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
*/
//__autoreleasing修饰符
@autoreleasepool {
id __autoreleasing obj = [NSMutableArray array];
}
/*编译器模拟代码
id pool = objc_autoreleasePoolPush();
id tmp = objc_msgSend(NSOBject,@selector(alloc));
objc_retainAutoreleaseReturnValue(obj);
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
*/
//引用计数器
{
id __strong obj = [[NSObject alloc]init];
id __weak o = obj;
NSLog(@"retain count = %d",_objc_rootRetainCount(obj));//1
NSLog(@"retain count = %d",_objc_rootRetainCount(obj));//1
}
//由于弱引用并不持有对象,所以赋值给附有__weak修饰的变量中也必定不会改变引用计数数值
@autoreleasepool {
id __strong obj = [[NSObject alloc]init];
id __autoreleasing o = obj;
NSLog(@"retain count = %d",_objc_rootRetainCount(obj));//2
}
//对像被附有__strong修饰符变量的强引用所持有,且被注册到autoreleasepool中,所以为2
{
id __strong obj = NSObject.new;
@autoreleasepool {
id __autoreleasing o = obj;
NSLog(@"retain count = %d",_objc_rootRetainCount(obj));//2
}
NSLog(@"retain count = %d",_objc_rootRetainCount(obj));//1
}
@autoreleasepool {
id __strong obj = NSObject.new;
_objc_autoreleasePoolPrint();
id __weak o =obj;
NSLog(@"before using __weak:retain count = %d",_objc_rootRetainCount(obj));//1
NSLog(@"class = %@",[o
class]);
NSLog(@"after using __weak:retain count = %d",_objc_rootRetainCount(obj));//2
_objc_autoreleasePoolPrint();
}
不使用__autoreleasing修饰符,仅使用附有__weak声明的变量也能将引用对象注册到了autoreleasepool中
以上例子均使用了_objc_rootRetainCount(),但实际上并不能完全信任其取得的数值。对于已释放的对象以及不正确的对象地址,
有时也返回1,另外,在多线程使用对象的引用计数数值,回为存有竞态条件的问题,所以取得的数值不一定完全可信
1.自己生成的对象,自己所持有
2.非自己生成的对象,自己也能持有
3.不再需要自己持有的对象时释放
4.非自己持有的对象无法释放
二、alloc/retain/release/dealloc在GNUsetp中的实现
typedef struct obj_layout{
NSUInteger retained;
}OBJ_Layout;
inline id NSAllocatedObject(Class aClass,NSUInteger extraBytes,NSZone *zone){
int size = 0;
id new = (NSZoneMalloc(zone, size));
memset((new),0, size);
//返回作为对象而使用的指针
new = (id)&(( OBJ_Layout *)new)[1];
return
new;
}
inline void NSDeallocateObject(id anObject){
OBJ_Layout *o = &((OBJ_Layout *)anObject)[-1];
free(o);
}
inline NSUInteger NSExtraRefCount(id anObject){
return ((OBJ_Layout *)anObject)[-1].retained;
}
inline void NSIncrementExtraRefCount(id anObject){
if (((OBJ_Layout *)anObject)[-1].retained ==UINT_MAX -1) {
[NSExceptionraise:NSInternalInconsistencyExceptionformat:@"NSIncrementExtraRefCount() asked to increment too far"];
}
(( OBJ_Layout *)anObject)[-1].retained++;
}
BOOL NSDecrementExtraRefCountWasZero(id anObject){
if (((OBJ_Layout *)anObject)[-1].retained ==0) {
return YES;
}else{
((OBJ_Layout *)anObject)[-1].retained --;
return NO;
}
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
// do your override
+ (id)alloc{
int size = sizeof( OBJ_Layout);
OBJ_Layout *p = (OBJ_Layout *)calloc(1,size);
return (id)(p+1);
}
- (NSUInteger)retainCount{
return NSExtraRefCount(self) +1;
}
-(id)retain{
NSIncrementExtraRefCount(self);
return
self;
}
- (void)release{
if (NSDecrementExtraRefCountWasZero(self)) {
[self dealloc];
}
}
- (void)dealloc{
NSDeallocateObject(self);
}
- (void)autorelease{
[NSAutoreleasePool addObject:self];
id autorelease_class = [NSAutoreleasePoolclass];
SEL autorelease_sel =
@selector(addObject:);
IMP autorelease_imp = [autorelease_class methodForSelector:autorelease_sel];
// (*autorelease_imp)(autorelease_class,autorelease_sel,self);
}
#pragma clang diagnostic pop
1.在objective-c的对象中存有引用计数这一整数值
2.调用alloc或是retain方法后,引用计数值加1
3.调用release后,引用计数值减1
4.引用计数值为0时,调用dealloc方法废弃对象
三、
1.苹果的实现大概是采用散列表(引用计数表)来管理引用计数(表键值为内存块地址的散列值)
2.引用计数表各记录中存有内存块地址,可从各个记录追溯到各对象的内存标志,即使出现故障导致对象占用的内存
块损坏,但只要引用计数表没有被破坏,就能够确认各内存块的位置
3.利用工具检测内存泄漏时,引用计数表和各记录也有助于检测和对象的持有者是否存在
4.autorelese类似于C语言中自动变量的特性,在cocoa框架中,相当于主循环的NSRunLoop或者其他程序可以
运行的地方,对NSAutoreleasePool对像进行生成、持有和废弃处理,NSRunLoop每次循环过程中NSAutorelease
对像被生成或废弃
四、ARC
”引用计数式内存管理“本质部分在ARC中并没有改变,只是自动处理“引用计数“的相关部分
1.使用ARC有效的编译方法:
使用clang(LLVM编译器)3.0或以上版本
指定编译器属性为"-fobjc-arc"
2.类型上必须加所有权修饰符四种:
__strong,
__weak,
__unsafe_unretained,
__autoreleasing
下面分别说明:
__strong修饰符
1>__strong强引用,是id类型和对象类型默认的所有权修饰符,持有的变量在超出其作用域时被废弃,强引用失效,引用对象会释放
eg:{id __strong obj = [[NSObject alloc]init];}
相当于ARC无效
{
id obj = [[NSObject alloc]init];
[obj release];
}
2>__strong修饰符变量,不仅只在变量作用域中,在赋值上也能够正确管理对象的所有者:
{//obj0持有对象A的强引用
id __strong obj0 = [[NSObject alloc]init];//对象A
//obj1持有对象B的强引用
id __strong obj1 = [[NSObject alloc]init];//对象B
//obj2不持有任何对象
id __strong obj2 =nil;
//obj0持有obj1赋值的对象B强引用
因为obj0被赋值,所以原先持有对像A的强引用失效,对象A的所有者不存在,因此废弃对象A。
此时,持有对象B的强引用的变量为obj0,obj1;
obj0 = obj1;
//obj2持有由obj0赋值的对象B的强引用,此时,持有对象B的强引用的变量为obj0,obj1,obj2
obj2 = obj0;
NSLog(@"%ld",CFGetRetainCount((__bridge void *)obj2));//3
//因为nil被赋予了obj1,所以对对象B的强引用失效,此时,持有对象B的强引用的变量为obj0,obj2
obj1 = nil;
//因为nil被赋予了obj0,所以对对象B的强引用失效,此时,持有对象B的强引用的变量为obj2
obj0 = nil;
//因为nil被赋予了obj2,所以对对象B的强引用失效,对象B的所有者不存在,因此废弃对像B
obj2 = nil;
}
3> __strong,__weak,__autoreleasing修饰符和自动变量初始化为nil,
id __strong obj0;等同于id__strong obj0 =nil;
4>通过__strong修饰符,不必再次键入retain或者release,就会完美满足“引用计数式内存管理思考方式"自己生成的对象,自己所持有,非自己生成的对象,自己也能持有:通过对带__strong修饰符的变量赋值即可不再需要自己持有的对象时释放:通过废弃带__strong修饰符的变量(变量作用域结束或是成员变量所属对象废弃)或对变量赋值。非自己持有的对象无法释放:由于不必再次键入relase,所以原本不会执行
__weak修饰符
1.生成对象不能继续为自己所有,所以生成对象会立即被释
2.使用__weak修饰符避免循环引用
3.在持有某对象时,若该对象被废弃,则此弱引用将自动失效且处于nil被赋值的状态。
4.通过检查附有__weak修饰符的变量时是否为nil,可以叛断被赋值的对象是否已废弃。
@interface Test:NSObject{
id __strong obj_;
}
- (void)setObject:(id__strong)obj;
@end;
@implementtaion Test
-(instancetype)init{
self = [super init];
return
self;
}
- (void)setObject:(id__strong)obj{
obj_ = obj;
}
@end
{
//test0
持有Test对象A的强引用
id test0 = [[Test alloc]init];//对象A
//test1
持有Test对象B的强引用
id test1 = [[Test alloc]init];//对象B
//Test对象A的obj_成员变量持有Test对象B的强引用,此时,持有Test对象B强引用的变量为Test对象A和obj_和
test1
[test0 setObject:test1];
//Test对象B的obj_成员变量持有Test对象A的强引用,此时,持有Test对象A强引用的变量为Test对象B和obj_和
test0
[test1 setObject:test0];
}
//因为test0变量超出其作用域,强引用失效,所有自动释放Test的对象A
因为test1变量超出其作用域,强引用失效,所有自动释放Test的对象B,此时,持有Test对象A的强引用变量为Test对象B的obj_;持有Test对象B的强引用变量为Test对象A的obj_,发行内存泄漏
对象持有自身时出会发生循环引用:
{
id test = [[Test alloc]init];//对象A
[test setObject:test]
}
{
id test = [[Test alloc]init];//对象A
[test setObject:[[NSObject alloc]init]]];
}
因为test变量超出其作用域,强引用失效,所以自动释放Test对Test对象不存在,因此废弃该对象,废弃Test对象的同时,Test对象的obj_成员也被废弃,NSObject对象的强引用失效,自动释放NSObject对象NSObject对象的所有者不存在,因此废弃该对象
__unsafe_unretained修饰符(现在估计很少用到)
1.__weak只能用于iOS5以上及OS X Lion以上版本,在iOS4及OS
X Snow Leopard应用程序中可用
__unsafe_unretained来代替__weak
2.赋值给附__unsafe_unretained修饰符变量的对象在通过该变量使用时,如果没有确保其确实存在,那么应用程序就会崩溃
3.尽管ARC式的内存管理是编译器的工作,但__unsafe_unretained修饰符的变量不属于编译器的内存管理对象
__autoreleasing修饰符
1.ARC有效时,要通过将象赋值给附加了__autoreleasing修饰的变量来替代调用autorelease方法。
对象赋值给附有__autoreleasing修饰符的变量
等价在ARC无效时调用对象的autorelease方法,即对象被注册到autoreleasepool eg:
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init] ;--> @autoreleasepool{
[obj autorelease]; -->id__autoreleasing obj2;obj2 = obj;
[pool drain]; -->}
}
2.显示附加__autoreleasing修饰符同显示地附加__strong修饰符一样罕见取得非自己生成并持有的对象时,虽然可以使用alloc/new/copy/mutableCopy以外的方法来取得对象,但该对象已被注册到了autoreleaspool.这同在ARC无效时取得调用了autorelease方法的对象是一样的,这是由于编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasepool.
@autoreleasepool{
//取得非自己生成并持有的对象
id __strong obj = [NSMutableArray array];
//因为变量obj为强引用,所以自己持有对象,并且该对象由编译器判断其方法后,自动注册到autoreleasepool
}
变量obj超出其作用域,强引用失效,自动释放自己持有的对象,同时随着@autoreleasepool块的结束,注册到autoreleasepool中的
所有对象被释放。因为对象的所有者不存在,所以废弃对象
3.不使用__autoreleasing修饰符也能使对象注册到autoreleasepool
+(id)array{
id obj = [[NSMutableArray alloc]init];
return obj;
}
由于return使得对象超出其作用域,所以该强引用对应的自己持有的对象会被自动释放,但对象作为函数的返回值,
编译器会自动将其注册到autoreleasepool
4.__weak修饰符是虽是为了避免循环引用而使用的,但在访问附有__weak修饰的变量时,实际上必定要访问注册到autoreleasepool的对象。__weak修饰对象只持有对象的弱引用,而在访问引用对象的过程中,该对象有可能被废弃,如果要把访问的对象注册到autoreleasepool中
那么@autoreleasepool块结束之前都能确保该对象存在。因此__weak修饰的变量时就必定要使用注册到autorelepool中的对象
id __weak obj1 = obj0;
NSLog([obj1 class]);
等价于
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog([tmp class]);
5.id指针或对象的指针在没有显式指定时会被附加上__autoreleasing修饰符
id __strong obj --> id__autoreleasing *obj
NSObject **obj --> NSObject * __autoreleasing *obj;
- (BOOL)performOperationWithError:(NSError **)error; -->(等价于)
- (BOOL)performOperationWithError:(NSError *__autoreleasing *)error;
6.ARC规则
1>不能使用retain/release/retainCount/autorelease
2>不能使用NSAllocateObject/NSDeallocateObject
3>须遵守内存管理的方法命名规则
4>不要显式调用dealloc
5>使用@autoreleasepool块替代NSAutoreleasePool
6>不能使用区域(NSZone)
7>对象型变量不能作为C语方结构体(struct/union)的成员
(struct Data{NSMutableArray *array};)
8>显式转换id和void*
7.指针转换
//ARC无效
id obj = NSObject.new;
void *p = obj;
id o = p;
[o release];
//ARC
有效 ,用 __bridge转换,id和void *就能够相互转换
id obj = NSObject.new;
void *p = (__bridgevoid*)obj;
id o =(__bridgeid) p;
但是转换为void *的__bridge转换,其安全性与赋值给__unsafe_unretained修饰符相近,甚至会更低,如果管理
时不注意赋值对象的所有者,就会悬垂指针而导致程序崩溃
__bridge转换分两种:
__bridge_retained使要转换赋值的变量也持有的赋值的对象,变量作用域结束时,__bridge_retained持有该对象不会被废弃
__brideg_transfer被转换的变量所持有的对象在该变时被赋值给转换目标变量后随之释放
void *p = 0;
{
id obj = [[NSObject alloc]init];
p = (__bridge_retained void*)obj;
}
NSLog([(__bridge id)p class]);
变量作用域结束时,虽然随着持有强引用的变量obj失效,对象随之释放,但由于__bridge_retained转换使变量p
看上去处于持有该对象的状态,因此该对象不会被废弃
ARC无效
{
id obj = (__bridge_transfer id)p;
}
相当于:
{
id obj = (id)p;
[obj retain];
[id)p release];
}
同__bridge_retained转换与retain类似,__bridge_transfer转换与release相似.
在给id ojb赋值时retain即相当于__strong修饰符的变量
//将生成并持有的NSMutableArray对象作为CoreFoundation对象来处理
CFMutableArrayRef cfObject = NULL;
{
/*
obj持有生成并持有对象的强引用
*/
id obj = [[NSMutableArray alloc]init];
/*
通过CFBridgingRetain,将对像CFRetain,赋值给变量cfObject
*/
cfObject = (void *)CFBridgingRetain(obj);
CFShow(cfObject);
/*
通过变量obj的强引用和通过CFBridgingRetain,引用计数为2
*/
printf("retain count = %ld\n",CFGetRetainCount(cfObject));
}
//变量obj超出其作用域,所以其强引用失效,引用计数为1
printf("retain count after the scope = %ld\n",CFGetRetainCount(cfObject));
//将对象CFRelease,其引用计数为0,该对象被废弃
CFRelease(cfObject);
//用__bridge转换来替代CFBridgingRetain或__bridge_retained转换时
CFMutableArrayRef cfObj = NULL;
{
// obj持有生成并持有对象的强引用
id obj = NSMutableArray .new;
//__bridge转换不改变对象的持有状况,只有通过变量obj的强引用,引用计数为1
cfObj = (__bridge CFMutableArrayRef)obj;
CFShow(cfObj);
printf("retain count = %ld\n",CFGetRetainCount(cfObj));
}
//变量obj超出其作用域,其强用失效,对象得到释放,无持有者的对象被废弃
printf("retain count after the scope = %ld\n",CFGetRetainCount(cfObj));
//此后对对象的访问出错,悬垂指针
CFRelease(cfObj);
//由此可知,CFBridgingRetain或者__brideg_retained转换是不可或缺的
//使用Core Foundation的API生成并持有对象,将该对象作为NSMutalbeArray对象来处理
{
//Core Foundation
框架的API生成并持有对象,之后的对象引用计数为1
CFMutableArrayRef cfObj = CFArrayCreateMutable(kCFAllocatorDefault,
0, NULL);
printf("retain count = %ld\n",CFGetRetainCount(cfObj));
//通过CFBridgingRelease赋值,变量obj持有对象强引用同时,对象通过CFRelease释放
//id obj = CFBridgingRelease(cfObj);
id obj = (__bridge_transfer id)(cfObj);
//因为只有变量obj,持有对生成并持有对象的强引用,故引用计数为1.另外,因为经由CFBridgingRelease
//转换后,赋值给变量cfObj中的指针也指向仍然存在的对象,所有可以正常使用
printf("retain count after the cast = %ld\n",CFGetRetainCount(cfObj));
NSLog(@"class = %@",obj);
}
//ojb超出其作用域,所以其强引用失效,对象得到释放,无所者的对象随之被废弃
// 用__bridge转换替代CFBridgingRelease或__bridge_transfer转换的情形
{
//Core Foundation
框架的API生成并持有对象,之后的对象引用计数为1
CFMutableArrayRef cfObj = CFArrayCreateMutable(kCFAllocatorDefault,
0, NULL);
printf("retain count = %ld\n",CFGetRetainCount(cfObj));
//通过赋值给__strong修饰的变量中,所以发生强引用
id obj = (__bridge id)cfObj;
//因为只有变量obj,持有对生成并持有对象的强引用,对象没有进行CFRelease,所以引用计数为2
printf("retain count after the cast = %ld\n",CFGetRetainCount(cfObj));
NSLog(@"class = %@",obj);
}//ojb超出其作用域,所以其强引用失效,对象得到释放
//因为引用计数为1,所以对象仍然存在,发生内存泄漏
/*因此,必须恰当使用CFBridgingRetain/CFBridgingRelease或者__bridge_retained/__bridge_transer转换在将OBjective-C变量赋值给C语言变量,即没有附加所有权修饰符的void*等指针型变量时,
伴随着一定的风险,在实现代码时要高度重视
*/
四、 ARC属性
属性声明的属性与所有权修饰符的对应关系
属性声明的属性 所有权修饰符
assign __unsafe_unretained修饰符
copy __strong修饰符(赋值的是被复制的对象)
retain __strong修饰符
strong __strong修饰符
unsafe_unretained __unsafe_unretained修饰符
weak __weak修饰符
五、数组中附有__strong修饰符变量,数组超出其变量作用域时,数组中各个附有__strong修饰符的变量也随之失效,
其强引用消失,所赋值的对象也随之释放。
//静态数组
{
id objs[2];
objs[0] = [[NSObject alloc]init];
objs[1] = [NSMutable array];
}
动态数组
Foundation框架的容器也可为我们管理对象,但在C语言的动态数组中也可以使用附有__strong修饰符的变量。id指针或对象的指针在没有显式指定时会被附加上__autoreleasing
*类型,所以有必要显式指定为__strong修饰符虽然保证了附有__strong修饰符的id型变量被初始化为nil,但并不保证附有__strong修饰符的id指针型变量被初始化为nil
NSObject *__strong *array =
nil;
// id *__strong *array = nil;
array = (id __strong *)calloc(1024,sizeof(NSObject
*));
// 或
array = (id __strong *)malloc(sizeof(NSObject *)*1024);
for (NSUInteger i =
0; i < 1024; ++i) {
array[i] = nil;
}
可以完全像静态数组一样使用,但动态数组中操作附有__strong修饰符的变量与静态数组有很大差异,需要自己释放所有的元素因为在静态数组中,编译器能够根据变量的作用域自动插入释放赋值对象的代码,而在动态数组中,编译器不能确定数组的生存周期,所以无从处理
array[0] = [NSObject
new];
//释放
for (NSUInteger i =
0; i < 1024; ++i) {
array[i] = nil;
}
free(array);
使用 memcpy函数拷贝数组元素以及realloc函数重新分配内存块也会有危险,由于数组元素所赋值的对象可以被保留在内存中或是重复被废弃,所以这两个函数也禁止。
六、ARC的实现
苹果官方说明称ARC是“由编译器进行内存管理"的,但实际上只有编译器是无法完全胜任的,在此基础上还需要Objective-C运行库的协助,也就是说,ARC由以上工具、库来实现:
clang(LLVM编译器)3.0以上
objc4 Objective-C运行时库493.9以上
// __strong修饰符
{
id __strong obj = NSObject.new;
}
新建一个clang.m文件,输入面内容。在终端命令中用clang -S clang.m可取得程序汇编输出。
根据objc4库源码和汇编输出就能知道程序是如何工作的。
/*
编译器模拟代码
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj , @selector(init));
objc_release(obj);
*/
看看除alloc/new/copy/mutalbleCopy以外的方法是什么情况
{
id __strong obj = [NSMutableArray array];
}
/*
编译器模拟代码
id obj = objc_msgSend(NSMutableArray,@selector(alloc));
objc_retainAutoreleaseReturnValue(obj);
objc_release(obj);
*/
+(id)array{
return [[NSMutableArray alloc]init];
}
//编译器模拟代码
+(id)array
{
id obj = objc_msgSend(NSMutableArray,@selector(alloc));
objc_msgSend(obj,@selector(init));
return objc_autoreleaseReturnValue(obj);
}
像源代码这样,返回注册到autoreleasepool中对象的方法使用了objc_autoreleaseReturnValue函数返回注册到autoreleasepool中的对象。但是objc_autoreleaseReturnValue函数objc_autorelease函数不同,一般不仅限于注册对象到autoreleasepool中objc_autoreleaseReurnValue函数会检查使用该函数的方法或函数调用方的执行命令列表,如果方法或函数的调用方在调用了方法或函数后紧接着调用objc_retainAutoreleasedReturnValue()函数,那么就不将返回的对象注册到autorreleasepool中,而是直接传到方法或函数的调用方。objc_retainAutoreleaseReturnValue函数与objc_retain函数不同,它即便不注册到autoreleasepool中而返回对象,也能够正确地获取对象。通过objc_autoreleaseReturnValue和objc_retainAutoreleasedReuturnValue函数的协作,可以不将对象注册到autoreleasepool中而直接传递,这一过程达到了最优化。
__weak修饰符
1.若附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量
2.使用附有__weak修饰符的变量,即是使用注册到autoreleasepool中的对象
{
id__weak obj1 = obj;// obj附加__strong修饰且对象被赋值
}
编译器模拟代码
id obj1;
objc_initWeak(&obj1,obj);//__初始化附有__weak修饰符的变量,在变量作用域结束时通过objc_destoryWeak释放
objc_destoryWeak(&obj1);
//objc_initWeak函数将附有__weak修饰符的变量初始化为0后,会将赋值的对象作为参数调用objc_storeWeak函数
obj1 = 0;
objc_storeWeak(&ojb1,obj)
//objc_destoryWeak函数将0作为参数调用objc_storeWeak函数
objc_destoryWeak(&ojb1,0)
前面源代码与下面源代码相同
id obj1;
obj1 = 0;
objc_storeWeak(&obj1,ojb);
objc_storeWeak(&obj1,0);
objc_storeWeak函数把第二参数的赋值对象的地址作为键值,将第一参数的附有__weak修饰符的变量的地址注册到weak表中,如果第二参数为0,则表变量的地址从weak表中删除weak表与引用计数表相同,如果使用weak表,将废弃的对象的地址作为键值进行检索,就能高速的获取对应的附有__weak修饰符的变量的地址。另外,由于一个对象可同时赋值给多个附有__weak修饰的变量中,所以对于一个键值,可注册多个变量的地址。释放对象时,废弃不持有的对象,对象将通过objc_release函数释放
1>objc_release
2>计数为0,执行dealloc
3>_objc_rootDealloc
4>objc)dispose
5>objc_destructInstance
6>objc_clear_deallocating
对象被废弃时最后调用objc_clear_deallocating函数如下:
1>从weak表中获取废弃对象的地址为键值的记录
2>将包含在记录中的所有附有__weak修饰符变量的地址,赋值为nil
3>从weak表中删除该记录
4>从引用计数表中删除废弃对象的地址为键值的记录
根据以上步骤,前面说的如果附有_weak修饰符的变量所有引用的对象被废弃,则将nil赋值给该变量这一功能取被实现由此可知,如果大量使用附有__weak修饰的变量,则会消耗相应的CPU资源,良策是只在需要避免循环引用时使用__weak修饰符自己生成并持有的对象赋值给附有__weak修饰的变量中,自己不能持有该对象,这时会被释放并被废弃
{
id __weak obj = [[NSObject alloc]init];
//id __unsafe_unretained unsafeObj = [[NSObject alloc]init];
NSLog(@"obj = %@ ",obj);//obj = (null)
}
/*编译器模拟代码
id obj;
id tmp = objc_msgSend(NSOBject,@selector(alloc));
objc_msgSend(tmp,@selector(init));
objc_initWeak(&obj,tmp);
objc_release(tmp);
objc_destoryWeak(&object);
虽然自己生成并持有的对象通过objc_initWeak()被赋值给附有__weak修饰的变量中,但编译器判断其没有持有者,帮该对象立即通过objc_release函数被释放和废弃,这样,nil就会赋值引用废弃对象了附有__weak修饰符的变量中
*/
__weak修饰的变量,即是使用注册到autoreleasepool中的对象
{
id __weak obj1 = obj;
NSLog(@"%@",obj1);
}
//编译器模拟代码
id obj1;
objc_initWeak(&obj1,obj);
id tmp = objc_loadWeakRetained(&obj1);//取出附有__weak修饰变量所引用的对象并retain
objc_autorelase(tmp);//将对象注册到autoreleasepool中
NSLog(@"%@",tmp);
objc_destoryWeak(&object);
由此可知,因为附有__weak修饰符变量所引用的对象像这样被注册到autoreleasepool中,所以在@autoreleasepool块结束之前都可以放心使用。但是,如果大量地使用附有__weak修饰符的变量,注册到autoreleasepool的对象也会大量地增加,因此在使用附有__weak修饰符的变量时,最好先暂时赋值给附有__strong修饰符的变时后再使用,如下代码使用了5次附有__weak修饰符的变量o
{
id __weak o = obj;
NSLog(@"1 = %@",o);
NSLog(@"2 = %@",o);
NSLog(@"3 = %@",o);
NSLog(@"4 = %@",o);
NSLog(@"5 = %@",o);
}
将附有__weak修饰符的变量o赋赋值给附有__strongk修饰符变量后再使用可以避免此类问题,在tmp=0时对象仅加到autoreleasepool中1次
{
id __weak o = obj;
id tmp = o
NSLog(@"1 = %@",tmp);
NSLog(@"2 = %@",tmp);
NSLog(@"3 = %@",tmp);
NSLog(@"4 = %@",tmp);
NSLog(@"5 = %@",tmp);
}
//allowsWeakReference/retainWeakReference
不能使用__weak修饰符
allowsWeakReference/retainWeakReference实例方法返回NO的情况,在赋值给_weak修饰符的变量时,如果赋值对象allowsWeakReference方法返回NO,程序将异常终止。即对于所有allowsWeakReference返回NO的类绝不能使用__weak悠饰符,另外,在使用__weak修饰符的变量时,当被赋值对象的retainWeakReference方法返回NO的情况下,该变量将使用nil.
@interface MyObject : NSObject{
NSUInteger count;
}
@end
@implementation MyObject
-(instancetype)init{
self = [super init];
return
self;
}
- (BOOL)retainWeakReference{
if (++count >
3) {
return NO;
}
return [super retainWeakReference];
}
@end
{
id __strong obj = [MyObject alloc].init;
id __weak o = obj;
NSLog(@"1 = %@",o);
NSLog(@"2 = %@",o);
NSLog(@"3 = %@",o);
NSLog(@"4 = %@",o);
NSLog(@"5 = %@",o);
}
/*
1 = <MyObject: 0x100203810>
2 = <MyObject: 0x100203810>
3 = <MyObject: 0x100203810>
4 = (null)
5 = (null)
*/
//__autoreleasing修饰符
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc]init];
}
/*编译器模拟代码
id pool = objc_autoreleasePoolPush();
id tmp = objc_msgSend(NSOBject,@selector(alloc));
objc_msgSend(tmp,@selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
*/
//__autoreleasing修饰符
@autoreleasepool {
id __autoreleasing obj = [NSMutableArray array];
}
/*编译器模拟代码
id pool = objc_autoreleasePoolPush();
id tmp = objc_msgSend(NSOBject,@selector(alloc));
objc_retainAutoreleaseReturnValue(obj);
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
*/
//引用计数器
{
id __strong obj = [[NSObject alloc]init];
id __weak o = obj;
NSLog(@"retain count = %d",_objc_rootRetainCount(obj));//1
NSLog(@"retain count = %d",_objc_rootRetainCount(obj));//1
}
//由于弱引用并不持有对象,所以赋值给附有__weak修饰的变量中也必定不会改变引用计数数值
@autoreleasepool {
id __strong obj = [[NSObject alloc]init];
id __autoreleasing o = obj;
NSLog(@"retain count = %d",_objc_rootRetainCount(obj));//2
}
//对像被附有__strong修饰符变量的强引用所持有,且被注册到autoreleasepool中,所以为2
{
id __strong obj = NSObject.new;
@autoreleasepool {
id __autoreleasing o = obj;
NSLog(@"retain count = %d",_objc_rootRetainCount(obj));//2
}
NSLog(@"retain count = %d",_objc_rootRetainCount(obj));//1
}
@autoreleasepool {
id __strong obj = NSObject.new;
_objc_autoreleasePoolPrint();
id __weak o =obj;
NSLog(@"before using __weak:retain count = %d",_objc_rootRetainCount(obj));//1
NSLog(@"class = %@",[o
class]);
NSLog(@"after using __weak:retain count = %d",_objc_rootRetainCount(obj));//2
_objc_autoreleasePoolPrint();
}
不使用__autoreleasing修饰符,仅使用附有__weak声明的变量也能将引用对象注册到了autoreleasepool中
以上例子均使用了_objc_rootRetainCount(),但实际上并不能完全信任其取得的数值。对于已释放的对象以及不正确的对象地址,
有时也返回1,另外,在多线程使用对象的引用计数数值,回为存有竞态条件的问题,所以取得的数值不一定完全可信
相关文章推荐
- React Native开发坑之null is not an object (evaluating 'inst.componentWillReceiveProps')
- 继承自NSObject的不常用又很有用的函数(2)
- 笔记:harvesting discriminative meta objects with deep CNN features for scene classification
- JavaScript 中的Object的使用详解笔记(一)
- 【书籍篇】《Objective-C程序设计》语法相关
- 三言两语 - 理解Objective-C的meta-class
- iOS中js与objective-c的交互
- iOS开发之Objective-C与JavaScript的交互
- ios经验收藏 使用objection来模块化开发iOS项目
- The Java™ Tutorials — Concurrency :Lock Objects 锁对象
- JSONObject与JSONArray的使用
- Objective-C runtime
- python flask api get时ValueError("No JSON object could be decoded")
- iOS Swift 和 Objective-c 两种语言对于多线程的详细解释
- regionlets for generic object detection
- objective-C学习 通知 Notification
- 用Pull解析将xml转换成List<Object>对象
- Objective-C中的const extern static用法
- 使用Qt的QAxObject将Excel文件转成CSV文本文件
- c# List<实体>转换List<object>集合