一、自动引用计数
1.1 内存管理的思考方式
对象操作 | Object-C 方法 |
生成并持有对象 | alloc、new、copy、mutableCopy |
持有对象 | retain方法 |
释放对象 | release方法 |
废弃对象 | dealloc方法 |
1.1.1 自己生成的对象,自己持有
使用以下名称开头的方法一位着自己生成的对象只有自己持有: alloc |new | copy |mutableCopy
使用NSObject类的alloc类方法就能自己生成并持有对象。指向生成并持有对象的指针被赋值给变量obj. 使用new类方法也能生成并持有对象,
[NSOject new] == [[NSObject alloc] init]
copy 方法基于NSCooying方法约定,由各类实现的copyWithZone:方法生成并持有对象的副本。
与copy方法类似,mutableCopyWithZone:方法生成并持有对象的副本。
两者区别在于copy方法生成不可变更的对象,而mutableCopy方法生成可变更的对象。这类似于NSArray类对象与NSMutableArray类对象的差异。用这些方法生成的对象,虽然是对象的副本,但同alloc、new方法一样,在“自己生成并持有对象”这点上没有改变。
1.1.2 非自己生成的对象,自己也能持有
除 alloc |new | copy |mutableCopy 之外的方法取得的对象,因为非自己生成并持有,所以自己不是该对象的持有者
eg1:
// 取得非自己生成并持有的对象 取得的对象存在,但自己不持有对象
id obj = [NSMutableArray array];
// NSMutableArray类对象被赋给变量obj,但变量obj自己并不持有该对象,使用retain方法可以持有对象。
// 自己持有对象
[obj retain];
//通过retain 方法,非自己生成的对象跟用alloc、new、cooy、mutableCopy方法生成并持有的对象一样,成为了自己所持有的。
1.1.3 不再需要自己持有的对象时释放
自己持有的对象,一旦不再需要,持有者有义务释放该对象,释放使用release方法
// 释放对象,对象不可再被访问
[obj release];
若要用某个方法生成对象,并将其返还给该方法的调用方?
- 根据命名规则,这些用来取得谁都不持有的对象的方法不能以alloc/ new / copy/ mutableCopy
- (id) allObject{id obj = [NSObject alloc] init];return obj;
}
id obj1 = [obj0 allObject];
如上例所示:原封不动低返回用alloc方法生成并持有的对象,就能让调用方也持有该对象。
调用[NSMutableArray array] 方法使取得的对象存在,但自己不持有对象,如何实现?
- (id) object{id obj = [NSObject alloc ]init];[obj autorelease];// autorelease提供这样的功能,使对象在超出指定的生存范围时你那个自动并正确地释放(调用realse方法return obj;
}
1.1.4 无法释放非自己持有的对象
对于持有者是自己的对象,不需要该对象时,需将其释放。
除此之外之外所得到的对象(非自己持有的对象)以及 自己持有对象后 释放过的对象,释放时会导致奔溃;
1.2 alloc/ retain/ release/ dealloc 实现
p13
alloc类方法用struct obj_layout中的retained整数来保存引用计数,并将其写入对象内存头部,该对象内存块全部置为0返回。
执行alloc 后的对象的retainCout是1
由对象寻址找到对象的内存头部,从而访问其中的retained变量。
- 由O调用bject - C对象中存有引用计数这一整数值。
- 调用 alloc 或是 retain 方法后,引用计数值加1。
- 调用release后,引用计数值减1。
- 引用计数值为0时,调用dealloc方法废弃对象。
苹果的引用计数的实现:将引用计数保存在引用计数表的记录中。(GNUstep是将引用计数保存在对象占用内存块头部的变量中)
1.3 autorelease
autorelease会像C语言的自动变量那也来对待对象实例。当超出其作用域(相当于变量作用域)时,对象实例的release实例方法被调用。另外,同c语言不同的是,编程人员可以设定变量的作用域。
autorelease 的具体使用方法如下:
- 生成并持有NSAutoreleasePool对象;
- 调用已分配对象的autorelease方法;
- 废弃NSAutoreleasePool对象。
1.4 所有权修饰符
ARC有效时,id类型和对象类型同c语言其他类型不同,其类型上必须附加所有权修饰符,所有权修饰符一共有4种。
_strong、_weak、_unsafe_unretained、 _autoreleasing
_strong修饰符是id类型和对象类型默认的所有权修饰符。即:下面两种方式相同。表示对对象的“强引用”。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。
ARC有效时:
id obj = [[NSObject alloc] init];
id _strong obj = [[NSObject alloc] init];
ARC无效时:
{id obj = [[NSObject alloc] init]; [obj release];
}
为了释生成并持有的对象,增加了调用release方法的代码。改代码进行的动作同先前ARC有效时的动作完全一样。
取得非自己生成并持有的对象,因为变量obj为强引用,所以自己持有对象。当变量obj超出其作用域,强引用失效,所以自动低释放自己所持有的对象,对象的所有者不存在,因此废弃该对象。
id _strong obj = [NSMutable array];
附有_strong修饰符的变量之间可以相互赋值
id _strong obj0 = [NSObject alloc]init]; // obj0持有对象A的强引用
id _strong obj1 = [NSObject alloc]init]; // obj1持有对象B的强引用
id _strong obj2 = nil; // obj2不持有任何对象
obj0 = obj1; // obj0持有由obj1赋值的对象B的强引用。因为obj0被赋值,所以原先持有的对对象A的强引用失效,对象A的所有者不存,因此废弃对象A。
// 此时,持有对象B的强引用的变量为obj0和object1
obj2 = obj0; // obj2持有由obj0赋值的对象B的强引用。
//此时,持有对象B的强引用的变量为obj0,obj1和obj2
obj1 = nil; //因为nil被赋予了obj1,所以对对象B的强引用失效
//此时,持有对象B的强引用的变量为obj0和obj2
obj0 = nil;//因为nil被赋予了obj0,所以对对象B的强引用失效
//此时,持有对象B的强引用的变量obj2
obj2 = nil;//因为nil被赋予了obj1,所以对对象B的强引用失效
//此时,象B的所有值不存在,因此废弃对象B
_weak
_strong会造成循环引用
@interface Test : NSObject{id _strong obj_;
}
- (void)setObject:(id _strong)obj;
@end
@implementation Test
- (id)init{self = [super init];return self;
}
- (void)setObject:(id _strong)obj{obj_ = obj;
}
@end
{id test0 = [[Test alloc] init]; // test0 持有Test对象A的强引用id test1 = [[Test alloc] init]; // test1 持有Test对象B的强引用[test0 setObject:test1]; //Test对象A的obj_成员变量持有Test对象B的强引用//此时,持有Test对象B的强引用的变量为 Test对象A的obj_和test1[test1 setObject:test0]; //Test对象B的obj_成员变量持有Test对象A的强引用//此时,持有Test对象A的强引用的变量为 Test对象B的obj_和test0
}
因为test0变量超出其作用域,强引用失效,所以自动释放Test对象A
因为test1变量超出其作用域,强引用失效,所以自动释放Test对象B
此时持有Test对象A的强引用的变量为Test对象B的obj_
此时持有Test对象B的强引用的变量为Test对象B的obj_
!!!发送内存泄漏
循环引用容易发送内存泄漏。所谓内存泄漏就是应当废弃的对象在超出其生存周期后继续存在。
此代码的本意是赋予变量test0()的对象A和赋予变量test1的对象B在超出其变量作用域时被释放,即在对象不被任何变量持有的状态下予以废弃。但是,循环引用使得对象不能再次废弃。
循环引用 ->自引用
id test = [Test alloc]init];
[test setObject:test];
_weak修饰符与_stong修饰符相反,提供弱引用,弱引用不能
{id _strong obj0 = [[NSObject alloc]init]; //自己生成并持有对象,因为obj0变量为强引用,所以自己持有对象。 id _weak obj1 = obj0; //obj1变量持有生成对象的弱引用
}
因为变量超出其作用域,强引用失效,所以自动释放自己持有的对象,因为所有者不在,所以废弃该对象。
因为带 _weak 修饰符的变量(即弱引用)不持有对象,所以在超出其变量作用域时,对象即被释放。如果像下面这样将先前可能发生循环引用的类成员变量改成附有 _weak修饰符的成员变量的话,该现象可避免。
@interface Test : NSObject{id _weak obj_;
}
- (void)setObject:(id _strong)obj;
@end
_weak修饰符还有另一个优点。在持有某对象的弱引用时,该对象被废弃,则此引用将自动失效且处于nil被赋值的状态(空弱引用)。如下代码所示:
id _weak obj1 = nil;
{id _strong obj0 = [[NSObject alloc] init];//自己生成并持有对象,因为obj0变量为强引用,所以自己持有对象。 obj1 = obj0;//obj1变量持有生成对象的弱引用
}
因为obj0变量超出其作用域,强引用失效,所以自动释放自己持有的对象,因为无所有者,所以废弃该对象。废弃所有者的同时,持有该对象弱引用的onj变量的弱引用失效,nil赋值给obj1.
使用 _weak修饰符可避免循环引用。通过检查附有_weak修饰符的变量是否为nil,可以判断被赋值的对象是否已废弃。
_autoreleasing修饰符
ARC 有效时,autorelease如何?
不能使用autolease方法,也不能使用NSAutoreleasePool类。但其实autolease功能起作用的。
//ARC无效
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [NSObject alloc] init];
[obj autorelease];
[pool drain];
//ARC无效
@autoreleasepool{id _autoreleasing obj = [NSObject alloc] init];
}
指定"@autoreleasepool 块"来替代“NSAutoreasePool类对象生成、持有以及废弃”这一范围。
ARC有效时,要通过将对象赋值给附加了_autoreleasing修饰符变量来替代调用autoreleasing方法。对象赋值给附有_autoreleasing修饰符的变量等价于在ARC无效时调用对象的autorelease方法,即对象被注册到autoreleasepool。
即在ARC有效时,用@autoreleasepool 块"来替代“NSAutoreasePool类,用附有_autoreleasing修饰符的变量替代autorelease方法。
取得非自己生成并持有的对象时,如下所示:虽然可以使用alloc、new、copy、mutableCopy以外的方法来取得对象,但该对象已被注册到了autoreleasepool。
这同在ARC无效时取得调用了autorelease方法得对象是一样的。
这是由于编译器会检查方法名是否以alloc、new、copy、mutableCopy开始,如果不是自动将返回值的对象注册到autoreleasepool.
!!! 要遵守内存管理方法命名规则,init方法返回值的对象不注册到autoreleasepool。
@autoreleasepool{id _strong obj = [MSMutable array];//取得非自己生成并持有的对象//因为变量obj为强引用,所以自己持有对象。并且该对象由编译器判断其方法后,自动注册到autoreleasepool
}
// 因为变量obj超出其作用域,强引用失效,所以自动释放自己持有的对象。
// 同时,随着@autoreleasepool 块的结束,注册到autoreleasepool中的所有对象被自动释放。因为对象的所有者不存在,所以废弃对象。
以上是不使用 _autoreleasing修饰符也能使对象注册到autoreleasepool。以下为取得非自己生成并持有对象时被调用方法:
+ (id) array{id obj = [[NSMutableArray alloc] init];return obj;
}
因为没有显示指定所有权修饰符,所以 id obj 同附有 _strong 修饰符的id_strong obj 是完全一样的。
由于return 使得对象变量超出其作用域,所以该强引用对应的自己持有的对象会被自动释放。但该对象作为函数的返回值,编译器会自动将其注册到autoreleasepool。
以下为使用_weak修饰符的例子。虽然_weak修饰符是为了避免循环引用而使用的,但在访问附有_weak修饰符的变量时,实际上必定要访问注册到autoreleasepool的对象。
id _weak obj1 = obj0;
NSLog(@"class = %@",[obj1 class]);
==========================
id _weak obj1 = obj0;
id _autoreleasing tmp = obj1;
NSLog(@"class = %@",[tmp class]);
为什么在访问附有_weak修饰符的变量必须注册到autoreleasepool的对象?
因为_weak修饰符只持有对象的弱引用,而在访问对象的过程中,该对象可能被废弃。如果把要访问的对象注册到autoreleasepool中,那么在@autoreleasepool块结束之前都能确保该对象存在。因此在使用附有_weak修饰符的变量时就必定要使用注册到autoreleasepool中的对象。
id的指针或对象的指针会默认附加上_autoreleasing修饰符,所以以下代码相等:
- (BOOL) performOperationWithError:(NSError *)error;
========================
- (BOOL) performOperationWithError:(NSError *_autoreleasing *)error;
作为alloc /new /copy /mutableCopy 方法返回值取得的对象是自己生成并持有的,其他情况下便是取得非自己生成并持有的对象。
因此,使用附有_autoreleasing修饰符的变量作为对象取得参数,与除alloc /new /copy /mutableCopy外其他方法的返回值取得对象完全一样,都会注册到autoreleasepool,并取得非自己生成并持有的对象。
_unasfe_unretained修饰符
不安全的所有权修饰符,所修饰的变量不属于编译器的内存管理对象。同_weak 一样,自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即释放。
在使用_unasfe_unretained修饰符时,赋值给附有_strong修饰符的变量时,有必要确保被复制的对象确实存在。否则会奔溃。
1.5 规则
dealloc 中只需记述废弃对象时所必须的处理。
1.6 属性
当ARC有效时,以下可作为这种属性声明中使用的属性来用。
1.7 ARC的实现
1.7.1 _strong修饰符