β

Objective-C ARC 下的陷阱和最佳实践

YoungShook 134 阅读

Objective-C 是一个非常酷的编程语言 (尽管它和 C++ 一个年代出生). 它的流行不仅仅是因为 iOS 和 MacOS 平台, 也是其在移动平台上强大的性能. 然而在日常开发过程中仍然要面对这个古老语言的诸多坑, 例如面对 Objective-C 的内存管理时, 虽然有强大的 ARC, 并且 Xcode 已经有很智能的警告提示, 但当面对复杂的内存管理的时候, 仍然会掉进坑中, 例如在 ARC 环境下进行 Core Foundation Foundation 之间数据类型交换. 这种技术也叫做 Toll-Free Bridging , 看起来很高端的样子, 今天我们就来说说如何以轻盈的姿势避开这些坑.

ARC 的最佳实践

先说下在使用 ARC 时一些所有权修饰符用法, @amattn 大神写的这篇 ARC 最佳实践 . 一篇关于 ARC 的最佳实践的文章, 下面是对部分的摘抄翻译.

@property (nonatomic, strong) id childObject;
@property (nonatomic, weak) id delegate;
@property (nonatomic, weak) NSTimer timer;
@property (nonatomic, assign) CGFloat width;
@property (nonatomic, assign) CGFloat height;
@property (nonatomic, copy) NSString* name;
@property (nonatomic, copy) NSArray* components;
@property (nonatomic, copy) (void (^)(void)) job;
@property (nonatomic, strong) NSMutableArray* badPatterns;

ARC 所有权修饰符

  1. __strong 是默认修饰符. 表示对对象的 强引用 . 持有强引用的变量在超出其作用域时被废弃, 随着强引用的失效, 引用的对象会随之释放.
  2. __weak 修饰符的变量 (即弱引用) 不持有对象, 所以在超出其变量作用域时, 对象即被释放, 并且并被赋值为 nil .
  3. __unsafe_unretained 修饰符正如其名 unsafe 所示, 是不安全的所有权修饰符, 在所有权上类似 __weak , 但是并不会在超出其变量作用域时被赋值为 nil .
  4. __autoreleasing 用来修饰一个声明为 (id *) 的函数的参数, 当函数返回值时被释放.

需要注意的是 ARC 的所有权修饰符只能来修饰 指针类型 , 也就是说你只能把所有权修饰符放在星号的右边.

MyClass * __weak w_self = self;    // 正确
MyClass __weak * w_self = self;    // 错误! 会引起令人抓狂的 Bug!
__weak MyClass * w_self = self;    // 错误!

__weak typeof(self) w_self = self; 
// 正确, 类似
// __weak (MyClass *) w_self = self;

typeof(self) __weak w_self = self; // 正确

可能在互联网上能看到很多的错误使用 ARC 修饰符. Apple 给出的 ARC 修饰符 范式 :

You should decorate variables correctly. When using qualifiers in an object
variable declaration, the correct format is:
ClassName * qualifier variableName;

ARC 和 Toll-free bridging

在 ARC 使用过程中, 最容易出错的地方就是 Core Foundation Foundation 之间数据类型交换.
按照下面这些规则使用, 我们就能容易的避免这些陷阱.

有两种方法来保留一个 CF 对象: 使用类型转换 (__bridge_retained) 或者 C 方法 CFBridgingRetain , 在 Clang 文档中推荐使用类型转换的方式, 我更喜欢后者, 因为更加便于阅读.

CFArrayRef arr = CFBridgingRetain(@[@"abc", @"def", @3.14] );
// or CFArrayRef arr = (__bridge_retained CFArrayRef)@[...];
// do stuffs..
CFRelease(arr);

当你从包含 Create Copy 方法名的 Core Foundation 方法中获取一个对象时, 应该使用 (__bridge_transfer) CFBridgingRelease 来做转换.

- (void)logFirstNameOfPerson:(ABRecordRef)person {
 
    NSString *name = (NSString *)CFBridgingRelease(ABRecordCopyValue(person, kABPersonFirstNameProperty));
    NSLog(@"Person's first name: %@", name);
}

ARC 在 Toll-free bridging 中的陷阱

有时候我们会看到这样的代码:

- (CGColorRef)foo {
    UIColor* color = [UIColor redColor];
    return [color CGColor];
}

当心, 这段代码可能随时引起程序崩溃, 因为我们不持有 UIColor, 它会在创建之后立即释放, 从而所拥有的 CGColor 将被释放, 从而引发 Crash.
下面有三种方式来修复这段代码:

- (CGColorRef)fooColorCopy {
    UIColor* color = [UIColor redColor];
    CGColorRef c = CFRetain([color CGColor]);
    return c;
}


CGColorRef c = [obj fooColorCopy];
// do stuffs
CFRelease(c);
- (CGColorRef)getFooColor {
    CGColorRef c = self.myColor.CGColor;
    return c;
}

ARC 在 Block 中的陷阱

当你使用一个变量在 self 所持有的 Block 中时, 将会引起一个循环引用.

@interface MyClass {
    id child;
}
@property (nonatomic, strong) (void(^)(void)) job;
@end

@implementation MyClass
- (void)foo {
    self.job = ^{
        [child work];
        // will expand to [self->child work]
    };
}

避免这种陷阱的一种方式是使用弱引用的 self. 并且在 Block 中强引用这个弱引用对象 self, 即形成所谓的 对外弱引用对内强引用 .
对内再次强引用的原因是弱引用对象可能随时被释放, 我们必须保证这个对外弱引用对象在使用时是有效的.

- (void)foo {
    MyClass* __weak w_self = self;
    self.block = ^{
        MyClass* s_self = w_self; // self 被强引用, 但仅仅在这个作用域中.
        if (s_self) {
            [s_self->child work];
            // do other stuffs
        }
    };
}

ARC 在 NSError 中的陷阱

如果你要实现一个 NSError 的方法, 请务必使用正确的所有权修饰符.

- (void)doStuffWithError:(NSError* __autoreleasing *)error; // 正确 
- (void)doStuffWithError:(__autoreleasing NSError **)error; // 错误!

事实上, 当你创建一个 NSError 对象时, 最好声明为 __autoreleasing .

NSError* __autoreleasing error = nil; // 正确
__autoreleasing NSError* error = nil; // 错误
NSError* error = nil;

Objective-C 是一个非常酷的编程语言 (尽管它和 C++ 一个年代出生). 它的流行不仅仅是因为 iOS 和 MacOS 平台,也是其在移动平台上强大的性能. 然而在日常开发过程中仍然要面对这个古老语言的诸多坑, 例如面对 Objective-C 的内存管理时, 虽然有强大的 ARC, 并且 Xcode 已经有很智能的警告提示, 但当面对复杂的内存管理的时候,仍然会掉进坑中, 例如在 ARC 环境下进行 Core Foundation Foundation 之间数据类型交换. 这种技术也叫做 Toll-Free Bridging , 看起来很高端的样子, 今天我们就来说说如何以轻盈的姿势避开这些坑.

作者:YoungShook
原文地址:Objective-C ARC 下的陷阱和最佳实践, 感谢原作者分享。

发表评论