clang中的特性之__attribute__
__attribute__ 可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。
其位置约束为: 放于声明的尾部“;” 之前
__attribute__ 书写特征为: __attribute__ 前后都有两个下划线,并且后面会紧跟一对原括弧,括弧里面是相应的__attribute__ 参数。
__attribute__ 语法格式为: __attribute__ ((attribute-list))
分类:
- 函数属性(Function Attributes)
- 变量属性(Variable Attributes)
- 类型属性 ( Type Attributes)
举例:
unused的使用:
map变量由于是被unused修饰,所以编译器不会警告⚠️:Unused variable 'map'
在介绍一个cleanup变量属性的运用:
cleanup (cleanup_function) The cleanup attribute runs a function when the variable goes out of scope. This attribute can only be applied to auto function scope variables; it may not be applied to parameters or variables with static storage duration. The function must take one parameter, a pointer to a type compatible with the variable. The return value of the function (if any) is ignored. If -fexceptions is enabled, then cleanup_function will be run during the stack unwinding that happens during the processing of the exception. Note that the cleanup attribute does not allow the exception to be caught, only to perform an action. It is undefined what happens if cleanup_function does not return normally.
意思就是:当被修饰的变量超出作用域时,cleanup属性将运行一个函数。此属性只能应用于自动作用域变量;它不适用于具有静态存储持续时间的参数或变量。函数必须带有一个参数,一个指向与变量兼容的类型的指针。函数的返回值(如果有)被忽略。
这里拿ReactiveCocoa中的@onExit{}中的实现原理,来介绍:
#define onExit \ rac_keywordify \ __strong rac_cleanupBlock_t metamacro_concat(rac_exitBlock_, __LINE__) __attribute__((cleanup(rac_executeCleanupBlock), unused)) = ^ typedef void (^rac_cleanupBlock_t)(void); static inline void rac_executeCleanupBlock (__strong rac_cleanupBlock_t *block) { (*block)(); }
当我们添加@onExit{ NSLog(@"var exit scope."); },可以分解成如下代码:
_strong rac_cleanupBlock_t rac_exitBlock_10 = __attribute__((cleanup(rac_executeCleanupBlock), unused)) = ^{ NSLog(@"var exit scope."); }
这样当rac_exitBlock_10退出作用域的时候,cleanup将要被调用rac_executeCleanupBlock函数,并将rac_exitBlock_10参数传入给rac_executeCleanupBlock函数。
这样就实现了在Swift中的defer
func test() { defer { print("exit scope.") } print("excute finish") }
可以用来修饰变量,方法,类和协议,表明被废弃,如果使用,编译器会发出警告。可以添加说明,用法__attribute__((deprecated("use Another class.")))。
__attribute__((unavailable))
可以用来修饰变量,方法,类和协议,表明不可用,如果使用,编译器会发出错误。同deprecated,可以添加说明。
用来修饰属性,表明Core Fundation类型的属性应该按照Objective-C的对象来进行内存管理。比如 @property(retain) __attribute__((NSObject)) CFDictionaryRef myDictionary;
__attribute__((objc_subclassing_restricted))
标记指定类不能有子类,例如:
#define FinalClass __attribute__((objc_subclassing_restricted)) NS_ASSUME_NONNULL_BEGIN FinalClass @interface MySupper : NSObject @end NS_ASSUME_NONNULL_END
如果子类继承了该类,将提示编译器错误:
__attribute__((objc_requires_super))
指定父类方法在被重写时,必须调用supper实现父类方法调用。
@interface BaseTemplate : NSObject - (void)sendMessage:(NSString *)msg __attribute__((objc_requires_super)); @end
/// 定义一个表示线的结构体,使之允许通过@(...)语法糖,转成NSValue对象 typedef struct __attribute__((objc_boxable)) { CGPoint start, end; } DrLine; /// NSValue增加DrLine类型转换的分类,使之可以方便的将NSValue转成DrLine类型 @interface NSValue (line) - (DrLine)lineValue; @end @implementation NSValue (line) - (DrLine)lineValue { DrLine line; if (@available(iOS 11.0, *)) { [self getValue:&line size:sizeof(DrLine)]; }else{ [self getValue:&line]; } return line; } @end /// 测试代码如下: DrLine line; line.start = CGPointMake(10, 10); line.end = CGPointMake(100, 100); NSValue *val = @(line); // objc_boxable DrLine myLine = [val lineValue]; // 封装的分类方法 NSLog(@"{{x: %.2f, y: %.2f}, {x: %.2f, y: %.2f}}", myLine.start.x, myLine.start.y, myLine.end.x, myLine.end.y);
__attribute__((enable_if(条件表达式,"错误描述"))):
仅用于修饰函数,告诉编译器,此函数的某个参数,必须满足某个条件,才可以编译通过。
static int _age = 0; /// 通过enable_if,来限制输入参数age的范围,如果不在次范围内,编译器将报错 void setAge(int age) __attribute__((enable_if(age > 0 && age < 130, "0 < age < 130"))) { _age = age; }
__attribute__((overloadable)):
仅用于修饰C函数,使之允许方法重载(即:允许方法名相同,但参数不同的函数存在)
/// 通过overloadable修饰该函数,使之允许实现方法重载 NSValue * convert(CGSize size) __attribute__((overloadable)) { return [NSValue valueWithCGSize:size]; } /// 通过overloadable修饰该函数,使之允许实现方法重载 NSValue * convert(CGPoint point) __attribute__((overloadable)) { return [NSValue valueWithCGPoint:point]; }
/// 通过objc_runtime_name修改UserInfo类在运行时的名称 __attribute__((objc_runtime_name("abcdefg"))) @interface UserInfo : NSObject @end @implementation UserInfo @end UserInfo *info = [[UserInfo alloc] init]; NSLog(@"info.class: %@", NSStringFromClass(info.class)); // 输出结果:info.class: abcdefg
我们从demo中看到,在运行时输出的UserInfo的类名变成了abcdefg,这样做的好处有助于保护我们的类,使之在运行时无法被第三方通过runtime来修改我们的类,或hook类方法,因为其Class名称已经不是UserInfo了,只要对这个名称采用一个随机数生成,那就相当于对这个类进行混淆了。如下:
Class cls = NSClassFromString(@"UserInfo"); if (cls) { NSLog(@"对UserInfo方法进行hook"); }else{ NSLog(@"UserInfo 不存在"); // 这里将被输出 }