iOS App开发 OC 第三天, 记忆体管理

tags: OC 30 day

原文位置网址

Objective-C里的记忆体管理主要有两种:

  1. Garbage Collection(缩写为GC)
  2. Reference Counting(缩写为RC)

以车子来举例的话,你可以先暂时把这两种记忆体管理方式想像成GC是自排车,RC是手排车,一个会自动帮忙回收没要用的记忆体,一个则是自己得手动释放用过不要再用的记忆体。

RC的运作机制

RC的运作机制是当某个物件生成并初始化之後,它的初始retain count会设定成1(其实不一定,也是有例外)。执行该物件的”retain”方法会让该物件的retain count加1,而release方法会让retain count减1。当该物件的retain count降到0的时候,这个物件自动会呼叫dealloc方法把自己解决掉,然後把占用的记忆体还回来。

也许你会觉得,都什麽年代了为什麽还得程序设计师自己用手动的方式来回收记忆体? 换个角度想,程序设计师为自己写的程序负责也是件好事,另外,也是最主要的原因就是有些环境就是根本不支援GC机制,所以只好用RC来处理。我相信想学Objective-C很多人都是为了想要开发iPhone App而来的,而iPhone正是那个不支援GC环境的其中之一。

又或许你会觉得这样一颗小物件是能占多少记忆体。这种东西积沙成塔的,你借了记忆体来用却没还回去,久了可能就会造成”漏水”(memory leaking)的情况。为了避免App在执行的过程中莫名奇妙的地方当掉,只好乖乖的来了解一下关於记忆体管理的机制。

You only release or autorelease objects you own.

Mac OS X Developer Library

Memory Management Policy

什麽是你拥用的物件? 当你用alloc、new或是copy开头的方法建立一个物件的话,程序就会向系统要一块记忆体来放这颗物件,而这颗物件就算在你头上。另外当你用对某个物件使用retain方法之後,那颗物件也算是你要负责的;一个物件可以同时有好几个主人,而当那个物件你不要用的时候,则使用release或是autorelease方法来把这个拥有的关系给断绝掉,准备把物件清掉并把占用的记忆体还给系统。直接来看段范例:

int main (int argc, const char * argv[])
{
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  NSNumber *n = [[NSNumber alloc] initWithInt:100];

  NSLog(@"the retain count is %d", [n retainCount]);

  [n retain];

  NSLog(@"the retain count is %d", [n retainCount]);

  [n release];

  NSLog(@"the retain count is %d", [n retainCount]);

  [n release];

  [pool drain];
  return 0;
}

输出结果:

the retain count is 1
the retain count is 2
the retain count is 1

除了retain/release之外,如果被collections,例如array、dictionary或set等等给拉进去的话,它的retain count也会加1;相对的,从Collection里拿出来的话,它的retain count会减1。

int main (int argc, const char * argv[])
{
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  NSMutableArray *array = [[NSMutableArray alloc] init];
  NSNumber *n = [[NSNumber alloc] initWithInt:100];

  NSLog(@"the retain count of n is %d", [n retainCount]);

  [array addObject:n];

  NSLog(@"the retain count of n is %d", [n retainCount]);

  [n release];

  NSLog(@"the retain count of n is %d", [n retainCount]);

  [array release];

  [pool drain];
  return 0;
}

输出结果:

the retain count of n is 1
the retain count of n is 2
the retain count of n is 1

在这个例子里可以看到,当使用addObject方法把n放进array之後,它的retain count会加1;当使用removeObjectAtIndex把物件从array移出来的时候,它的retain count会减1。最後,当array本身收到release的时候,它会对目前全部的内容物发送release讯息。所以在上面的例子来说,如果在[array release]之後再想存取n变数,就会出现错误讯息。

记得,你retain了一个物件,确定没要再用之後就release掉。retain跟release的次数通常是成对的,你手动retain了几次,到时候就得手动release几次。

有一些常见可能会发生问题的写法:

- (void)reset
{
  NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
  [self setCount:zero];
}

这里用alloc建立物件,却没有对等的release,可能造成memory leak。

- (void)reset
{
  NSNumber *zero = [NSNumber numberWithInteger:0];
  [self setCount:zero];
  [zero release];
}

在这里numberWithIngeter产生的是一个autorelease物件,所以这并不属於你拥有的,当你对它执行release它可能就会因为retain count变成0而被清掉,在autorelease pool里因为会再对所有标记为autorelease的物件再送一次release讯息,这时候就会出错了。请记忆体释放的原则:「You only release or autorelease objects you own.」,如果物件不属於你,就不要随便对它执行release方法。

关於Autorelease pool,你可能会在一些刚建立好的专案里看到它帮写好几行程序码了,autorelease pool并不是真正的GC机制,它比较像是GC的替代品。简单的说,当你对物件执行autorelease方法时,就是把该物件标记成”待会再释放”的物件,在每个run loop或是pool drain的时候就会对这些有标记的所有物件发送release讯息。虽说是替代品,但有些地方还是非用不可。

结论:

在你跟系统要了一块记忆体来用的当下,请先养成「我什麽时候会还回去」的习惯。
retain/release通常都是成对的,手动做了N次的retain,就记得要做N次的release。
如果你要某个物件,就retain它;如果不用了,就release它,把记忆体还回去。


<<:  资讯治理(Data Governance)

>>:  Leetcode/AlgoExpert 解题笔记 – Array 篇 (1)

【Side Project】 开发工具及开发环境

我: 客户要的网站我已经开发好,可以架上去了。 同事: 好,谢谢。 (过几天後...) 同事: 客...

Day42. 范例:仿真Git (备忘录模式)

本文同步更新於blog 情境:让我们利用备忘录模式,实作一个仿真Git 首先定义Commit &...

#15. CSS Perspective Slider(Vue版)

#15. CSS Perspective Slider 今天挑战的任务算是我蛮喜欢的一个小proje...

[Q&A] 04 专案必要文件难产

资讯安全管理制度运行会产出一系列的文件化纪录,着也是稽核程序中可能会发现的不合理之处。 其中,在风险...

Day 30 : 综合整理 MLOps Level 0 ~ 2

MLOps 是值得持续投入的新兴学门,如同 Day 01 谈到的此系列目的,谈如何从布署机械学习至...