iOS 内存管理

Posted 北渚

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS 内存管理相关的知识,希望对你有一定的参考价值。

一、Objective-C 使用引用计数来管理内存。

每个对象都有一个计数器,来表示引用该对象的个数;每次引用就加1,用完就减1;当计数为0时表示不再使用该对象,于是就销毁该对象。
多个对象之间的引用形成闭环会导致循环引用,从而不能够相互释放,造成内存泄漏。

二、ARC 自动引用计数

ARC 自动引用计数,把内存管理事宜交由编译器来处理。
使用 ARC 时,实际上引用计数还是在执行的,只不过保留操作和释放操作由 ARC 在编译时自动为你添加。
ARC 环境下特殊情况处理。

- (void)dealloc {
    // 移除通知中心的监听者
    // 移除 KVO 监听者
    // 关闭 NSTimer,并将定时器置空(nil)
    // 释放非 Objective-C 对象的内存,如 CFRelease(...), free(...)
}

三、自动释放池

@autoreleasepool {} 自动释放池。
当向一个对象发送autorelease消息时,系统会将该对象放入到最新的自动释放池。
在自动释放池的作用域内,池子内的对象依然可以正常使用。
当自动释放池的作用域结束时,再释放池子内的对象———每个对象收到几个autorelease就进行几次release操作。
常见用途:循环内部创建大量临时对象时,没有及时释放导致内存使用急剧增加,可以使用自动释放池在每次循环结束时释放对象。

for (NSInteger i = 0; i < 100000; i++) {
    @autoreleasepool {
        NSString *str = @"Hello World";
        str = [str stringByAppendingFormat:@"- %ld", i];
        str = [str uppercaseString];
    }
}

四、@property 属性修饰符

原子性(多线程管理):atomic、nonatomic
读写属性:readwrite、readonly
setter 语意:assign、retain、copy
强弱引用:strong、weak
1、NSNumber、NSString、Block 使用 copy。
2、IBOutlet 属性、引用“引用对象”的属性(控制器引用view的子视图)、代理属性使用 weak。
3、Class、id 类型的属性使用 retain。
4、NSString 属性使用 retain 时,可能赋值一个 NSMutableString,导致值为 NSMutableString 类型。
5、NSMutableString 属性使用 copy 时,导致属性值为 NSString 类型,有可能出现未识别方法调用的异常。
atomic 只是保证了读写的线程安全,但是在多线程环境访问对象属性时,访问结果不一定符合我们的预期。

- (void)setAtomicObj:(NSObject *)atomicObj{
    @synchronized(self) {
        if (_atomicObj != atomicObj) {
            [_atomicObj release];
            _atomicObj = [atomicObj retain];
        }
    }
}
- (NSObject *)atomicObj{
    @synchronized(self) {
        return _atomicObj;
    }
}

ARC下同时重写getter、setter方法时,需使用关键字@synthesize 声明变量和属性的关系。
@synthesize name = _name;
注意:@property = ivar + getter + setter;

五、定时器使用时的内存管理

通常控制器会拥有一个定时器,如果把控制器设置为定时器的 target,定时器会引用控制器,导致定时器不关闭销毁时不能释放控制器。因此,想办法不要定时器直接或者间接引用控制器即可。
定时器解决循环引用思路:
1、ios10 以后系统提供了block 方法执行定时器操作。

    [NSTimer timerWithTimeInterval:(NSTimeInterval) repeats:(BOOL) block:^(NSTimer * _Nonnull timer) {
        
    }];
    [NSTimer scheduledTimerWithTimeInterval:(NSTimeInterval) repeats:(BOOL) block:^(NSTimer * _Nonnull timer) {
        
    }];

2、给 NSTimer 提供一个分类,实现自定义的 block 方法。

@interface NSTimer (BBBlocksSupport)

+ (NSTimer *)bb_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                         block:(void(^)(void))block
                                       repeats:(BOOL)repeats;

@end
@implementation NSTimer (BBBlocksSupport)

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

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

@end

3、中间件模式。
设计思路:给中间对象动态添加定时器需要执行的方法。

#import <Foundation/Foundation.h>

@interface BBTimerManager : NSObject

@property (weak, nonatomic) NSTimer *bb_timer;

+ (instancetype)bb_timerManagerWithTimeInterval:(NSTimeInterval)interval
                                         target:(id)aTarget
                                       selector:(SEL)aSelector
                                       userInfo:(id)userInfo
                                        repeats:(BOOL)repeats;

@end
#import "BBTimerManager.h"
#import <objc/runtime.h>

@implementation BBTimerManager

+ (instancetype)bb_timerManagerWithTimeInterval:(NSTimeInterval)interval
                                         target:(id)aTarget
                                       selector:(SEL)aSelector
                                       userInfo:(id)userInfo
                                        repeats:(BOOL)repeats {
    Method targetMethod = class_getInstanceMethod([aTarget class], aSelector);
    if (!class_addMethod([BBTimerManager class], aSelector, method_getImplementation(targetMethod), method_getTypeEncoding(targetMethod))) {
        class_replaceMethod([BBTimerManager class], aSelector, method_getImplementation(targetMethod), method_getTypeEncoding(targetMethod));
    }
    BBTimerManager *manager = [BBTimerManager new];
    manager.bb_timer = [NSTimer scheduledTimerWithTimeInterval:interval target:manager selector:aSelector userInfo:userInfo repeats:repeats];
    return manager;
}

@end
- (void)dealloc {
    [_timerManager.bb_timer invalidate];
    _timerManager.bb_timer = nil;
}

六、优雅使用 KVO

在需要处理属性变化事件的对象中添加观察者,并在该对象销毁时移除观察者。

@interface RootViewController ()
@property (retain, nonatomic) Focus *focus;
@end
@implementation RootViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.focus = [[Focus alloc] init];
    [self.focus addObserver:self
                 forKeyPath:@"number"
                    options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
                    context:nil];
    self.focus.number = @2;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"number"]) {
        NSLog(@"%@", change);
    }
}

- (void)dealloc {
    [self.focus removeObserver:self forKeyPath:@"number"];
}

七、copy 操作

对于不可变对象 NSString、NSArray、NSDictionary 表示引用加1。
对于可变对象 NSMutableString、NSMutableArray、NSMutableDictionary 表示复制对象,返回不可变对象。
mutableCopy 操作:
对于不可变对象、可变对象都是表示复制对象,返回可变对象。
自定义对象使用 copy 和 mutableCopy 需要遵守 NSCopying 和 NSMutableCopying 协议。

- (id)copyWithZone:(nullable NSZone *)zone;
- (id)mutableCopyWithZone:(nullable NSZone *)zone;

八、weak 引用原理

runtime 维护了一个 weak 表,weak 表其实是一个 hash(哈希)表,其中 Key 是所指对象的地址,Value 是 weak 指针的地址数组。
weak 的实现原理可以概括一下三步:
1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

以上是关于iOS 内存管理的主要内容,如果未能解决你的问题,请参考以下文章

iOS 内存管理问题

C 中的共享内存代码片段

iOS 5编程 内存管理 ARC技术概述

iOS开发--漫谈内存管理

理解 iOS 的内存管理

如何使用模块化代码片段中的LeakCanary检测内存泄漏?