聊聊 iOS 中的内存管理

cubegao 2018-08-23 PM 522℃ 0条

对于开发者来说,内存管理几乎是一个永恒的话题。

内存管理对于编写出高效率的 iOS APP 是非常重要的,这是因为iOS是多任务系统,在同一时刻可能有多个应用程序共享内存,有时为了使某个任务更好地执行,iOS系统可能会对其它任务分配的内存进行移动,甚至删除。

现在被广泛使用的内存管理机制主要有GCRC两种。
GC:垃圾回收机制,定期查找不再使用的对象,释放对象占用的内存。
RC:引用计数机制,采用引用计数来管理对象的内存,当需要持有一个对象时,使它的引用计数+1,当不需要再持有一个对象的时候,使它的引用计数-1;当一个对象的引用计数为0时,该对象就会被销毁。

Objective-c支持三种内存管理机制:GC,MRC,ARC。以前MacOS开发中是支持GC的,后面引入ARC后就弃用了GC机制。iOS开发中使用的是RC机制,从MRC到现在的ARC

引用计数

可以看我之前的一篇关于引用计数

MRC时代

基本内存管理规则:MRC下是要严格遵守引用计数内存管理规则。
内存管理模型是基于对象的所有权。任何对象都可以拥有一个或者多个所有者,但是一个对象最少要拥有一个所有者,它才会继续存在。如果对象没有所有者的,它将会被销毁。

四条内存管理策略:

1.自己生成的对象,自己持有
alloc/new/copy/mutableCopy等方法创建的对象,我们自己直接持有,它的RC初始值为1,我们可以直接使用,不需要使用的时候调用release释放。

2.非自己生成的对象,自己也能持有。
不通过上面方法创建的对象,它的RC初始值也为1,但是我们不能直接使用,要先调用retain,否则可能会crash。原因是因为这些方法内部是给对象调用了autorelease方法,所以这些对象是被添加到了自动释放池中了。

两种情况,程序没有手动指定@autoreleasepool,当runloop迭代结束时,会自动给释放池中的对象调用release方法。如果我们使用前不进行retain,刚好runloop迭代结束,对象的RC从1变成0,然后就被销毁了.我们去访问一个已经被销毁的对象,程序就会crash
程序手动指定了@autoreleasepool,当时出了其作用域,对象会调用release方法,然后销毁,这个时候使用对象也会crash

3.不再需要自己持有的对象时,释放。
不再需要持有或者使用对象的时候,就调用release或者autorelease方法进行释放。rc变成0时,就会调用dealloc方法销毁。
release立马rc-1autorelease加入自动释放池,在合适的时候rc-1,实际上就是延迟释放。

4.非自己持有的对象,无需释放。
自己不是持有者,调用release的话,会crash

实用的内存管理原则

使用访问器方法可以省略大量的retainrelease
推荐使用访问器方法设置属性,一个是可以避免忘记写retainrelease,第二个是触发kvo
不要在初始化和dealloc中使用访问器方法,原因是在初始化中使用访问器方法,self=[super init],首先会调用父类的访问器方法,然后如果子类重写了访问器方法,会去调用子类的访问器方法,而这个时候,子类实际上是没有初始化的,所以会出错。dealloc实际上是个反向的。就是子类先释放,再去调用父类。就是调用父类的dealloc时,会调用父类的访问器方法,如果子类重写了该访问器方法,就会去调用子类的访问器方法,但是子类已经释放掉了,也会出错。还有一个原因就是会触发KVO,在准备释放前,触发了kvo,即将释放的对象被持有了。就是出现莫名其妙的错误。
推荐使用@autoreleasepool,虽然mrc下,可以使用NSautoreleasepool,但是速度慢很多。
1.写的程序是非UI框架,比如命令行工具
2.循环创建大量的临时变量。
3.多线程中。比如创建辅助线程。

ARC时代

ARC时代,内存管理基本都交给编译器来处理了,对于开发者来说是十分友好了。
ARC的编译器有两部分,前端编译器和优化器。前端编译器会对每一个对象插入相应的release语句。类拥有的对象会在dealloc中释放。方法内创建的对象,前端编译器会在方法末尾自动插入release语句销毁它。优化器负责移除多余的retainrelease操作语句。

但是还是有些地方需要注意的。

新增的几个关键字:
__strong 强引用,用来保证对象不会被释放。
__weak 弱引用 释放时会置为nil
__unsafe_unretained 弱引用 可能不安全,因为释放时不置为nil。悬垂对象。
__autoreleasing 对象被注册到autorelease pool中方法在返回时自动释放。

block使用中出现循环引用

_person1 = [[Person alloc] init];
_person2 = [[Person alloc] init];
_person2.name = @"张三";
__weak __typeof(self) weakSelf = self;
_person1.block = ^{
    __typeof(&*weakSelf) strongSelf = weakSelf;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",strongSelf.person2.name);
    });
};

使用weakSelf结合strongSelf的情况下,能够避免循环引用,也不会造成提前释放导致block内部代码无效。
在MRC下,应该使用__block。

NSTimer循环引用

使用category处理。

@implementation NSTimer (BlcokTimer)

+ (NSTimer *)bl_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void (^)(void))block repeats:(BOOL)repeats {
    
    return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(bl_blockSelector:) userInfo:[block copy] repeats:repeats];
}

+ (void)bl_blockSelector:(NSTimer *)timer {
    
    void(^block)(void) = timer.userInfo;
    if (block) {
        block();
    }
}
@end

使用中间件,利用runtime,self依赖timer,time依赖中间件,中间件弱引用self

@interface WeakObject()

@property (weak, nonatomic) id weakObject;

@end

@implementation WeakObject

- (instancetype)initWithWeakObject:(id)obj {
    _weakObject = obj;
    return self;
}

+ (instancetype)proxyWithWeakObject:(id)obj {
    return [[WeakObject alloc] initWithWeakObject:obj];
}


/**
 * 消息转发,让_weakObject响应事件
 */
- (id)forwardingTargetForSelector:(SEL)aSelector {
    return _weakObject;
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    void *null = NULL;
    [invocation setReturnValue:&null];
}

- (BOOL)respondsToSelector:(SEL)aSelector {
    return [_weakObject respondsToSelector:aSelector];
}

使用method swizzing,利用Method Swizzling,通过替换原有的接口,将循环引用的对象改为弱引用。

//替换类方法——宏
#define ClassMethodSwizzling(func0, func1) \
{\
Method imp = class_getInstanceMethod(object_getClass([self class]), @selector(func0));\
Method myImp = class_getInstanceMethod(object_getClass([self class]), @selector(func1));\
method_exchangeImplementations(imp, myImp);\
}

+ (void)load
{
    //替换方法
   ClassMethodSwizzling(scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:, fc_scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:);
}

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo
{
    typeof(aTarget) __weak weakTarget = aTarget;
    
    //创建类方法的引用,并指向中间函数
    NSTimer *timer = [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(fc_blockInvoke:) userInfo:userInfo repeats:yesOrNo];
    typeof(NSTimer *) __weak weakTime = timer;
    //生成block
    timer.fcTimerBlock = ^(void){
        if (!weakTarget)
        {
            return;
        }
        IMP imp = [weakTarget methodForSelector:aSelector];
        void (*func)(id, SEL, id) = (void *)imp;
        func(weakTarget, aSelector,weakTime);
    };
    weakTime = timer;
    return timer;
}
标签: iOS

非特殊说明,本博所有文章均为博主原创。

评论啦~