iOS内存管理笔记

2020-06-18  本文已影响0人  BytePorter

原文链接

ARC

ARC(Automatic Reference Counting)是指内存管理中对引用采用自动计数。由编译器自动管理 retain 和 release。此外,在对象释放后,OC运行时库将会把指向该对象的所有 weak 变量置 nil。

内存管理方式

Autorelease

用于类方法返回 Autorelease 对象

// ARC
id array = [NSArray array];

// MRC
id array = [[[NSArray alloc] init] autorelease];

AutoreleasePool 对象释放时机

RunLoop 即将进入休眠(kCFRunLoopBeforeWaiting)时释放池中对象

产生大量 autorelease 对象时,应使用 AutoreleasePool 降低内存使用峰值

for (int idx = 0; idx < INT_MAX; idx++) {
    @autoreleasepool {
        NSString *string = [NSString stringWithFormat:@"%@ say: Hello World!", @(idx)];
        NSLog(@"%@", string);
    }
}

ARC 规则

单一文件可选择是否使用 ARC

在 Build Phases --> Compile Sources --> xxx.m --> Compiler Flags 设置是否使用 ARC。-fobjc-arc表示禁用 ARC

ARC 所有权修饰符

对象指针

- (void)performSelectorWithError:(NSError **)error
{

}    
// 等同于
- (void)performSelectorWithError:(NSError * __autoreleasing *)error
{
    
}
// error: pointer to non-const type 'NSError *' with no explicit ownership
NSError *error = nil;
NSError **pError = &error;

// right
NSError *error = nil;
NSError * __strong *pError = &error;

打印注册到 autoreleasepool 上的对象

无论 ARC 是否有效,调试时可使用 _objc_autoreleasePoolPrint()

(lldb) po _objc_autoreleasePoolPrint()

不要显式调用 dealloc

显示转换 id 和 void *

id型或对象型变量赋值给 void * 或着逆向赋值时都需要进行特定的转换。如果只想单纯赋值,则可以使用__bridge转换

// error: Implicit conversion of Objective-C pointer type 'id' to C pointer type 'void *' requires a bridged cast
id obj = [NSObject new];
void *p = obj;

// right
id obj = [NSObject new];
void *p = (__bridge void *)(obj);
id obj_1 = (__bridge id)(p);

__bridge_retained

转换要赋值的变量并持有该对象

// ARC 
id obj = [NSObject new];
void *p = (__bridge_retained void *)obj;
    
// __bridge_retained 对等于下面非 ARC 实现
id obj = [NSObject new];
void *p = obj;
[(id)p retain];    

// error: EXC_BAD_ACCESS
void *p = 0;
{
    id obj = [NSObject new];
    p = (__bridge void *)obj;
}
NSLog(@"%@", (__bridge id)p);

// right
void *p = 0;
{
    id obj = [NSObject new];
    p = (__bridge_retained void *)obj;
}
NSLog(@"%@", (__bridge id)p);

__bridge_transfer

__bridge_retained相反,被转换的变量所持有的对象在该变量被赋值给转换目标变量后随之释放

// ARC 
id obj = (__bridge_transfer id)p;

// __bridge_transfer 对等于下面非 ARC 实现
id obj = (id)p;
[obj retain];
[(id)p release];

ARC 场景分析

Double release

// ARC 隐式无效
// *** -[CFString release]: message sent to deallocated instance 0x6000009e9620
{
    id string = nil;
    @autoreleasepool {
        NSNumber *num = [[NSNumber alloc] initWithLong:6666666666666666];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSNumber instanceMethodSignatureForSelector:@selector(stringValue)]];
        invocation.target = num;
        invocation.selector = @selector(stringValue);
        [invocation invoke];
        [invocation getReturnValue:&string];
        NSLog(@"%p", string);
    }
}

// 解决
// __weak
__weak id string = nil;

// __unsafe_unretained
__unsafe_unretained id string = nil;

可以正常运行么?

// NSTaggedPointerString
{
    id string = nil;
    @autoreleasepool {
        NSNumber *num = [[NSNumber alloc] initWithLong:666];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSNumber instanceMethodSignatureForSelector:@selector(stringValue)]];
        invocation.target = num;
        invocation.selector = @selector(stringValue);
        [invocation invoke];
        [invocation getReturnValue:&string];
        NSLog(@"%p", string);
    }
}

Block 为啥还要 strongSelf

// __weak 告诉 block 不要强引用 self,防止发生循环引用 
__weak typeof(self) weakSelf = self;

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
    // 虽然加了判断,其实跟不加的效果一致
    if (weakSelf == nil) {
        return;
    }
    
    // 由于是弱引用,所以 weakSelf 随时都可能被释放。
    // 以下调用行为不确定,存在各种情况。可能都失败,或者都成功,或者部分成功
    [weakSelf doSomething];
    [weakSelf doSomethingElse];
});


dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // 如果 weakSelf 未被释放, __strong 则保证 self 在 block 作用域不被释放
    __strong typeof(weakSelf) strongSelf = weakSelf;
    
    // 要么直接 return , 要么都会执行。调用行为确定
    if (strongSelf == nil) {
        return;
    }
    
    [strongSelf doSomething];
    [strongSelf doSomethingElse];
});

下面的代码阔以编译通过么?

// Dereferencing a __weak pointer is not allowed due to possible null value caused  
// by race condition, assign it to strong variable first

__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
    weakSelf->_someProperty = [NSObject new];
});

下面的 __weak 可以避免循环引用么?

@interface ViewController ()

@property (nonatomic, strong) NSTimer *timer;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
        
    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1
                                                  target:weakSelf
                                                selector:@selector(onTimerFire)
                                                userInfo:nil
                                                 repeats:YES];
}

@end

官方文档的注释如下:

The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated.

target 无论传的是 __weak or __strong 其内部还是会对其进行强引用,无法避免循环引用。下面说说一种通用的解决方案,代码如下:

@implementation NSTimer (Extension)

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      block:(void(^)(NSTimeInterval time))block
                                    repeats:(BOOL)repeats
                            loopCommonModes:(BOOL)loopCommonModes {
    
    // 将 NSTimer 类对象作为 target,这样子就可以完美的解决循环引用的问题
    NSTimer *timer = [self scheduledTimerWithTimeInterval:interval
                                                   target:self
                                                 selector:@selector(blockInvoke:)
                                                 userInfo:[block copy]
                                                  repeats:repeats];
    
    if (loopCommonModes) {
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    }
    
    return timer;
}


+ (void)blockInvoke:(NSTimer *)timer 
{
    if (timer.isValid) {
        void (^block)(NSTimeInterval) = timer.userInfo;
        if (block) {
            block([timer timeInterval]);
        }
    }
}

@end

Exceptions 下的 try catch 内存泄漏

obj 可以正常释放么?

@try {
    MyObject *obj = [MyObject new];
    [obj description];

    @throw [NSException exceptionWithName:@"ML" reason:@"Memory Leak" userInfo:@{}];
} @catch (NSException *exception) {
    NSLog(@"%@", exception);
} @finally {
    NSLog(@"finally call");
}

myObject 可以正常释放么?

{
    ViewController *vc = [ViewController new];
    vc.myObject = [MyObject new];

    @try {
        [vc.myObject removeObserver:self forKeyPath:@"Unknown name"];
    } @catch (NSException *exception) {
        NSLog(@"%@", exception);
    } @finally {
        NSLog(@"finally call");
    }
}

运行结果:obj 和 myObject 都不能正常释放。这是为什么呢?在 Objective-C Automatic Reference Counting (ARC) 中有以下说明:

By default in Objective C, ARC is not exception-safe for normal releases:

  1. It does not end the lifetime of __strong variables when their scopes are abnormally terminated by an exception.
  2. It does not perform releases which would occur at the end of a full-expression if that full-expression throws an exception.

解决:

参考文档

1. Block Implementation Specification

2. Objective-C Automatic Reference Counting (ARC)

3. I finally figured out weakSelf and strongSelf

4. 深入理解Tagged Pointer

上一篇下一篇

猜你喜欢

热点阅读