__bridge
我们在开发中经常能见到__bridge
今天我们从应用层面了解一下其意义,先看一个小例子,创建自定义类Son
并实现方法method
@implementation Son
- (void)method{
NSLog(@"method");
}
@end
以下代码可以正常运行吗?
int main(int argc, char * argv[]) {
@autoreleasepool {
Class class = [Son class];
void *cf = &class;
[(__bridge id)cf method];
}
return 0;
}
运行结果
method
他是可以正常运行的,我们逐行分析
Class class = [Son class];
// 在底层源码中class的实现为
+ (Class)class {
return self;
}
// 所以class就是类Son
C++语言在对于void* 类型的使用很特别,因为void* 可以间接引用任何其他数据类型的指针,比如int*、float甚至抽象数据类型的指针,而且可以从void 强制转换为任何其他数据类型的指针,所以使用起来有时候会比较危险
可以理解为void *
是一个通用指针,可以指向任何类型,这一行代码的意思就是声明一个指针cf
指向类Son
void *cf = &class;
__bridge
在cocoa
应用中,我们离不开Foundation
同时又经常用到Core Foundation
,Foundation
是Core Foundation
的一层包装,其底层数据结构是一样的,我们可以使用__bridge
在CF
对象和NS
对象之间相互转化
,但是不移交所有权
NSString *str = @"abc";
CFStringRef cfstring = (__bridge CFStringRef)str;
NSLog(@"1 -- %@",cfstring);
NSString *s = (__bridge NSString *)cfstring;
NSLog(@"2 -- %@",s);
执行结果
1 -- abc
2 -- abc
__bridge_retained
__bridge_retained
或CFBridgingRetain
将OC
指针转为CF
指针并且移交所有权
,需要CF
通过CFRelease
来负责对象的释放
NSString *str = @"abc";
CFStringRef cfstring = (__bridge_retained CFStringRef)str;
NSLog(@"1 -- %@",cfstring);
CFRelease(cfstring);
执行结果
1 -- abc
__bridge_transfer
和__bridge_retained
相反,是将CF
指针转为OC
指针并移交所有权
NSString *str = @"abc";
CFStringRef cfstring = (__bridge_retained CFStringRef)str;
NSLog(@"1 -- %@",cfstring);
NSString *s = (__bridge_transfer NSString *)cfstring;
NSLog(@"2 -- %@",s);
执行结果
1 -- abc
2 -- abc
回到上面的例子里面来
[(__bridge id)cf method];
这行代码的意思就是将void *
类型的指针转为id
类型,并执行方法method
,这个小例子的三行代码总结为
- 1、
class
为Son
类对象 - 2、声明通用指针
cf
指向Son
类对象 - 3、将指针
cf
转为id
类型并调用Son
实例方法method
现在的问题就简化为了通过一个指向类对象的指针能不能调用类的实例方法??
我们通过实例对象来调用实例方法的流程是通过实例对象的isa
获取指向类对象的指针,然后开始执行方法查找流程,查到方法了就调用,这里已经绕过了通过isa
获取类指针的过程,所以后面方法查找流程是一样的,是可以正常调用的,nice!!!
慎用
这些用法虽然看起来很秀,但是我们还是慎用,因为他会涉及到所有权的转化问题,会使得我们的内存管理变得更加复杂,稍有不慎可能造成问题,例如下面代码就会出问题
void *p;
{
Son *son = [[Son alloc] init];
// 将OC指针转为CF指针赋值给p
p = (__bridge void *)son;
// 出了作用域OC对象就要释放了
}
// 这里访问空对象会出问题
NSLog(@"%@",p);
如果将__bridge
改为__bridge_retained
就没问题
void *p;
{
Son *son = [[Son alloc] init];
// 将OC指针转为CF指针赋值给p,并将所有权交给CF管理
p = (__bridge_retained void *)son;
// 出了作用域并不会影响p对象
}
// 这里仍然可以正常访问
NSLog(@"%@",p);
执行结果
<Son: 0x281210150>
attribute
__attribute__
是一个编译器指令,可以帮助我们在编译阶段排查更多的错误,格式__attribute__(参数)
,这里我们只是简单探索其含义,再见到时知道什么意思也就是了。
format(格式检查)
以NSLog
为例
FOUNDATION_EXPORT void NSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2) NS_NO_TAIL_CALL;
#define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A)))
format
参数为
format (archetype, string-index, first-to-check)
archetype
指定哪种风格,这里是NSString
string-index
指定传入的第几个参数是格式化字符串first-to-check
指定第一个可变参数所在的索引
这里传入的是1
和2
表示NSLog
的第一个参数必须是NSString
字符串,第二个参数开始是可选参数
availability
__attribute__((availability(ios,introduced=2_0,deprecated=7_0,message=""__VA_ARGS__)));
ios
平台introduced
首次出现的版本deprecated
要废弃的版本message
提示信息
unavailable
提示这个方法不让用了
- (void)method __attribute__((unavailable("仙人板板")));
nonnull
参数不能为空提示
- (void)method1:(NSString * _Nonnull)name;
- (void)method2:(NSString *)name __attribute__((nonnull(1)));
constructor/ destructor
这是c++
函数的构造函数和析构函数
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"main");
}
return 0;
}
__attribute__((constructor(1))) void methodStart1(){
NSLog(@"构造函数1");
}
__attribute__((destructor(1))) void methodEnd1(){
NSLog(@"析构函数1");
}
__attribute__((constructor(2))) void methodStart2(){
NSLog(@"构造函数2");
}
__attribute__((destructor(2))) void methodEnd2(){
NSLog(@"析构函数2");
}
在Son
类中实现load
方法
@implementation Son
+(void)load{
NSLog(@"load");
}
@end
执行结果
load
构造函数1
构造函数2
main
析构函数2
析构函数1
构造函数在main
函数之前执行,析构函数在main
函数之后执行,后面的数字代表执行优先级,
- 数值越小优先级越高,执行的早,释放的晚
- 数值越大优先级越低,执行的晚,释放的早
load
函数比构造函数执行的早
原因:dyld(动态链接器,程序的最初起点)在加载 image(可以理解成 Mach-O 文件)时会先通知 objc runtime 去加载其中所有的类,每加载一个类时,它的 +load 随之调用,全部加载完成后,dyld 才会调用这个 image 中所有的 constructor 方法,然后才调用main函数.
cleanup
objc_runtime_name
在编译时修改类或者协议的名称
__attribute__((objc_runtime_name("Son")))
@interface Person : NSObject
@end
NSLog(@"%@", NSStringFromClass([Person class])); // "Son"