1.ARC的本质
ARC是编译器的特性,它并没有改变OC引用计数式内存管理的本质,更不是GC(垃圾回收),底层实现依然依赖引用计数,只不过ARC模式下编译器会自动帮我们管理。
打开ARC:-fobjc-arc
关闭ARC:-fno-objc-arc
2.引用计数管理原则
(1)自己生成的对象自己持有
(2)非自己生成的对象自己也可以持有
(3)自己持有的对象不需要时可以释放
(4)非自己持有的对象自己不能释放
3.四种变量所有权修饰符
__strong
__weak
__autoreleasing
__unsage_unretaied
以上是变量所有权修饰符
以下是属性修饰符
copy 对应的所有权类型是 __strong
拷贝,复制一个对象并创建strong关联,引用计数为1,原对象计数不变
retain 对应的所有权类型是 __strong
持有(MRC),强引用,原对象引用计数加1
strong 对应的所有权类型是 __strong
持有(ARC),强引用,原对象引用计数加1
weak 对应的所有权类型是 __weak
赋值,弱引用,不改变引用计数。对象释放后把指针置为nil,避免了也指针
assing 对应的所有权类型是 __unsage_unretaied
赋值,弱引用,引用计数不变。ARC中对象不能使用assign,但基本类型(BOOL、int、float)仍可以使用
unsafe_unretained 对应的所有权类型是 __unsage_unretaied
关于__strong
强引用,对于所有对象,只有当没有任何一个强引用指向它时,它才能够被释放。如果声明引用时不加修饰符,默认是强引用。如果要释放强引用指向的对象时,要将强引用置为nil。
关于__weak
弱引用,弱引用不会影响对象的释放,如果一个对象释放了,那么指向它的所有弱引用全部置为nil,防止产生野指针。
最常见的用法是使用__weak来避免强循环引用。比如:
(1)设置delegate属性为weak。
(2)block中防止强循环引用。
(3)一般由SB和xib脱线的控件
@property (weak, nonatomic) IBOutlet UIButton *testButton;。
关于__autoreleasing
表示在autorelease pool中自动释放对象,与MRC模式下相同。并没有对应的属性修饰符,任何一个对象的属性都不应该是autorelease型的。但是autorelease是一直存在于ARC模式下的。
以下两行代码的意义是相同的。
NSString *str = [[[NSString alloc] initWithFormat:@"hehe"] autorelease]; // MRC
NSString *__autoreleasing str = [[NSString alloc] initWithFormat:@"hehe"]; // ARC
__autoreleasing常见的用法:
(1)参数传递返回值
示例:
- (NSObject *)object {
NSObject *o = [[NSObject alloc] init];
return o;
}
这里o默认是__strong,由于return使得o超出了其作用于,本来应该释放的,但是因为需要将它作为返回值,所以一般情况下要将它注册倒Autorelease Pool中(ARC模式下可以通过运行时优化方案来跳过autorelease机制)。
autorelease机制:
接收方:调用方法的对象,使用o的话就需要强引用它,那么retaincount +1,使用后再rataincount -1。
提供方:提供对象作为返回值,创建了对象那么retaincount +1,使用后再rataincount -1。
但是你要保证对象在返回前没有被释放,否则返回的是nil,这个时候需要一个合理的机制来延长对象的生命周期,又能保证释放它,这个机制就是autorelease机制。
对象作为返回值时,会被放入正在使用的Autorelease Pool中,Autorelease Pool会强引用这个对象,所以对象不会被释放,延长了生命周期,Autorelease Pool自己销毁的时候会讲里面的对象一并销毁。
Autorelease Pool是与线程一一映射的,如果Autorelease Pool的drain方法没有在接收方和提供方交接过程中触发,那么autorelease对象不会被释放(除非严重的线程错乱)。
Autorelease Pool释放的时机
- Run Loop会在每次loop结尾时销毁它。
- GCD 的 dispatched blocks 会在一个 Autorelease Pool 的上下文中执行,这个 Autorelease Pool 不时的就被销毁了(依赖于实现细节)。NSOperationQueue 也是类似。
- 其他线程则会各自对他们对应的 Autorelease Pool 的生命周期负责。
ARC下跳过autorelease机制的优化方法
为了兼容MRC,以及在MRC-to-ARC,ARC-to-MRC,ARC-to-ARC切换。
return的时候:ARC调用objc_autoreleaseReturnValue() 替代autorelease。
调用方持有对象的时候:ARC 会调用objc_retainAutoreleasedReturnValue()。
在调用 objc_autoreleaseReturnValue() 时,ARC会在栈上查询return address来确定return value是否传给了objc_retainAutoreleasedReturnValue()。如果没传,那么会走autorelease过程。如果传了(返回值能从提供方传给交接方),就跳过autorelease并同时修改retain address来跳过objc_retainAutoreleasedReturnValue(),从而一举消除了autorelease和retain。
(2)访问__weak修饰的变量
访问__weak修饰的变量,实际上必定会访问到Autorelease Pool中注册的对象。
id __weak obj1 = obj0;
NSLog(@"class=%@", [obj1 class]);
// 等同于:
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class=%@", [tmp class]);
因为对象只是弱引用,为了保证访问对象的时候,对象没有被废弃,将对象注册到Autorelease Pool中,这样就能保证在Autorelease Pool被销毁前对象时存在的。
(3)引用传递参数
NSError *__autoreleasing error;
if (![data writeToFile:filename options:NSDataWritingAtomic error:&error])
{
NSLog(@"Error: %@", error);
}
error参数的类型是(NSError *__autoreleasing *)。如果你讲error定义为strong类型,那么编译器会隐式的进行转换:
NSError *error;
NSError *__autoreleasing tempError = error; // 编译器添加
if (![data writeToFile:filename options:NSDataWritingAtomic error:&tempError])
{
error = tempError; // 编译器添加
NSLog(@"Error: %@", error);
}
所以为了提高效率,在定义error的时候将其声明为_autrorelease类型的。
在ARC中,这种指针的指针类型的函数参数(NSError **),如果不加修饰符,编译器默认为_autrorelease类型。
(4)某些类的方法会隐式的使用自己的Autorelease Pool,这时使用_autorelease类型要小心。
- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){
@autoreleasepool // 被隐式创建
{
if (there is some error && error != nil)
{
*error = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil];
}
}
}];
// *error 在这里已经被dict的做枚举遍历时创建的autorelease pool释放掉了 :(
}
为了能正常使用*error,需要一个临时的强引用,在dict的block中使用它,保证引用的对象不会在出了block后被释放:
- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
__block NSError* tempError; // 加__block保证可以在Block内被修改
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop)
{
if (there is some error)
{
*tempError = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil];
}
}]
if (error != nil)
{
*error = tempError;
}
}
关于__unsafe _unretained
为了兼容低版本,相当于MRC模式下的assign,仅仅是引用了对象,没有其他任何操作,当对象被释放后依然指向对象(所在的内存地址),会形成野指针,非常的不安全。
现版本可以忽略掉这个修饰符,因为是ARC的时代了。
*的正确使用方式:
NSString * __weak str = @"hehe";
声明在栈中的指针默认为nil,如:
- (void)myMethod
{
NSString *name;
NSLog(@"name: %@", name);
}
会输出null而不是crash。
ARC与Block
MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyController = myController;
myController.completionHandler = ^(NSInteger result) {
MyViewController *strongMyController = weakMyController;
if (strongMyController) {
// ...
[strongMyController dismissViewControllerAnimated:YES completion:nil];
// ...
}
else {
// Probably nothing...
}
};
这里myController强引用了completionHandler
completionHandler调用了dismiss方法,也强引用了completionHandler
这样的话就形成了循环引用。
ARC模式下__ block只代表能在block中修改这个变量,没有其他作用。
要想避免循环引用,需要使用一个weakMyController对象弱引用myController,这样block中对myController持有弱引用的话,就不会产生循环引用。
但是由于block对myController是持有弱引用,那么就有可能在block引用myController之前,myController已经被释放,block因此不能正常使用(单线程中问题不大,一般出现在多线程中)。
所以我们在block中定义一个强引用strongMyController,用它来指向weakMyController指向的对象(我是这样想的,引用其实是指针,这里是将weakMyController指向的地址赋值给了strongMyController,这样strongMyController也指向了相同的地址,也就是同一个对象),这样在block使用之前,myController就不会被提前释放了。
block捕获的变量和在block中定义的变量是有区别的,存在空间和生命周期都是不同的
并不是说所有的block都存在循环引用,比如下面这个:
TestObject *aObject = [[TestObject alloc] init];
aObject.name = @"hehe";
self.aBlock = ^(){
NSLog(@"aObject's name = %@",aObject.name);
};
堆和栈的区别
栈:系统分配,自动管理,存放参数,变量,指针等。基本数据类型存放在栈中,所以内存管理不包括这些。
堆:程序员自己分配,自己释放,存放对象类型,是内存管理的主要内容。
block是分配在栈上面的,为了不被系统回收,声明时会使用copy属性,copy到堆中。
结构体也一样,由于是分配到栈中,所以如果想要传递结构体类型的参数的话,应该将结构体作为一个对象的属性,然后将对象放到数组或字典中(只能存放对象类型)。
引用文章链接:iOS开发ARC内存管理技术要点