iOS 内存管理

ARC

ARC是 Object-C 编译特性, 不是运行时特性也不是垃圾回收机制, ARC 所做的只是在代码编译自动在合适的地方插入 release 或 autorelese, 只要没有强指针指向对象, 对象就会被释放

  • 前端编译器
    前端编译器会为"拥有的"每一个对象插入相应的release语句. 如果对象的修饰符是 __strong, 那么它就是被拥有的. 如果再某个方法内创建了一个对象, 前端编译器会为他的末尾自动插入 release 语句可以销毁它. 而类拥有的对象会在 dealloc 方法内被释放. 事实上, 你并不需要写 dealloc 方法或者调用父类的方法, ARC 会自动帮你完成一切. 此外, ARC 生成的代码甚至会比你写的 release 语句的性能还要好, 因为编译器可以做出一些假设. 在 ARC 中, 没有类可以覆盖 release方法, 也没有调用它的必要. ARC 会通过直接使用objc_release 来优化调用过程. 而对于 retain 也是同样的方法. ARC 会调用 objc_retain 来取代保留消息.
  • ARC 优化器
    虽然前端编译器听起来很流弊的样子, 但是代码中有事还是会出现对 retain 和 release 的重复调用. ARC 优化器负责移除多余的 retain 和release 语句. 确保生成的代码运行速度高于手动引用计数的代码

测试, 下面关于 Objective-C 内存管理的错误描述是

A. 当使用 ARC 来管理内存时, 代码中不可以出现 autorelease
B. autoreLeasePool 在drain 的时候会释放在其中分配的对象
C. 当使用 ARC 来管理内存的时候, 在线程中大量分配对象而不用 autoreleasepool 则可能会造成内存泄漏
D. 在使用 ARC 的项目中不能使用 NSZone

  • 参考答案: A
  • 理由: ARC 只是在大多时候编译自动为我们添加上内存管理的代码, 只是我们的源代码看不到. 但是在编译的时候会添加相关内存管理代码. 对于自动释放池, 在 drain 时会将自动释放池中所有对象的应用计数减一, 若引用技术为 0, 则会自动释放掉其内存. 如果在线程中需要大量分配内存, 我们理应添加自动释放池, 以防内存泄漏. 比如在 for 循环中要分配大量的内存处理数据. 那么我们应该在 for 循环内添加自动释放池, 在每个循环后释放掉, 防止内存泄漏. 在 ARC 项目中, 自然不能手动使用 NSZone, 也不能调用父类的dealloc

MRC文件在ARC工程混编时, 需要在文件的 Compiler Flages 上添加什么参数

A. -shared B. -fno-objc-arc C. -fobjc-arc D. -dynamic
参考答案: B

什么情况使用 weak 关键字, 相比 assign 有什么不同

什么情况下使用 weak 关键字

  • 在 ARC 中, 在有可能出现循环引用的时候, 玩玩要让其中一端使用 weak 来解决, 比如: delegate 代理属性, blcok 里面的 self.
  • 自身已经对它进行一次强引用, 没有必要再强引用一次, 此时也会使用 weak, 自定义 IBOutlet 控件一般也使用 weak: 当然, 也可以使用 strong.
    weak 和 assign 的不同
  • weak 此特质表明该属性定义了一种"非拥有关系", 为这种属性设置新值时, 设置方法既不保留新值, 也不释放就只. 这个特性和 assign 类似, 然后在属性所指的对象遭到摧毁时, 属性值也会清空 (nil out). 而 assign 的 "设置方法" 只会执行对 "纯量类型" (scalar type, 例如 CGFloat 或 NSInteger 等) 的简单赋值操作
  • assign 可以用在非 OC 对象, weak 只能用在 OC 对象中.

调用对象的 release 方法会销毁对象吗

  • no no no, release 只是应用计数减 1 , 只有应用计数为 0 的时候对调用对象的 dealloc 方法进行释放对象的内存.

下面代码有木有什么问题呀

for (int i = 0; i < someLargeNumber; ++i)
{
NSString *string = @"Abc";
string = [string lowercaseString];
string = [string stringByAppendingString:@"xyz"];
NSLog(@"%@",string);
}

有问题的大兄弟, 每执行一次循环, 就会有一个 string 加到当前的 runloop 中的自动释放池中, 只有当自动释放池被 release 的时候, 自动释放池中的 标示了 autorelease 的这些数据所占用的内存空间才能被释放掉. 假设, 当 someLargeNumber 大到一定的成都的时候, 内存空间呗消耗干净又没有被释放掉, 就会出现内存溢出.

  • 解决办法 1 :如果 i 比较大, 可以用 @autoreleaseppol {} 解决, 放在 for 循环外, 循环结束后, 销毁创建的对象, 解决占据栈区内存的问题
  • 解决方法 2 : 如果 i 不要脸的大, 一次训话你都会造成自动释放池被填满, 自动释放池放在 for 循环里面, 每次循环的时候都让上一次创建的对象 release
    修改之后
for(int i = 0; i<1000;i++) {
  NSAutoreleasePool * pool1 = [[NSAutoreleasePool alloc] init];
  NSString *string = @"Abc";
  string = [string lowercaseString];
  string = [string stringByAppendingString:@"xyz"];
  NSLog(@"%@",string);
  // 释放池
  [pool1 drain];
 }

Object-C 对象的内存布局是怎样的

  • Objective - C 中没有多继承, 因此内存布局还是挺简单的, 也就是: 最前面是 isa 指针, 然后父类的实例变量存放在子类的成员变量之前.

看下面代码, 第一个 NSLog 会输出什么, retainCount 是多少, 第二个和第三个呢, 为什么?

NSMutableArray* ary = [[NSMutableArray array] retain];
NSString *str = [NSString stringWithFormat:@"test"];
[str retain];
[aryaddObject:str];
NSLog(@”%@%d”,str,[str retainCount]);
[str retain];
[str release];
[str release];
NSLog(@”%@%d”,str,[str retainCount]);
[aryremoveAllObjects]
NSLog(@”%@%d”,str,[str retainCount]);
  • 第一个是3, retain + 1, 加入数组自动 + 1, retainCount + 1, 总的为3
  • 第二个是2, retain + 1, release - 1, release - 1. 值为2
  • 最后一个1, 数组删除 -1. 得到结果为1.

看看下面 person 的 retainCount 值

Person * per = [[Person alloc] init];
self.person = per;
  • 第一句代码 person 的 retainCount 的值是 1
  • 在 self.person 时 , 分三种情况
    • assign 修饰, 值不变, 还为 1
    • retain 修饰, + 1, 变为 2
    • copy 修饰, 值不变, 还为1

什么时候需要在程序中创建内存池

  • 用户自己创建的数据线程, 炫耀创建改线程的内存池

我们不创建内存池, 时候有内存池提供给我们.

  • 界面线程维护自己的内存池, 用户自己创建的数据线程, 则需要创建改线程的内存.

苹果怎么实现 autoreleasepool的

autoreleasepool 以一个队列数组的形式实现, 主要通过以下三个函数完成,

  • objc_autoreleasepoolPush
  • objc_autoreleasepoolPop
  • objc_autorelease
    根据函数名可知道, 对 autorelease 分别执行 push, pop 操作. 销毁对象时执行 release 操作.

objc使用什么机子管理对象内存

  • 通过引用计数器的机子来决定对象是否需要释放. 每次 runloop 完成一个循环的时候, 都会检查对象的 retainCount, 如果 retainCount, 如果retainCount 为0, 说明该对象没有地方需要继续使用了, 可以释放掉

为什么要进行内存管理

  • 因为移动设备的内存及其有限, 当一个程序说占内存达到一定数值的时候, 系统会发出内存警告, 当程序达到更大的值的时候, 程序会出现闪退, 影响用户体验. 为了保证程序的运行流畅, 必须进行内存管理.
  • 内存管理的范畴
  • 管理所有继承自 NSObject 的对象, 对基本数据类型无效, 是因为对象和其他数据类型在系统中储存的控件是不一样的, 其他局部变量主要存储在栈区(因为基本类型占用的存储空间是固定的, 一般存放在栈区), 而对象存储在堆中, 当代码块结束的时候, 这个代码块会设计到所有局部变量会自动弹栈清空, 指向对象的指针也会被回收, 这时对象就没有指针指向, 但依然存在于堆内存中, 造成内存泄漏.

objc 使用什么机子管理对象内存(或者内存管理方式有哪些)

  • MRC (manual retain-release)手动内存管理
  • ARC (automatic reference counting) 自动引用计数
  • Garbage collection (垃圾回收). 但是 iOS 不支持垃圾回收, ARC 作为 LLVM 3.0 编译器的一项特性, 在 iOS 5.0 (Xcode-4)版本后推出的.
  • ARC 的判断准则, 只要没有强指针指向对象, 对象就会被释放.

内存管理原则

  • 只要还有人在使用这个对象, 那么这个对象就不会被回收
  • 只有你想使用这个对象, 那么就应该让这个对象的引用计数器加1
  • 当你不想使用这个对象时, 应该让对象的引用计数器减1
  • 谁创建, 就由谁来release
    • 如果你通过alloc, new, copy 来创建一个对象, 当你不想用这个对象的时候就必须调用release 或者autorelease 让引用计数器减1
    • 不是你创建的就不用你负责 release
      谁retain 谁release
  • 只要你调用了retain ,无论这个对象如何生成, 都需要调用release
    总结:
  • 有加就应该有减, 曾让某个计数器加1, 就应该让其在最后减1

内存管理研究对象

  • 野指针: 指针变量没有进行初始化或者指向的空间已经被释放了
    • 使用野指针调用对象方法, 会报异常, 程序崩溃
    • 通常再调用玩 release 方法后, 把 保存对象指针的地址清空, 赋值为 nil, 找 oc 中没有空指针异常, 所以[nil retain] 调用方法不会有异常
  • 内存泄漏
    • 如 Person *person = [Person new]; (对象提前赋值nil或者清空)在栈区的person已经被释放, 而堆区new 产生的对象还没有释放, 就会造成内存泄漏.
    • 僵尸对象: 堆中已经被释放的对象(retainCount = 0)
  • 空指针: 指针赋值为空, nil

未完待续

你可能感兴趣的:(iOS 内存管理)