iOS 内存管理

作者: 朝雨晚风 | 来源:发表于2016-07-18 18:10 被阅读129次

iOS的操作系统运行的简单原理

当前的主流iPhone实际物理内存一般都不超过1G,要高效利用的这1G空间,达到高性能,ios是怎么做的嗯?iOS的操作系统更是抛弃了不必要的复杂——系统层面不支持App内存页换出,当内存吃紧时,对于可以重新载入的只读数据来说,直接清理掉,而对于可写的数据,只能通过App自己去管理维护。内存紧张时,iOS会向App发起memory warning,不配合释放足够内存者,kill掉!

进程的内存结构

最简单来说分为两大部分:指令+数据。再细分一点,五部分:代码(指令),初始化数据区,未初始化数据区,堆,栈。
** 主要的布局如下图所示:**

内存结构.png

** 从代码上来分析:**

//main.cpp 
int a = 0; 全局初始化区 
char *p1; 全局未初始化区 
main() 
{ 
int b; 栈 
char s[] = "abc"; 栈 
char *p2; 栈 
char *p3 = "123456"; 123456\0在常量区,p3在栈上。 
static int c =0; 全局(静态)初始化区 
p1 = (char *)malloc(10); 
p2 = (char *)malloc(20);  分配得来得10和20字节的区域就在堆区。 
strcpy(p1, "123456"); 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。 
} 
  • 栈区(stack):由编译器自动分配和释放,存放 函数的参数值、局部变量值等,栈是向低地址扩展的数据结构,是一块连续的内存区域。即栈顶的地址和栈的最大容量是系统预先设定好的。

  • 堆(heap):一般由程序员分配和释放、若程序员不释放,程序结束后由OS回收。堆是不连续的内存区域,向高地址扩展的数据结构。

  • 区别:堆只有动态分配的,栈有静态分配(编译器完成,比如局部变量)和动态分配的(alloc函数,编译器来释放);栈的分配效率比堆高,但堆灵活和内存占用大,由于堆空间的不连续性,会存在碎片问题。

  • 全局区和静态区:全局变量和静态变量是放在一块的,内部划分初始化和未初始化两个块区。程序结束后由系统释放。

  • 文字常量区:常量字符串放在这里的,程序结束后由系统释放

  • 程序代码区:存放函数体的二进制代码

iOS的内存管理机制

** MRC(iOS 5.0 之前)**

MRC,手动引用计数, 手动管理内存的三个原则

  1. 谁创建,谁释放,如果你通过alloc new copy来创建一个对象,那么你必须调用release或者这autorrelease。换句话说,,不是你创建的,你就不用去释放。

  2. 除了alloc、new、copy之外的方法创建的对象都被声明了autorelease。

  3. 谁retain,谁release。只要你调用了retain,无论这个对象是如何生成的,你都要调用release

** ARC(iOS 5.0 之后)**

自 iOS5.0 之后,苹果开始支持ARC(Automatic Reference Counting)自动引用计数,是一个编译器特性,编译器自动的添加完成了内存管理的引用计数代码。arc只针对OC对象类型,OC类型:任何继承于NSObject对象都属于OC的类型。它对基本数据类型不管用。

** @property属性 **

OC中@property属性关键字的使用(assign/weak/strong/copy) ,property的属性默认是:readwrite、strong、 atomic(没有这个关键字)

  • assign
    用于 ‘基本数据类型’、‘枚举’、‘结构体’ 等非OC对象类型
    eg:int、bool ,struct,enum等

  • copy
    一般用在NSString*类型、block类型上

  • strong
    OC对象类型(NSArray、NSDate、NSNumber、模型类)
    一个对象只要有强指针引用着,就不会被销毁
  • weak
    一般应用: IBOutlet UI控件、delegate代理(防止循环引用)
    weak型的指针变量指向的对象不存在时,会自动置为nil。

注: 如果还能打印出指针的地址,说明指针并不会马上销毁,那么什么时候回销毁恩?
在每一个运行循环(runloop)的开始,系统会自动创建一个自动释放池autoreleasepoo;在每一个运行循环的结尾,系统会自动销毁这个自动释放池。
当一个自动释放池自身被销毁(dealloc)时,它会给池中每一个对象发送一个release消息,所以进行下一轮消息循环(runLoop)之后才会发现这个指针被销毁了。

注意:使用arc机制后代码中不能再出现这些:

  1. 代码中不能使用retain,release,autorelease
  2. 不重载dealloc(可以重写覆盖,不能调用)
  3. 不能使用NSAutoReleasePool、而需要@autoreleasepool块

IOS 内存泄漏及预防

在使用ARC的时候,可以在一定程度上简化我们的编程操作,但是在使用的过程中也会出现内存泄露的问题

  • delegate循环引用造成的内存泄露
    把delegate引用声明成了strong
@property (nonatomic, strong) id delegate;

解决办法:改成弱引用

@property (nonatomic, weak) id delegate; 或者  @property (nonatomic, assign) id delegate;
  • block的循环引用
    Block 循环引用也很常见,假设 self 代表当前 ViewController,那么一般写法是这样
  __weak typeof(self)weakSelf = self;
  self.completionHandler = ^{
    NSLog(@"%@", weakSelf);
};
  • NSTimer
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;

如果在一个 ViewController 里创建了一个定时器,并且 repeats:值为 YES,一定记得在 pop/dismiss 当前 ViewController 将 timer 设为 invalidate,否则 会造成循环引用,这里要特别需要 注意 的一点是:我们不要在 ViewController 的 dealloc 方法里调用 [timer invalidate]; 因为从来不会调到这里,我们应该在 viewWillDisappear里边调用,像这样:

- (void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    if ([self isMovingFromParentViewController]) {
        [timer invalidate]; //解除对self的引用
    }
}
  • CoreFundation/CoreGraphics 相关函数
    ARC 针对的 Objective-C 对象来说的,而 CoreFundation/CoreGraphics 这种非Objective-C对象,我们一定要记得自己手动释放,否则会造成内存泄漏
- (void)display {
   dispatch_async(backgroundQueue, ^{
       CGContextRef ctx = CGBitmapContextCreate(...);
       // draw in context...
       CGImageRef img = CGBitmapContextCreateImage(ctx);
       CFRelease(ctx);
       dispatch_async(mainQueue, ^{
           layer.contents = img;
       });
   });
}

只要记住一点:CG/CF 开头的函数,有创建就要有释放,这样可以有效降低内存泄漏的风险。

  • 基本数据类型申请的内存空间需要手动free
    uint8_t *buffer=malloc(1024)
    free(buffer)

如何检测内存泄漏

开发应用程序,内存泄漏不可避免,那么如何检测内存泄漏呢,那就要说道 Xcode工具套件中的 Instruments。针对 内存泄漏,主要是两个 Allocations 和 Leaks

这里有篇教程写的比较详细:http://www.raywenderlich.com/23037/how-to-use-instruments-in-xcode
我建议大家直接看英文原版吧,也不难。这里主要说明几点:

教程中用的 Instruments 界面跟目前最新的略微有些差别,比如 里边 提到的处于左边的 Mark Heap 按钮,目前改为了 Mark Generation ,在这里:


eded

对于 循环引用 如何进行检测
简单点可以通过在对应的类中复写 dealloc 方法,像这样:

- (void)dealloc{
    NSLog(@"=== %@ dealloced! ===", NSStringFromClass([self class]));
}

如果调用这里,说明不存在循环引用。

同样,我们也可以通过 Instruments 来检测,利用 Allocations ,如图:


eeee

这里以 ViewController 为例,在搜索框中输入想要查看的类名,然后连续进行 pop/push 操作,看 #Total 那一列的个数有没有连续增加,如果连续增加,那么说明出现循环引用了,注意:此方法只适用于真机。

相关文章

网友评论

    本文标题:iOS 内存管理

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