iOS - 内存管理(ARC)

作者: 壮骨 | 来源:发表于2018-01-17 18:46 被阅读0次
    历史

    苹果在 2011 年的时候,在 WWDC 大会上提出了自动的引用计数(ARC)。ARC 背后的原理是依赖编译器的静态分析能力,通过在编译时找出合理的插入引用计数管理代码.
    2014 年的 WWDC 大会上,苹果推出了 Swift 语言,而该语言仍然使用 ARC 技术,作为其内存管理方式


    1.引用计数

    1.1什么是引用计数

    引用计数(Reference Count)是一个简单而有效的管理对象生命周期的方式。当我们创建一个新对象的时候,它的引用计数为 1,当有一个新的指针指向这个对象时,我们将其引用计数加 1,当某个指针不再指向这个对象是,我们将其引用计数减 1,当对象的引用计数变为 0 时,说明这个对象不再被任何指针指向了,这个时候我们就可以将对象销毁,回收内存。


    引用计数

    2.ARC的内存管理

    ARC 能够解决 iOS 开发中 90% 的内存管理问题,但是另外还有 10% 内存管理,是需要开发者自己处理的,这主要就是与底层 Core Foundation 对象交互的那部分,底层的 Core Foundation 对象由于不在 ARC 的管理下,所以需要自己维护这些对象的引用计数。

    内存管理问题
    1.循环引用(block,代理)
    2.Core Foundation 对象需要手动管理计数器

    2.1 循环引用

    引用计数这种管理内存的方式虽然使开发变得简便,但是也有瑕疵,那就是不能很好解决循环引用.


    循环引用

    上图所示:对象A和对象B 相互引用成为成员变量,,只有当对象A销毁时,才会对对象A中所以成员变量引用计数-1.因为对象A的销毁依赖于对象B的销毁,对象B的销毁依赖于对象A的销毁.那么就造成了循环引用。即使这两个对象在在其他地方已经没有任何指针调用它们,它们依旧不会释放.
    上述问题不单只是在两个对象之间出现,只有是多个对象中出现了相互持有引用的情况下,都会出现循环引用

    解决方法:

    1. 主动断开循环引用
      因为是相互引用造成的循环引用,那么只要在 不再需要使用这个对象时,将其主动断开引用


      主动断开

      代码示例

    @property (nonatomic,copy) void(^testBlock)(void);
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.testBlock = ^{
            //当你在快中主动调用self时 Xcode会提示你 出现循环引用 这里只是为了测试
            NSLog(@"%@",self);
        };
        self.testBlock();
    }
    - (void)viewWillDisappear:(BOOL)animated{
        [super viewWillDisappear:animated];
        self.testBlock = nil;
        NSLog(@"viewWillDisappear");
    }
    - (void)dealloc {
        NSLog(@"TestViewController -- dealloc");
    }
    
    

    上述代码中 在 viewDidLoad 中 self引用 testBlock 作为成员变量 , testBlock 值中调用self, 那么就造成了循环引用. 所以我们假设在 viewWillDisappear 中不再需要使用 testBlock 所以将值设为nil, 那么testBlock 中就不再调用self了, 所以最后self走了dealloc方法

    image.png

    主动断开循环引用这种操作依赖开发中手动操作,这样感觉像回到了 MRC年代 谁创建谁释放 的年代 ,依赖于开发者自己知道什么时候不再需要,主动断开. 所以这种方法不常用.

    1. 使用弱引用
      弱引用虽然持有对象,但是并不增加引用计数,这样就避免了循环引用的产生。在 iOS 开发中,弱引用通常在 delegate 模式中使用。


      弱引用

    声明属性时将其修饰为弱引用

        @property (nonatomic,weak) id<TestDelegate> delegate;
    

    如果block中调用到相互持有的对象 那么将其弱引用 就不会造成循环引用

        __weak typeof(self) weakSelf = self;
        self.testBlock = ^{
            NSLog(@"%@",weakSelf);
        };
        self.testBlock();
    

    弱引用的实现原理

    弱引用的实现原理是这样,系统对于每一个有弱引用的对象,都维护一个表来记录它所有的弱引用的指针地址。这样,当一个对象的引用计数为 0 时,系统就通过这张表,找到所有的弱引用指针,继而把它们都置成 nil。

    从这个原理中,我们可以看出,弱引用的使用是有额外的开销的。虽然这个开销很小,但是如果一个地方我们肯定它不需要弱引用的特性,就不应该盲目使用弱引用。举个例子,有人喜欢在手写界面的时候,将所有界面元素都设置成 weak 的,这某种程度上与 Xcode 通过 Storyboard 拖拽生成的新变量是一致的。但是我个人认为这样做并不太合适。因为:

    1. 我们在创建这个对象时,需要注意临时使用一个强引用持有它,否则因为 weak 变量并不持有对象,就会造成一个对象刚被创建就销毁掉。
    2. 大部分 ViewController 的视图对象的生命周期与 ViewController 本身是一致的,没有必要额外做这个事情。
    3. 早先苹果这么设计,是有历史原因的。在早年,当时系统收到 Memory Warning 的时候,ViewController 的 View 会被 unLoad 掉。这个时候,使用 weak 的视图变量是有用的,可以保持这些内存被回收。但是这个设计已经被废弃了,替代方案是将相关视图的 CALayer 对应的 CABackingStore 类型的内存区会被标记成 volatile 类型,详见《再见,viewDidUnload方法》
    2.2 Core Foundation 对象需要手动管理计数器

    一般来说,以CF开头的系统API都是 CoreFoundation框架的
    手动管理,说白了,就是谁创建,谁销毁
    创建两个CF对象

    // 创建一个 CFStringRef 对象
    CFStringRef strRef= CFStringCreateWithCString(kCFAllocatorDefault, “hello world", kCFStringEncodingUTF8);
    // 创建一个 CTFontRef 对象
    CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
    

    创建完后 两个对象引用计数器 = 1
    当这两个对象不需要再使用时,那么就需要调用 CFRelease 来使计数器-1

    CFRelease(strRef);
    CFRelease(fontRef);
    
    手动管理计数器

    所以对于底层 Core Foundation 对象,我们只需要延续以前手工管理引用计数的办法即可。
    其实Core 开头的框架 很多有会 retain 和 release 方法, 所以我们一般在使用结束后,将创建出来的core对象release一次.

    除此之外,还有另外一个问题需要解决。在 ARC 下,我们有时需要将一个 Core Foundation 对象转换成一个 Objective-C 对象,这个时候我们需要告诉编译器,转换过程中的引用计数需要做如何的调整。这就引入了bridge相关的关键字,以下是这些关键字的说明:

    • __bridge: 只做类型转换,不修改相关对象的引用计数,原来的 Core Foundation 对象在不用-时,需要调用 CFRelease 方法。
    • __bridge_retained:类型转换后,将相关对象的引用计数加 1,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法。
    • __bridge_transfer:类型转换后,将该对象的引用计数交给 ARC 管理,Core Foundation 对象在不用时,不再需要调用 CFRelease 方法。
      我们根据具体的业务逻辑,合理使用上面的 3 种转换关键字,就可以解决 Core Foundation 对象与 Objective-C 对象相对转换的问题了。

    使用 Xcode 检测循环引用
    在Xcode的的菜单栏中选择 :Product -> Profile,然后选择 “Leaks”,再点击右下角的”Choose” 按钮开始检测。如下图


    Leaks

    这个时候 iOS 模拟器会运行起来,我们在模拟器里进行一些界面的切换操作。稍等几秒钟,就可以看到 Instruments 检测到了我们的这次循环引用。Instruments 中会用一条红色的条来表示一次内存泄漏的产生。


    切换到 Leaks 这栏,点击”Cycles & Roots”,就可以看到以图形方式显示出来的循环引用。这样我们就可以非常方便地找到循环引用的对象了
    代码截图

    如果出现下图的情况 那么说明Profile中的Build Configuration 选的不是Debug


    Profile错误 修改 Profile中的Build Configuration

    然后重启Xcode,重新 Product -> Profile 就可以了,还不行就重启电脑.

    总结

    在ARC的环境下,iOS开发在内存管理方面的工作大部分都不用手动来实现了,但是我认为,我们还是需要去理解引用计数这种内存管理方式,注意循环引用的问题.
    学会使用 Instruments 工具来调试项目

    最后,愿大家共同进步.

    相关文章

      网友评论

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

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