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

上篇文章讲述了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

内存警告处理

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

  • 尽可能多的释放资源,尤其是图片等占用内存多的资源,等需要的时候再进行重建
  • 单例模式的滥用,会导致单例对象一直持有资源,在内存紧张的时候要进行释放,当然我也在其他博客中看到一些单例模式的替换方案
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 29.理解引用计数 Objective-C语言使用引用计数来管理内存,也就是说,每个对象都有个可以递增或递减的计数...
    Code_Ninja阅读 1,470评论 1 3
  • 前言 现在iOS开发已经是arc甚至是swift的时代,但是内存管理仍是一个重点关注的问题,如果只知盲目开发而不知...
    明仔Su阅读 26,528评论 16 175
  • 内存管理 简述OC中内存管理机制。与retain配对使用的方法是dealloc还是release,为什么?需要与a...
    丶逐渐阅读 1,948评论 1 16
  • 现在iOS开发已经是ARC甚至是swift的时代,但是内存管理仍是一个重点关注的问题,如果只知盲目开发而不知个中原...
    码代码的小马阅读 559评论 0 1
  • 1.1 什么是自动引用计数 概念:在 LLVM 编译器中设置 ARC(Automaitc Reference Co...
    __silhouette阅读 5,080评论 1 17