iOS ARC 下与 C++ 混编那些事儿

By | 2016-12-07


前言

iOS 的 ARC 模式各位应该都很熟了, 虽然在这个 Swift 都已经出到 3 的年代再来提 ARC 真是有点落伍了, 不过 ARC 下和 C++ 混编时各种问题倒是很少人提到, 正好这次将 ZFFramework 迁移到 ARC, 顺便留个文记录一下

PS: 作为从 C艹 撸过来的猴子, 对 ARC 其实并没有抱多少好感, 毕竟不像 Java 的 GC 那么方便, 又会有各种语法限制, 不过这次试了下感觉 ARC 还是很好用很有意义的

基础

个人倾向于简单一句话理解为 "有指针指向, 则保持对象的 retain 状态"

教科书式的教程什么的最没劲了, 这里就简单列一些不那么容易看到的情况

非 property 指针

static MyObject *_instance = nil;

@implementation MyHolder
- (id)init
{
    self = [super init];
    _instance = [MyObject new];
    return self;
}
- (void)cleanup
{
    _instance = nil;
}
- (void)dealloc
{
    _instance = nil;
}
@end

在 ARC 下, 非 property 指针可以视为和 property 的行为完全一致, new/alloc 之后直接赋值, 要销毁时设为 nil 即可

readonly property

// in *.h file
@interface MyObject : NSObject
@property (nonatomic, strong, readonly) id myValue;
@end

// in *.m file
@interface MyObject()
@property (nonatomic, strong, readwrite) id myValue;
@end

这个 ARC/MRC 下都可以用这种设计, ARC 下更是什么都不用管, 不管是 self.myValue 还是 self->_myValue 都可以直接赋值, 还是很好用的

显式 retain

偶尔一些代码逻辑还是希望能够明确的表示 "我想要 retain 这个对象", 然而 ARC 不让你干, 这时候可以用个简单粗暴的方式, 就是在对象里弄个 strong property 来存自己, 需要显式释放时置空就好

performSelector

这个问题应该也容易碰到, 到处搜索一下也有各种解决方案, 因为比较重要这边也列一下

原因: 非显示声明的 selector, ARC 无法判断返回值是否需要自动 release

问题例子:

- (id)func1
{
    return [MyObject new];
}
- (id)func2
{
    return [MyObject sharedInstance];
}

// 此处 ARC 无法知道返回值是否需要释放, 因此会报警告
SEL action = (someCond ? @selector(func1) : @selector(func2));
[target performSelector:action];

// 此处编译器可以自行推导出来, 因此没有问题
[target performSelector:@selector(func1)];
[target performSelector:@selector(func2)];

网上各种解决方案大多是让你们用各种路子绕过编译器警告, 实际上是不靠谱的方案, 因为相当于让 ARC 不要处理返回值, 因此上述例子的 func1 就会造成内存泄露了

个人建议的方法是, 非必须情况下就不要用 selector 返回值了, 确实要返回, 可以用参数来输出, 比如

@interface Holder : NSObject
@property (nonatomic, strong) MyObject *result;
@end

- (void)func:(Holder *)result
{
    result.result = xxx;
}

与 C++ 混编

基本上就是以下三个关键词转来转去:

  • __bridge : 简单的指针转换, 不涉及引用计数变化
  • __bridge_retained : OC对象指针转 C指针, 所有权交给 C指针
  • __bridge_transfer : C指针转 OC对象指针, 所有权交给 OC对象指针

(大多数文章也就讲到此了, 给的例子也是让人摸不着头脑, 所以这里我尽量用简单并容易理解的例子再列一遍)

典型流程

  1. OC对象放到 C代码中保存

    // wrapper
    static void *cFunc(void)
    {
        MyObject *ocObj = [MyObject new];
        return (__bridge_retained void *)ocObj;
    }
    
    // 保存
    void *cPointer = cFunc();
    
  2. 通过 C指针访问 OC对象

    MyObject *ocObj = (__bridge MyObject *)cPointer;
    [ocObj xxx];
    
    // 作为参数时也要这么搞
    [other func:(__bridge MyObject *)cPointer];
    
  3. 手动释放

    MyObject *ocObj = (__bridge_transfer MyObject *)cPointer;
    ocObj = nil;
    

其实讲太多反而晕, 最简洁的理解为

  • __bridge_retained 相当于 MRC 的 retain
  • __bridge_transfer 相当于 MRC 的 release
  • __bridge 相当于 static_cast
  • 总的来说, 与 C 交互时, 相当于回到了 MRC (而且是没有 autorelease 的 MRC)
  • 对比一下你会发现, 这个和 JNI 挺像的

    • JNI 里面的 global ref 对应 ARC 的 __bridge_xxx
    • JNI 里面的 local ref 对应 ARC 帮你自动插入的 retain/release, 或者更像是 autorelease

转载请注明来自: http://zsaber.com/blog/p/147

既然都来了, 有啥想法顺便留个言呗? (无奈小广告太多, 需审核, 见谅)

发表评论

电子邮件地址不会被公开。