美文网首页性能优化技术iOS
先弄清楚这里的学问,再来谈 iOS 内存管理与优化(二)

先弄清楚这里的学问,再来谈 iOS 内存管理与优化(二)

作者: Joy___ | 来源:发表于2016-02-26 19:10 被阅读7055次

    上篇文章讲述了iOS内存管理的基本概念,这里是一些内存优化的小技巧

    Strong Weak Dance

    经常使用闭包的话,应该会特别注意这一条吧。

    先将强引用的对象转为弱引用指针,防止了Block和对象之间的循环引用。再在Block的中,将weakSelf的弱引用转换成strongSelf这样的强引用指针,防止了多线程和ARC环境下弱引用随时被释放的问题

    __weak MyViewController *wself = self;
    self.completionHandler = ^(NSInteger result) {
        __strong __typeof(wself) sself = wself; // 强引用一次
        [sself.property removeObserver: sself forKeyPath:@"pathName"];
    };
    

    降低内存峰值

    Lazy Allocation

    延时加载是很常用的一种优化方法,如果有些情况我们不会立即使用某一对象和某些资源,我们完全可以在使用的时候再进行加载,这些就可以避免初次运行程序的时候内存消耗严重。

    lazy var goodsImageView: UIImageView = {
            let goodsImageView = UIImageView()
            return goodsImageView
        }()
    

    喵神的博客中,也发现了很好玩的地方,Swift标准库中,定义了一些Lazy方法,可以配合像 map或是 filter 这类接受闭包并进行运行的方法一起,让整个行为变成延时进行。

    let data = 1...3
    let result = data.lazy.map {
        (i: Int) -> Int in
        print("正在处理 \(i)")
        return i * 2
    }
    
    print("准备访问结果")
    for i in result {
        print("操作后结果为 \(i)")
    }
    
    print("操作完毕")
    

    运行结果为:

    // 准备访问结果
    // 正在处理 1
    // 操作后结果为 2
    // 正在处理 2
    // 操作后结果为 4
    // 正在处理 3
    // 操作后结果为 6
    // 操作完毕
    

    图片的读取

    相信写代码的时候都会涉及到图片的读取,我们经常使用的方法

    imageView?.image = UIImage(named: name)
    imageView?.image = UIImage(contentsOfFile: path)
    
    • 对于第一种,是带缓存机制的,如果频繁读取小文件,用它就只需要读取一次就好,但是缺点就是如果使用大图片会常驻内存,对于降低内存峰值是不利的。
    • 对于第二种方法,不带缓存机制,适合使用大图片,使用完就释放

    NSData & 内存映射文件

    在我们经常使用NSData时,出镜率很高的两个方法

    public init?(contentsOfFile path: String)
    public init(contentsOfFile path: String, options readOptionsMask: NSDataReadingOptions) throws
    

    第二种比第一个多一个Options,第二种方式是创建了一个内存映射文件,把内容放在虚拟内存中,只有读取操作的时候才会读到相对应页的物理内存页中。所以后者,对于大文件是很划算的,推荐使用第二种。对于可选的方式如下:

    映射

    其他方式

    庄延军老师的分享中,还有其他一些技巧,但是对于只懂Swift的我,实在没接触过哪些方案,等我接触之后,再来写第三个分享。下面简单列举一下:

    • calloc VS malloc + memset
    • 栈内存分配

    NSAutoReleasePool

    MRC时代说起,当需要我们手动来管理对象引用计数的时候,我们需要RetainRelease方法,那么可能一个线程中有大量的RetainRelease方法,这个时候使用一个池一起管理他们是不是方便很多,将线程中要执行的任务都放在自动释放池中,自动释放池会捕获所有任务中的对象,在任务结束或线程关闭之时自动释放这些对象。

    当然自动释放池都会使用NSAutoreleasePool类创建,并在自动释放池收到 drain消息时将这些对象的引用计数减一,然后将它们从池子中移除 (这一过程形象地称为“抽干池子”)。

    ARC时代,不再使用NSAutoreleasePool来创建自动释放池,而是使用@autoreleasepool代码块,新建Objective-C工程的时候,会帮我们创建一个main.m文件,已经帮我们在主线程创建了一个自动释放池。

    int main(int argc, char * argv[]) {
        @autoreleasepool {
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    

    更进一步,其实@autoreleasepool 在编译时会被展开为 NSAutoreleasePool,并且在每个主Runloop结束时进行drain操作。

    那么到了Swift时代,又具有了新的变化,不需要手动地调用autorelease 这样的方法来管理引用计数,但是这些方法还是都会被调用的,只不过是编译器在编译时在合适的地方帮我们加入了而已。

    在Swift标准库中可以发现:

    public func autoreleasepool(@noescape code: () -> ())
    

    那么我们就可以利用闭包的特点,在需要的时候非常简便的使用它

    autoreleasepool { 
        // 线程执行任务的逻辑代码
    }
    

    AutoReleasePool

    为什么在ARC时代还需要使用自动释放池呢?其原因就是为了避免内存峰值,那比如说我有一个很大的For循环,里面不断生成autorelease
    对象(比如:dataWithContentsOfFile返回的是 autorelease 的对象)。其实每迭代一次,资源都已经用完了(就是说我用好了,还你),不需要再用了,这个时候就可以释放了,但是程序需要等到Runloop结束的时候才可以释放,这就增大了内存的峰值。

    上面的话有点绕,但是真心非常重要。

    那么怎么做,可以减小内存峰值呢?我们可以在For循环中添加autoreleasepool,这样就可以保证每次迭代完毕一次就可以释放点内存。

    func loadBigData() {
        for i in 1...10000 {
            autoreleasepool {
                let data = NSData.dataWithContentsOfFile(
                    path, options: nil, error: nil)
            }
        }
    }
    

    简单总结

    上面的阐述了多种方法,其中对于降低内存峰值有作用的是:

    • Lazy Allocation
    • 图片的读取的正确方式
    • NSData & 内存映射文件
    • callocVS malloc + memset
    • 栈内存分配
    • autoReleasePool

    内存警告处理

    到收到内存警告的时候,所要做的:

    • 尽可能多的释放资源,尤其是图片等占用内存多的资源,等需要的时候再进行重建
    • 单例模式的滥用,会导致单例对象一直持有资源,在内存紧张的时候要进行释放,当然我也在其他博客中看到一些单例模式的替换方案

    相关文章

      网友评论

      • NSBug:👍
      • HelloiWorld:我现在经常会崩的地方就是播放本地GIF图,后台给的图片太大,在4s设备频繁,由于项目里面GIF比较多,其他设备也会出现崩溃的情况
        柴小斌:@HelloiWorld 毕竟4S
        HelloiWorld:@Joy___ 大神,这也是我想解决的。我现在也只是少使用单例,常量用const多于define,懒加载和重用,大图压缩展示,弹出式的视图使用完置nil等,感觉很常规。用久了该崩还是崩,一点面子都不给呀:joy:
        Joy___:@HelloiWorld 对于低内存崩溃有什么招数可以分享么
      • 8e78b5b03b17:謝謝分享,學習了!
      • happycolt:谢谢分享,很好的总结!
      • 爱上我们的微笑:真心感觉swift好* ,哎,抗拒
        Joy___:@爱上我们的微笑 哈哈
      • dibadalu:好文,之前看视频时有很多不理解。现在看了你的文章,很多东西都明白了。等你再出好文。 :+1:
        Joy___:谢谢
      • luodezhao:RunLoop在每个event完成的时候,才会去释放autoreleasePool,所以在for 循环里面加个pool是否真的能降低内存峰值。
        Joy___:@luodezhao 简单来说:如果我们不手动添加 可以理解为由main入口的pool释放,那么我们加了手动pool时,出pool作用域就会释放
        Joy___:@luodezhao 喵神针对此问题做了详细的描述和实验http://swifter.tips/autoreleasepool/ 你可以参考一下
        luodezhao:能否及时的降低内存峰值
      • HenryCheng:自动释放池这个好像有很多不知道,之前有好多人让我看他们app我都发现比如一次上传多张图片,还有就是下拉刷新帧动画之类的循环,都会带来很多的内存问题,写的不错,赞一个:smile::smile::smile:
        HenryCheng:下次来点图形处理之类的:heart_eyes::heart_eyes::heart_eyes:
        Joy___:@HenryCheng :innocent:
      • ChrisPzzz:学习了 最近开始用swift与oc混编
      • 孙沪stupid:内容很有深度,真是个好人啊
        Joy___:@stupidwho 咋还扯到好人去了😂

      本文标题:先弄清楚这里的学问,再来谈 iOS 内存管理与优化(二)

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