前言

信任许多iOS从业者都知道Foundation目标与Core Foundation目标,前者是Objective-C目标,在ARC中会主动办理它们的生命周期,后者是C目标,在ARC中需求开发者手动办理其生命周期,以免造成内存泄漏。两者之间能够通过“无缝桥接”技能相互转化。

一、转化办法

举个比如

// NSObject 转 C obj
NSArray *nsArray = [NSArray new];
CFArrayRef cfArray = (__bridge CFArrayRef)nsArray;
// C obj 转 NSObject
CFStringRef cfStr = CFStringCreateWithCString(NULL, "cString", kCFStringEncodingUTF8);
NSString * nsStr = (__bridge NSString *)cfStr;

上面的代码中,__bridge表明不改变目标的办理权所有者
  例如在第一个比如里,nsArray转化成cfArray目标后,nsArray仍然由ARC办理。开发者无需手动办理,即不需求手动开释cfArray
  而在第二个比如里,cfStr转化成nsStr后,cfStr不会转化办理者,仍然需求开发人员办理其生命周期,最后需求调用CFRelease(cfStr);来开释cfStr

日常开发中,咱们最常用的就是__bridge桥式转化,可是也有别的桥式转化是指定的,例如__bridge_retained__bridge_transfer

  __bridge_retained 是用于在将Foundation目标转化成Core Foundation目标时,进行ARC内存办理权的掠夺,意味着ARC将交出目标的所有权。若是在第一个比如中改成用__bridge_retained,那么开发者需求用完数组之后就要加上 CFRelease(cfArray) 以开释其内存。
  而__bridge_transfer则与之相反,它用于将Core Foundation目标转化成Foundation目标时将办理权交给ARC,开发者无需再关心其内存开释。

这三种转化办法都是 “桥式转化”

二、桥式转化用途

信任许多单纯objc言语开发的程序员在平日的开发中很少会用到桥式转化,那么为什么需求桥式转化呢?什么场景下咱们会用到桥式转化呢?
  其实在 Foundation 结构中,objc类所具备的某些功用,是 CoreFoundation 结构中的C言语数据结构所不具备的,反之亦然。举个比如,假设咱们在运用objc的字典NSDictionary时,如果咱们想key存入的是一个咱们自界说的模型目标,那就会出问题,例如下面的代码

关于iOS中无缝桥接技术

为什么会出现在 -[MyCustomClass copyWithZone:]: unrecognized selector sent to instance 0x6000005a43e0 这个过错呢?这是由于在 Foundation 结构中的字典,其键的内存办理语义为 “复制”,而值的语义是却是“保存”,而NextModel并没有遵从NSCopying协议,并完成copyWithZone办法,所以产生了溃散。可是CoreFoundation 结构中的字典界说是能够自界说其内存办理语义 的,这时候咱们就能够先界说CoreFoundation 结构中的字典,然后运用强大的无缝桥接技能,将它转化成 Foundation 结构中的字典,就能处理这个问题了。
  以下就来说一下如何结构CoreFoundation的字典,先来看看苹果的界说文档如下:

关于iOS中无缝桥接技术
关于iOS中无缝桥接技术
关于iOS中无缝桥接技术

图片显现文档界说了CoreFoundation的字典CFMutableDictionaryRef的结构函数是

CFMutableDictionaryRef CFDictionaryCreateMutable(CFAllocatorRef allocator,
                        CFIndex capacity,
                        const CFDictionaryKeyCallBacks *keyCallBacks,
                        const CFDictionaryValueCallBacks *valueCallBacks);

其中,allocator表明内存分配器,担任分配和收回这些 CoreFoundation目标里的数据结构占用的内存,一般传NULL表明运用默许的分配器。
capacity声明字典的初始巨细,熟悉C言语的开发都知道这仅仅初始默许创立的巨细,仅仅向分配器提示了一开始应该分配多少内存,后面会根据它的数据插入而增大容量。
CFDictionaryKeyCallBacksCFDictionaryValueCallBacks是两个指向结构体的指针,其结构如下图

关于iOS中无缝桥接技术

关于iOS中无缝桥接技术

除了version表明版本号(目前都是填0),其他都是函数指针,它们界说了当各种事件产生时应该采用哪个函数来履行相关任务。要害的是需求把CFDictionaryKeyCallBacks从copy改成retain,所以我仿写了一下代码如下:

//调用CFRetain函数来增加键的引证计数,并返回键。这将导致CFDictionary不会复制键,而是保存键的引证计数。
const void* myRetainCallback(CFAllocatorRef allocator, const void *value) {
   return CFRetain(value);
}
void myReleaseCallback(CFAllocatorRef allocator, const void *value) {
  CFRelease(value);
}
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view.
  CFDictionaryKeyCallBacks keyCallbacks = {
    0,
    myRetainCallback,
    myReleaseCallback,
    NULL,
    CFEqual,
    CFHash
  };
  CFDictionaryValueCallBacks valueCallbacks = {
    0,
    myRetainCallback,
    myReleaseCallback,
    NULL,
    CFEqual
  };
    //创立CoreFoundation的字典aCFDictionary
  CFMutableDictionaryRef aCFDictionary = CFDictionaryCreateMutable(NULL, 0, &keyCallbacks, &valueCallbacks);
    //将CoreFoundation的字典aCFDictionary转化成Foundation的字典anNSDictionary
  NSMutableDictionary *anNSDictionary = (__bridge_transfer NSMutableDictionary *)aCFDictionary;
  static MyCustomClass *customKey = NULL;//防止被开释
  customKey = [[MyCustomClass alloc] init];
  [anNSDictionary setObject:@"" forKey:customKey];
}

上面这段代码里,CFDictionaryKeyCallBacks引证我自界说的myRetainCallback来办理键的引证计数,从而来避免要求MyCustomClass完成NSCopying协议。可是需求注意的这种情况下是键是可变的,在修改键时可能会更改键的值,从而使键在字典中的位置变得不确定,因此必须小心运用这种办法。其实最好仍是建议运用不可变键,或者在修改键时运用自界说的回调函数来保证字典中的键始终保持不变。

三、后续

当我认为这样现已完毕的时候,我把我的代码运转了一下,成果仍是报了 -[MyCustomClass copyWithZone:]: unrecognized selector sent to instance 0x600003cd42b0 的过错,不管怎么改都无补于事,莫非网上的材料是过错的吗?我尝试询问ChatGpt,得到以下的答复

关于iOS中无缝桥接技术
我怀疑是objc通过多个版本的迭代和更新,在NSDictionay运转到setObject:forkey:时,并不会预先查看其运转办理语义,并且直接主动寻找这个key类里边的copyWithZone办法,找不到则直接抛出异常,导致运转失利。

四、处理

今天看了一下官方文档,发现NSMutableDictionary是这么界说的

关于iOS中无缝桥接技术
setObject:forkey:的aKey需求遵从NSCopying协议,这肯定会查看到copyWithZone办法的,这与我之前的猜想根本契合,那换个思路,假设我先把数据塞进aCFDictionary,再将aCFDictionary转成anNSDictionary,然后再取出来是否能够呢?先去看看NSMutableDictionary取数据的key是不是也要遵从NSCopying协议

关于iOS中无缝桥接技术
从界说看得出来,KeyType并没有一定要遵从NSCopying协议,所以我修改了一下我的写法,如下

    CFMutableDictionaryRef aCFDictionary = CFDictionaryCreateMutable(NULL, 0, &keyCallbacks, &valueCallbacks);
  static MyCustomClass *customKey = NULL;//防止被开释
  customKey = [[MyCustomClass alloc] init];
    //先塞进去aCFDictionary
  CFDictionarySetValue(aCFDictionary, (__bridge const void *)customKey, (__bridge const void *) @"123");
    //不必这个方案
   //[anNSDictionary setObject:@"" forKey:customKey];
   //看看能不能从aCFDictionary取出来
   NSString *val = (__bridge NSString *)CFDictionaryGetValue(aCFDictionary, (__bridge const void *)customKey);
  NSLog(@"aCFDictionary:%@", val);
   //再转成anNSDictionary
  NSMutableDictionary *anNSDictionary = (__bridge_transfer NSMutableDictionary *)aCFDictionary;
    NSLog(@"==> %@", anNSDictionary);
    //看看能不能从anNSDictionary读出来
  NSLog(@"%@", [anNSDictionary objectForKey:customKey]);

运转一下,成功了!

关于iOS中无缝桥接技术

五、总结

通过反复的查文档,和搭档分享经验,换了个思路终于得出处理的办法,感觉仍是需求不断学习,业精于勤而荒于嬉,欢迎各路大神和我分享评论,感谢!