有这么一道iOS面试题 以下代码有没有什么问题?如果有?如何解决?

for (int i = 0; i < largeNumber; i++) {
NSString *str = [NSString stringWithFormat:@"hello -%04d", i];
str = [str stringByAppendingString:@" - world"];
}

局部释放池和RunLoop释放池的概念:

主线程的RunLoop是默认开启的(视图用[[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]]来停止它,也是做不到的), 每一次消息循环开始的时候会先创建自动释放池,这次循环结束前,会释放自动释放池,然后RunLoop等待下次事件源。 在这个过程中,由RunLoop创建的释放池类似于一个全局的释放池。但是开发者可以任何执行的地方创建释放池,也就是局部的释放池,这时的释放池类似于代码块 当释放池结束的时候会自动释放。因此一般情况下,局部的自动释放池很快就被释放了,而RunLoop释放池会等一次消息循环结束的时候释放。

什么样的对象会交给释放池管理:

返回当前类的实例的类方法创建出来的对象,都是autorelease的,会交给所在的释放池进行管理。 例如创建一个Person类,使用[[self alloc]init]方法创建的对象的管理不会交给它所在的释放池,而是根据引用计数来控制释放的时机, 如果使用[[[self alloc]init] autorelease]创建的对象,会交给所在的释放池管理,控制其释放的时机。

- (void)test{
@autoreleasepool {
Person *p = [[Person alloc] init];
p = nil;
NSLog(@"---");
}
NSLog(@"autorelease结束");
}

执行结果:

Person---dealloc
---
autorelease结束
- (void)test1{
@autoreleasepool {
Person *p = [Person person]; // 内部是[[[self alloc] init] autorelease]
p = nil;
NSLog(@"---");
}
NSLog(@"autorelease结束");
}

执行的结果为:

---
Person---dealloc
autorelease结束

因此自动释放池被销毁或耗尽时会向池中所有使用autorelease创建的对象发送release 消息,释放所有autorelease的对象,而不是所有的对象。

回到面试的问题:

当我们使用for循环创建很多个使用autorelease方式创建的NSString对象的时候,将所有的对象的释放权都交给了RunLoop 的释放池,而RunLoop的释放池会等待这个事件处理之后才会释放,因此就会使对象无法及时释放,堆积在内存造成内存泄露,可以在Debug Navigation 中观察到内存激增。为了验证确实是因为autorelease这种创建方式引起的内存泄露,我做了如下的测试:

int largeNumber = 100000000;
- (void)correctSolution1{
for (int i = 0; i < largeNumber; i++) {
NSString *str = [[NSString alloc] initWithFormat:@"hello -%04d", i];
str = [str stringByAppendingString:@" - world"];
}
}
// stringWithFormat:本质上是调用了alloc + initWithFormat: + autorelease
// 我在本例中将stringWithFormat:方法换成了alloc + initWithFormat:
// 这样做问题就解决了:内存几乎没有变化。反向验证了内存飙升确实是autorelease创建方式造成的。

但是在编写代码的时候我们仍然习惯用类的快速创建方法,而不是alloc+init。也就是说,为了方便写程序,又使用了底层实现是alloc+init+autorelease的快速创建对象的方法(如 stringWithFormat:)。因此解决的方案就是添加局部的释放池,以及时释放内存,如果将局部释放池添加到循环外:

- (void)wrongSolution{
@autoreleasepool {
for (int i = 0; i < largeNumber; i++) {
NSString *str = [NSString stringWithFormat:@"hello -%04d", i];
str = [str stringByAppendingString:@" - world"];
}
}
}

这样显然是没有效果的,释放池需要等循环执行之后再释放内存,这和使用RunLoop创建的释放池没有什么区别。 较好的方案就是每次循环的时候添加一个释放池:

- (void)correctSolution2{
for (int i = 0; i < largeNumber; i++) {
@autoreleasepool {
NSString *str = [NSString stringWithFormat:@"hello -%04d", i];
str = [str stringByAppendingString:@" - world"];
}
}
}

这样每一次循环的结束时都会释放一次内存,因而这个循环全部执行完成时也几乎不消耗内存。

总结是:

做多线程开发时,需要在线程调度方法中手动添加自动释放池,尤其是当执行循环的时候,如果循环内部有使用类的快速创建方法创建的对象, 一定要将循环体放到自动释放池中。

RunLoop和autorelease的一道面试题的更多相关文章

  1. PHP递归创建多级目录(一道面试题的解题过程)

    今天看到一道面试题,要写出一个可以创建多级目录的函数: 我的第一个感觉就是用递归创建,具体思路如下: function Directory($dir){ if(is_dir($dir) || @mkd ...

  2. 关于Java类加载双亲委派机制的思考(附一道面试题)

    预定义类加载器和双亲委派机制 JVM预定义的三种类型类加载器: 启动(Bootstrap)类加载器:是用本地代码实现的类装入器,它负责将 <Java_Runtime_Home>/lib下面 ...

  3. 一道面试题比较synchronized和读写锁

    一.科普定义 这篇博文的两个主角“synchronized”和“读写锁” 1)synchronized 这个同步关键字相信大家都用得比较多,在上一篇“多个线程之间共享数据的方式”中也详细列举他的应用, ...

  4. 一道面试题:按照其描述要求用java语言实现快速排序

    回来想了想,写出了如下的程序: /** * 一道面试题,按照其描述要求进行快速排序(英文的,希望理解是对的..) * 要求:和一般的快速排序算法不同的是,它不是依次交换pivot和左右元素节点(交换2 ...

  5. <转>一道面试题比较synchronized和读写锁

    一.科普定义(原文:http://903497571.iteye.com/blog/1874752) 这篇博文的两个主角“synchronized”和“读写锁” 1)synchronized 这个同步 ...

  6. 一道面试题与Java位操作 和 BitSet 库的使用

    前一段时间在网上看到这样一道面试题: 有个老的手机短信程序,由于当时的手机CPU,内存都很烂.所以这个短信程序只能记住256条短信,多了就删了. 每个短信有个唯一的ID,在0到255之间.当然用户可能 ...

  7. Java中有关构造函数的一道笔试题解析

    Java中有关构造函数的一道笔试题解析 1.详细题目例如以下 下列说法正确的有() A. class中的constructor不可省略 B. constructor必须与class同名,但方法不能与c ...

  8. 【C#基础知识】静态构造函数,来源于一道面试题的理解

    看到园友的一道面试题,很好奇,测试了一下结果. public class A { public static int X=B.Y ; public A() { ++X; } } public clas ...

  9. 一道笔试题来理顺Java中的值传递和引用传递

      题目如下: private static void change(StringBuffer str11, StringBuffer str12) { str12 = str11; str11 = ...

随机推荐

  1. sql中的!=判断的注意事项

    sql查询中where过滤条件为某字段 colName='xx'时一般不会出什么问题, 但如果想达到不为xx的时候就要注意了,用colName!= 'xx'可能就有问题了,因为该字段可能为空,为nul ...

  2. JSP内置对象之application对象

    虽然常把Web应用称为B/S架构的应用,但其实Web应用一样是C/S结构的应用,只是这种应用的服务器是Web服务器,而客户端是浏览器. 现在抛开Web应用直接看Web服务器和浏览器. Web服务器负责 ...

  3. mysql登录报错“Access denied for user 'root'@'localhost' (using password: YES”的处理方法

    使用/etc/mysql/debian.cnf文件中[client]节提供的用户名和密码: 文件内容: [client]host = localhostuser = debian-sys-maint ...

  4. soj杂题

    unique()函数是一个去重函数,STL中unique的函数 unique的功能是去除相邻的重复元素(只保留一个),还有一个容易忽视的特性是它并不真正把重复的元素删除.他是c++中的函数,所以头文件 ...

  5. 利用session_set_save_handler()函数将session保存到MySQL数据库中

    PHP保存session默认的是采用的文件的方式来保存的,这仅仅在文件的空间开销很小的windows上是可以采用的,但是如果我们采用uinx或者是liux上的文件系统的时候,这样的文件系统的文件空间开 ...

  6. shell--2.shell数组

    shell 数组 (1)定义数组 shell中,用括号表示数组,数组元素用空格分开,定义数组的一般形式 arrt_name=(val1 val2 val3) 或者 arry_name=(val1 va ...

  7. win7使用自带资源管理器来登陆FTP

    使用windows自带的资源管理器登陆FTP,其实很简单,打开计算机,在地址栏直接输入ftp://ftp.hostname即可. 但是这种方式需要每次输入ip地址,还要输入用户名和密码,比较麻烦.可能 ...

  8. linux 使用 nvidia 的 gpu

    第一种方法: [Wizard@Wizard ~]$ nvidia-detect kmod-nvidiaOptimus hardware detected: An Intel display contr ...

  9. 关于XSS(跨站脚本攻击)和CSRF(跨站请求伪造)

    我们常说的网络安全其实应该包括以下三方面的安全: 1.机密性,比如用户的隐私被窃取,帐号被盗,常见的方式是木马. 2.完整性,比如数据的完整,举个例子,康熙传位十四子,被当时四阿哥篡改遗诏:传位于四子 ...

  10. Ruby基本类型

    #!/usr/bin/ruby =begin Ruby支持的有5种类型的变量 全局变量:以$开头 未初始化的全局变量的值为0 并使用-w选项产生警告 全局变量的赋值会改变全局状态 不推荐使用全局变量  ...