寒哥管理的技术专题iOS 开发 首页投稿(暂停使用,暂停投稿)

iOS中内存管理漫谈

2016-01-12  本文已影响1028人  庸者的救赎

内存管理!MRC说一堆blabla(当然啦,各种release的痛想必记忆犹新),ARC说一堆blabla...

多么老生常谈的问题,想必每个开发者在起初不止一次的被问起这个问题,但是一旦被问起,一般大家都会说起'黄金法则’,然后@property,blabla一大堆…

大家都知道iOS的内存管理是通过引用计数来管理的,当引用计数为0的时候,对象会自动释放掉,在ARC环境下系统会“自动管理”对象的引用计数,然而事实上并非如此,我们常常遇到试图控制不能正常释放问题,block使用的时候内存泄露.

好吧,多么痛才能领悟,今天咱们就聊聊这个蛋疼的问题,当然啦是在ARC环境下,分别从这么几个地方聊聊

Property

Property想必大家都写过,当然不排除还有很多一直在使用_ivar的,当然啦他们之间的区别以及该用那个好咱们今天不聊,如有想了解的同学请移步这里.属性的内存管理语义有 以下几个:

assign:适用于基本数据类型,其set方法只会进行简单的赋值操作NSInteger,CGFloat...

/** 基本数据类型*/
@property (assign, nonatomic) NSInteger age;

strong:强引用类型,被定义了该类型的属性会被其所指对象持有,为这种属性设置新值的时候,set方法先保留新值,并释放掉旧值,然后再把新值赋值上去

/** 适用于对象类型,并且被持有*/
@property (strong, nonatomic) UIView *view;

weak:爱恨交织的weak!弱引用类型,被定义了该类型的属性不会被持有,在set的时候,既不保留新值也不会释放旧值,和assign类似,不同的是在该属性所指对象被销毁之后,属性值也会被置为nil

/** 弱引用类型,对象释放后置为nil*/
@property (weak, nonatomic) id<SSDelegate> delegate;

unsafe_unretained:这个跟assign就更像了,但不同的是它适用于对象类型.同样的不会被持有,但与weak不同是,所指对象被销毁之后不会把属性置为nil

/** 弱引用类型,对象释放后不会置为nil*/
@property (unsafe_unretained, nonatomic) UIView *view;

copy:它与所指对象是持有关系,与strong类似,然而set方法并不会保留新值,而是将其拷贝一份.所以,当你声明一个非可变集合类型(collection type)的时候应该使用这个修饰符,不单单是NSString,同样的NSArray,NSDictionary,NSSet也应当使用它来修饰,copy可以保护非可变集合类型(collection type)的封装性,因为在其传递过程中有可能会指向一个可变类型(mutable type),此时如果不是copy,那么设置完属性之后,其值可能会在对象不知情的情况下被更改了.所以使用copy修饰符,保证set的时候copy一份不可变(immutable)类型的值,确保前后的一致性.

/** 适用于集合类型(collection type)*/
@property (copy, nonatomic) NSString *name;

Self

看到这个self估计很多人应该开始吐槽了,这SB,self有啥内存管理可言?需要管理么?

哥负责任的告诉你,需要!需要!需要!!!

行,你说需要就需要吧,哪里需要?Show me the code!!!

也许你可能会看到过这样代码,然后一头雾水的就略过了...

#import "SSYNetwork.h"

- (void)startRequestData {
    SSYNetwork *strongSelf = self;
    [strongSelf.delegate finishedRequestData:strongSelf];
    if (strongSelf.successCompletionBlock) {
        strongSelf.successCompletionBlock(strongSelf);
    }
    [strongSelf clearCompletionBlock];
} 

我头一次看到的时候也是一头雾水,这是干嘛呢?写得看起来很高大.仔细分析后就能够发现,这段代码之所以增加一行strongSelf,是为了防止提前释放导致crash.

这是一个请求网络数据的start方法,然而却在里面调用了finish方法,其实还没有等到start方法执行结束并返回的时候,self已经被finishedRequestData:方法释放,造成crash,简单的来说大致是这样的:

- (void)clickAvatar {
    // self持有delegate
    [self.delegate clickAvatar]; // clickAvatar这个代理方法释放了self
    // 这个时候self成了野指针,然后就Boom💣
}

那么现在我们可以考虑一下了,在ARC里面self到底是个什么状态?为何会这样?

其实ARC下,self既不是strong,也不是weak,而是被我们忽略的unsafe_unretained

在我们调用方法的时候,ARC不会对selfretainrelease,self的生命周期由他的调用方法来决定

所以self还是需要做一定的内存管理,不然一不小心就会💣(Boom)

Block

block其实是我们几乎每天都在用的东西,然而又让人讨厌的东西,之所以讨厌,是因为没有做好内存管理,产生循环引用导致内存泄露甚至是crash,下面咱们就唠唠这个烦人的玩意儿

声明方式

// 局部变量
returnType (^blockName)(parameterTypes) = ^returnType(parameters){...}

// 属性,作为属性的时候其修饰符一定是copy
@property (nonatomic, copy) returnType (^blockName)(parameters);

// 作为参数
- (void)someMethodThatTakesABlock:(returnType (^)(parameters))blockName;

// 方法调用是作为参数
[someObject someMethodThatTakesABlock:^returnType (parameters) {...}];

// 宏定义
typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(parameters) {...};

循环引用

在使用block的时候产生了循环引用是最让人头疼的,那么为什么会产生循环引用呢?先看看产生循环引用的代码长什么样,再分析分析why?

// 两个对象间相互引用
@property (nonatomic, copy) void(^block)(void);
self.block = ^{
    [self doSomething];
};

// 多个对象间相互引用,多对象相互引用更难以发现
ClassA* objA = [[ClassA alloc] init];
self.block = ^{
    [self doSomething];
};
self.objA = objA;

上面的这段代码中selfblock之间相互持有,在block里面调用的doSomething方法,使得self引用计数+1,这个过程中self是不会被释放,这就导致了循环引用.OK,那么问题来了,如何解决呢?

其实,解决这个问题就在于如何让block不持有self,要解决这个问题,只需要使用__weak声明一个弱引用(weakSelf)就可以了,代码如下:

@property (nonatomic, copy) void(^block)(void);

// 弱引用
__weak typeof(self) weakSelf = self; 

self.block = ^{
    [weakSelf doSomething];
};

搞定,这样之后我们再调用dealloc方法,打上断点,就可以看出self已经被释放了

Wow!💐💐💐💐💐

不要天真的以为这样就万事大吉,就可以丢下鼠标等胜利了!

如果我说,有时候即使你使用了__weak做了弱引用,也还是有crash的危险!

咱不逗行么.png

请看下面的代码:

// 弱引用
__weak typeof(self) weakSelf = self; 

// 异步线程,这里的block是作为一个参数的
dispatch_async(dispatch_get_main_queue(), ^{
    weakSelf.xx = xx;
});

让我们分析一下原因,将block作为参数传递给dispatch_async时,系统会将block拷贝到堆(heap)上,如果block中使用了self持有的对象,就会retain self,因为dispatch_async并不知道self会在什么时候被释放,所以为了确保系统调度执行block中的任务时self没有被意外释放掉,dispatch_async必须自己retain一次self,任务完成后在release self.但是这里使用了__weak,使得dispatch_async没有增加self的引用计数,这就导致了在调度执行block之前,self可能已经被释放掉了,然后就crash💣,控制台提示BAD_ACCESS

好吧,算你对了,那你说该咋办?

嘿嘿,既然没有retain,那就想办法retain呗?代码如下:

开启装逼模式.jpg
// 弱引用
__weak typeof(self) weakSelf = self; 

// 异步线程,这里的block是作为一个参数的
dispatch_async(dispatch_get_main_queue(), ^{
    // 加强引用防止提前释放,并且做判断
    __strong typeof(weakSelf) strongSelf = self; 
    if (strongSelf) {
        strongSelf.xx = xx;
    }
});

然而这样写就好了么?还有没有更好的方法?程序员都是有追求的,我们是一群高逼格的从业者!

再装逼试试.jpg

好吧,我只是想说还有更优雅的方式来写下面两句话

__weak __typeof(self)weakSelf = self;
__strong __typeof(weakSelf)strongSelf = weakSelf;
别激动,开个小玩笑.jpg

有使用过ReactiveCocoa的同学应该知道,其实我们可以使用影子变量@weakify/@strongify这种方式同样能解决,至于关于影子变量的详细介绍这里就不多说了,有兴趣的同学可以到这里看看,使用影子变量后,代码就会像下面这样:

@weakify(self);
dispatch_async(dispatch_get_main_queue(), ^{
    @strongify(self);
    if (!self) return;
    self.xx = xx; // 这里就可以放心的使用self,简单粗暴
});

OK,装逼结束!!!
如有错误之处,欢迎讨论.

上一篇下一篇

猜你喜欢

热点阅读