OC底层之__bridge、__attribute__释义

1,752 阅读5分钟

__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 FoundationFoundationCore Foundation的一层包装,其底层数据结构是一样的,我们可以使用__bridgeCF对象和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_retainedCFBridgingRetainOC指针转为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、classSon类对象
  • 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指定第一个可变参数所在的索引

这里传入的是12表示NSLog的第一个参数必须是NSString字符串,第二个参数开始是可选参数

availability

__attribute__((availability(ios,introduced=2_0,deprecated=7_0,message=""__VA_ARGS__)));
  • ios平台
  • introduced首次出现的版本
  • deprecated要废弃的版本
  • message提示信息

image.png

unavailable

提示这个方法不让用了

- (void)method __attribute__((unavailable("仙人板板")));

image.png

nonnull

参数不能为空提示

- (void)method1:(NSString * _Nonnull)name;
- (void)method2:(NSString *)name __attribute__((nonnull(1)));

image.png

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

黑魔法attribute((cleanup))

objc_runtime_name

在编译时修改类或者协议的名称

__attribute__((objc_runtime_name("Son")))
@interface Person : NSObject
@end

NSLog(@"%@", NSStringFromClass([Person class])); // "Son"

参考文章

# iOS 之bridge

# 关于void*类型的用法(相当于OC中的id类型)

# iOS中bridge的使用

# iOS _attribute_ 的用法总结