我是程序员;您好程先生;叫我序员就好了程序员首页推荐

iOS内存管理理解

2018-04-26  本文已影响5人  Jadyn_Wu

在Build Phases -> Compile Sources -> 对应的文件加上-fno-objc-arc的编译参数可以启用MRC模式

1.简介

1.1 内存管理的思考方式

1.2 ARC下的内存管理

ARC虽然能够解决90%的内存管理问题,另外还有10%的需要开发者自己处理

  1. 过度使用block,无法解决循环引用问题
  2. 遇到底层Core Foundation对象,需要自己手动管理它们的引用计数时

2.引用计数(Reference Count)

2.1 什么是引用计数

引用计数是一种管理对象生命周期的方式,一个内存块被强指针指向的数量。

2.2 alloc/retain/release/dealloc

cocoa框架中foundation框架类库的NSObject类是担负着内存管理的

指向一个内存块的每一条指针,都能使用retain和release对这个内存块的引用计数进行改变。

//开辟内存块,引用计数为1
id object1 = [[NSObject alloc] init];
//object2的指针指向object1创建的内存块,内存块的引用计数还是1
id object2 = object1;
//object2使用retain是引用计数+1
[object2 retain];
//object1使用release将内存块上的引用计数-1
[object1 release];
//object1还是可以使用release将内存块的引用计数再-1
//此时内存块上的引用计数值为0,内存空间被系统回收
//tips:系统知道马上就要回收内存了,没必要-1了,直接回收对象
[object1 release];

如果想要销毁一个对象,不仅需要使用release将引用计数-1,还需要将对象的指针置为nil

id object1 = [[NSObject alloc] init];
[object1 release];
object1 = nil;

2.3 引用计数的实现

GNUstep中的实现:采用在内存块头部放置引用计数来进行管理

  1. 分配存放对象所需要的内存空间,将该内存空间置0
  2. 用一个整数来记录retain的引用计数值,并把这个整数写到内存空间的头部
  3. 返回对象指针

GNUstep优点:

  1. 少量代码就能完成
  2. 能够统一管理引用计数用的内存块和对象用内存块

苹果源码中的实现:采用引用技术表(散列表)管理引用计数。

  1. 分配存放对象所需要的内存空间,将空间置0
  2. 在散列表中加入引用计数并附带对象的内存块地址
  3. 返回对象指针

苹果源码优点:

  1. 对象用内存块分配无需考虑内存块头部
  2. 引用技术表各记录中存有内存块地址,可从各个记录追溯到各个内存块地址,在调试时,只要引用计数表没有被破坏,就可以确认内存块位置,就可以检测各对象持有者是否存在。

2.4 别向已经释放的对象发消息

当一个对象引用计数为0时,系统已经将该对象的地址回收,它的输出结果是不一定的,如果被回收的地址为空,则会输出0,如果地址被内存复用了,就会造成系统崩溃。

NSObject *object = [[NSObject alloc] init];
NSLog(@"Reference Count = %u", [object retainCount]);
[object release];
//此时object的内存被释放了,输入为0或者崩溃
NSLog(@"Reference Count = %u", [object retainCount]);

3.循环引用(Reference Cycle)

如果对象A的成员变量是对象B,对象B的成员变量是A,释放对象A需要先释放成员变量B,而释放成员变量B也需要先释放成员变量A,形成一个无法释放的循环,造成内存泄漏,这就是循环引用。

- (void)viewDidLoad {
    [super viewDidLoad];
    CustomView *view = [[CustomView alloc] init];
    [self.view addSubview:view];
    view.callbackBlock = ^{
        //view持有的block中调用了self,意味着view成员变量的一根指针指向了self
        [self doSomething];
    }
}

当 viewDidLoad 方法执行时,创建一个 block 并赋值给对象 view 的 callbackBlock 属性,callbackBlock捕捉 self,self 持有 self.view, v 在 addSubview 后成为 self.view 的子 view 而被 self.view 持有,这样就形成了一个引用循环,self -> self.view -> view -> callBackBlock -> self。

环路越是大的循环引用,越难以被发现。

打破循环方法1:主动断开循环引用

根据业务逻辑主动断开引用,在view 执行完callbackBlock后,将block置为nil,主动断开循环引用。

if (self.callbackBlock) {
    self.callbackBlock();
    //调用方将指向callbackBlock的指向指向了nil,主动释放block
    self.callbackBlock = nil;
}

循环变成在view -> callBackBlock处被主动断开,循环引用打破

解决方法2:弱引用

以代理模式为例,对象A中创建对象B并拿到它的delegate,如果delegate为strong声明的,则会形成循环引用A -> B -> BDelegate -> A,而事实上delegate对象通常都被声明为weak型变量,就是为了避免循环引用,从BDdelegate -> A处打破循环引用。

系统对于每一个有弱引用的对象,都维护一个表来记录它所有的弱引用的指针地址。每当一个对象的引用计数为 0 时,系统就通过这张表,找到指向对象的所有弱引用指针,把它们置成 nil。

weakSelf 和 strongSelf:

weakSelf是将一根weak声明的指针指向了self,在self -> self.view -> view -> callBackBlock -> self循环中,weakSelf使用弱引用的方式从callBackBlock -> self处打破循环引用。

- (void)viewDidLoad {
    [super viewDidLoad];
    CustomView *view = [[CustomView alloc] init];
    [self.view addSubview:view];
    __weak __typeof__(self) weakSelf = self;
    view.callbackBlock = ^{
        [weakSelf doSomething];
    }
}

但是使用weakSelf存在一个 callbackBlock 执行时self对象会释放的问题,如果在刚刚执行callbackBlock 中的方法时 weakSelf 就为 nil了,那么callbackBlock中的所有方法中的weakSelf都会是nil,callbackBlock的输出结果是唯一的,不会造成什么影响。但是如果self是在callbackBlock执行到一半的时候释放的,就会导致 callbackBlock出现多种不同的执行结果,这种时候就需要利用strongSelf来保证在所在block的作用域中self不被释放。

- (void)viewDidLoad {
    [super viewDidLoad];
    CustomView *view = [[CustomView alloc] init];
    [self.view addSubview:view];
    //将指向self的指针变成弱指针,这样不会造成循环引用
    __weak __typeof__(self) weakSelf = self;
    view.callbackBlock = ^{
        //保证在所在block的作用域中self不被释放
        //如果不加strongSelf,可能会出现在doSomething时self还存在,而在执行doAnoterThing时,self变成了nil
        __typeof__(self) strongSelf = weakSelf;
        [strongSelf doSomething];
        [strongSelf doAnoterThing];
    }
}

在嵌套block中,每个block中都需要设置strongSelf。

DemoViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    CustomView *view = [[CustomView alloc] init];
    [self.view addSubview:view];
    //将指向self的指针变成弱指针,这样不会造成循环引用
    __weak __typeof__(self) weakSelf = self;
    view.callbackBlock = ^{
        //保证在callbackBlock作用域内的self一直不释放
        //如果self进入block时就为nil,则一直为nil。
        __typeof__(self) strongSelf = weakSelf;
        [strongSelf doSomething];
        [strongSelf doAnoterThing];
        obj.objCallbackBlock = ^{
            //需要重新设置strongSelf,保证在objCallbackBlock作用域内的self一直不释放
            __typeof__(self) strongSelf = weakSelf;
            [strongSelf doObjSomething];
            [strongSelf doObjAnotherThing];
        }
    }
}

4.Core Foundation

上一篇下一篇

猜你喜欢

热点阅读