美文网首页
内存管理(ARC)

内存管理(ARC)

作者: 火焰与柠檬 | 来源:发表于2016-06-19 11:35 被阅读0次

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内存管理技术要点

相关文章

  • iOS夯实:ARC时代的内存管理

    iOS夯实:ARC时代的内存管理 iOS夯实:ARC时代的内存管理

  • OC中内存管理

    在OC中内存管理MRC手动内存管理和ARC自动内存管理,ARC是从iOS 4.0开始,在iOS 4.0之前...

  • 内存管理

    ARC内存管理机制详解理解 iOS 的内存管理

  • 内存及性能优化

    1. 用ARC管理内存 ARC除了帮你避免内存泄露,ARC还可以帮你提高性能,它能保证释放掉不再需要的对象的内存。...

  • iOS开发之Autoreleasepool简介

    Autoreleasepool即自动释放池,是在ARC自动管理内存机制下用来管理程序中开辟的内存的,ARC工程每个...

  • OC - OC的内存管理机制

    导读 一、为什么要进行内存管理 二、内存管理机制 三、内存管理原则 四、MRC手动内存管理 五、ARC自动内存管理...

  • 《iOS开发进阶》阅读笔记(一)内存管理释疑

    ARC ARC能够解决iOS开发中90%的内存管理问题,但是另外还有10%的内存管理是需要手动管理的,主要就是与底...

  • MRC、ARC内存管理机制

    MRC、ARC内存管理机制?(为什么要进行内存管理, 内存管理的范围和对象, 内存管理的原理) ** (为什么)...

  • 11-AutoreleasePool实现原理上

    我们都知道iOS的内存管理分为手动内存管理(MRC)和自动内存管理(ARC),但是不管是手动内存管理还是自动内存管...

  • iOS面试常问的知识点

    内存管理方面(ARC、MRC、autorelease、autoreleasepool,简单粗暴的说一说内存管理) ...

网友评论

      本文标题:内存管理(ARC)

      本文链接:https://www.haomeiwen.com/subject/qfybdttx.html