attribute是GNU C特色之一,在iOS用的比较广泛.系统中有许多地方使用到. attribute可以设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute)等.
函数属性(Function Attribute)
- noreturn
- noinline
- always_inline
- pure
- const
- nothrow
- sentinel
- format
- format_arg
- no_instrument_function
- section
- constructor
- destructor
- used
- unused
- deprecated
- weak
- malloc
- alias
- warn_unused_result
-
类型属性(Type Attributes)
aligned
- packed
- transparent_union,
- unused,
- deprecated
-
变量属性(Variable Attribute)
aligned
-
Clang特有的
availability
- overloadable
书写格式
书写格式:attribute后面会紧跟一对原括弧,括弧里面是相应的attribute参数__attribute__(xxx)
常见的系统用法
format
官方例子:NSLog
format属性可以给被声明的函数加上类似printf或者scanf的特征,它可以使编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配。该功能十分有用,尤其是处理一些很难发现的bug。对于format参数的使用如下#define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A)))
format (archetype, string-index, first-to-check)
第一参数需要传递“archetype”指定是哪种风格,这里是 NSString;“string-index”指定传入函数的第几个参数是格式化字符串;“first-to-check”指定第一个可变参数所在的索引.noreturn
官方例子: abort() 和 exit()
该属性通知编译器函数从不返回值。当遇到类似函数还未运行到return语句就需要退出来的情况,该属性可以避免出现错误信息。availability
官方例子:
``` (CGSize)sizeWithFont:(UIFont *)font NS_DEPRECATED_IOS(2_0, 7_0, “Use -sizeWithAttributes:”) __TVOS_PROHIBITED; //来看一下 后边的宏
define NSDEPRECATEDIOS(_iosIntro, _iosDep, …) CF_DEPRECATED_IOS(_iosIntro, _iosDep, __VA_ARGS)
define CFDEPRECATEDIOS(iosIntro, iosDep, …) attribute((availability(ios,introduced=iosIntro,deprecated=iosDep,message=”” __VA_ARGS))) //宏展开以后如下 __attribute((availability(ios,introduced=2_0,deprecated=7_0,message=””__VA_ARGS))); //ios即是iOS平台 //introduced 从哪个版本开始使用 //deprecated 从哪个版本开始弃用 //message 警告的消息 ``` availability属性是一个以逗号为分隔的参数列表,以平台的名称开始,包含一些放在附加信息里的一些里程碑式的声明。
introduced:第一次出现的版本。
- deprecated:声明要废弃的版本,意味着用户要迁移为其他API
- obsoleted: 声明移除的版本,意味着完全移除,再也不能使用它
- unavailable:在这些平台不可用
- message:一些关于废弃和移除的额外信息,clang发出警告的时候会提供这些信息,对用户使用替代的API非常有用。
- 这个属性支持的平台:ios,macosx。
简单例子:
//如果经常用,建议定义成类似系统的宏
- (void)oldMethod:(NSString *)string __attribute__((availability(ios,introduced=2_0,deprecated=7_0,message="用 -newMethod: 这个方法替代 "))){
NSLog(@"我是旧方法,不要调我");
}
- (void)newMethod:(NSString *)string{
NSLog(@"我是新方法");
}
效果:
Paste_Image.png
//如果调用了,会有警告
unavailable
告诉编译器该方法不可用,如果强行调用编译器会提示错误。比如某个类在构造的时候不想直接通过init来初始化,只能通过特定的初始化方法()比如单例,就可以将init方法标记为unavailable;
//系统的宏,可以直接拿来用
#define UNAVAILABLE_ATTRIBUTE __attribute__((unavailable))
#define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE
@interface Person : NSObject
@property(nonatomic,copy) NSString *name;
@property(nonatomic,assign) NSUInteger age;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age;
@end
Paste_Image.png
//实际上unavailable后面可以跟参数,显示一些信息,如:
//系统的
#define NS_AUTOMATED_REFCOUNT_UNAVAILABLE __attribute__((unavailable("not available in automatic reference counting mode")))
objc_root_class
表示这个类是一个根类(基类),比如NSObject,NSProxy.
//摘自系统
//NSProxy
NS_ROOT_CLASS
@interface NSProxy <NSObject> {
Class isa;
}
//NSObject
__OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0)
OBJC_ROOT_CLASS
OBJC_EXPORT
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
NSObject
@property (nonatomic,strong) __attribute__((NSObject)) CFDictionaryRef myDictionary;
CFDictionaryRef属于CoreFoundation框架的,也就是非OC对象,加上attribute((NSObject))后,myDictionary的内存管理会被当做OC对象来对待.
objc_designated_initializer
用来修饰类的designated initializer初始化方法,如果修饰的方法里没有调用super类的 designated initializer,编译器会发出警告。可以简写成NS_DESIGNATED_INITIALIZER
这篇文章讲的很好,建议参考这个.
https://yq.aliyun.com/articles/5847
visibility
语法:
__attribute__((visibility("visibility_type")))
其中,visibility_type 是下列值之一:
- default
假定的符号可见性可通过其他选项进行更改。缺省可见性将覆盖此类更改。缺省可见性与外部链接对应。 - hidden
该符号不存放在动态符号表中,因此,其他可执行文件或共享库都无法直接引用它。使用函数指针可进行间接引用。 - internal
除非由 特定于处理器的应用二进制接口 (psABI) 指定,否则,内部可见性意味着不允许从另一模块调用该函数。 - protected
该符号存放在动态符号表中,但定义模块内的引用将与局部符号绑定。也就是说,另一模块无法覆盖该符号。 - 除指定 default 可见性外,此属性都可与在这些情况下具有外部链接的声明结合使用。
您可在 C 和 C++ 中使用此属性。在 C++ 中,还可将它应用于类型、成员函数和命名空间声明。
系统用法:
// UIKIT_EXTERN extern
#ifdef __cplusplus
#define UIKIT_EXTERN extern "C" __attribute__((visibility ("default")))
#else
#define UIKIT_EXTERN extern __attribute__((visibility ("default")))
#endif
nonnull
编译器对函数参数进行NULL的检查,参数类型必须是指针类型(包括对象)
//使用
- (int)addNum1:(int *)num1 num2:(int *)num2 __attribute__((nonnull (1,2))){//1,2表示第一个和第二个参数不能为空
return *num1 + *num2;
}
- (NSString *)getHost:(NSURL *)url __attribute__((nonnull (1))){//第一个参数不能为空
return url.host;
}
常见用法
aligned
__attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐.例如:
不加修饰的情况
typedef struct
{
char member1;
int member2;
short member3;
}Family;
//输出字节:
NSLog(@"Family size is %zd",sizeof(Family));
//输出结果为:
2016-07-25 10:28:45.380 Study[917:436064] Family size is 12
//修改字节对齐为1
typedef struct
{
char member1;
int member2;
short member3;
}__attribute__ ((aligned (1))) Family;
//输出字节:
NSLog(@"Family size is %zd",sizeof(Family));
//输出结果为:
2016-07-25 10:28:05.315 Study[914:435764] Family size is 12
和上面的结果一致,因为 设定的字节对齐为1.而结构体中成员的最大字节数是int 4个字节,1 < 4,按照4字节对齐,和系统默认一致.
修改字节对齐为8
typedef struct
{
char member1;
int member2;
short member3;
}__attribute__ ((aligned (8))) Family;
//输出字节:
NSLog(@"Family size is %zd",sizeof(Family));
//输出结果为:
2016-07-25 10:28:05.315 Study[914:435764] Family size is 16
这里 8 > 4,按照8字节对齐,结果为16,不知道字节对齐的可以看我的这篇文章http://www.jianshu.com/p/f69652c7df99
可是想了半天,也不知道这玩意有什么用,设定值小于系统默认的,和没设定一样,设定大了,又浪费空间,效率也没提高,感觉学习学习就好.
packed
让指定的结构结构体按照一字节对齐,测试:
//不加packed修饰
typedef struct {
char version;
int16_t sid;
int32_t len;
int64_t time;
} Header;
//计算长度
NSLog(@"size is %zd",sizeof(Header));
输出结果为:
2016-07-22 11:53:47.728 Study[14378:5523450] size is 16
可以看出,默认系统是按照4字节对齐
//加packed修饰
typedef struct {
char version;
int16_t sid;
int32_t len;
int64_t time;
}__attribute__ ((packed)) Header;
//计算长度
NSLog(@"size is %zd",sizeof(Header));
输出结果为:
2016-07-22 11:57:46.970 Study[14382:5524502] size is 15
用packed修饰后,变为1字节对齐,这个常用于与协议有关的网络传输中.
noinline & always_inline
内联函数:内联函数从源代码层看,有函数的结构,而在编译后,却不具备函数的性质。内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。编译时,类似宏替换,使用函数体替换调用处的函数名。一般在代码中用inline修饰,但是能否形成内联函数,需要看编译器对该函数定义的具体处理
- noinline 不内联
- always_inline 总是内联
- 这两个都是用在函数上
内联的本质是用代码块直接替换掉函数调用处,好处是:快代码的执行,减少系统开销.适用场景:
- 这个函数更小
- 这个函数不被经常调用
使用例子:
//函数声明
void test(int a) __attribute__((always_inline));
warn_unused_result
当函数或者方法的返回值很重要时,要求调用者必须检查或者使用返回值,否则编译器会发出警告提示
- (BOOL)availiable __attribute__((warn_unused_result))
{
return 10;
}
objc_subclassing_restricted
因为某些原因,我们不希望这个类被继承,也就是 “最终”的类,用法如下:
__attribute__((objc_subclassing_restricted))
@interface ViewController : UIViewController
@end
如果继承了这个类,编译器会报错
Paste_Image.png
objc_requires_super
这个属性要求子类在重写父类的方法时,必须要重载父类方法,也就是调用super方法,否则警告.示例如下:
@interface ViewController : UIViewController
- (void)jump __attribute__((objc_requires_super));
@end
- (void)jump{
NSLog(@"父类必须先执行");
}
@interface SGViewController : ViewController
@end
@implementation SGViewController
- (void)jump{
NSLog(@"子类才能再执行");
}
@end
objc_boxable
实现类似于NSNumber 的快速打包能力@(…),一般对于struct,union我们只能通过NSValue将其打包. objc_boxable 可以帮助我们实现快速打包,示例如下:
//自定义结构体
typedef struct __attribute__((objc_boxable)){
CGFloat x,y,width,height;
}SGRect;
SGRect rect = {0,0,100,200};
//这里直接打包成NSValue
NSValue *value = @(rect);
//这里我直接用系统的方法打印
NSLog(@"%@",NSStringFromCGRect(value.CGRectValue));
输出:
2016-07-21 21:28:43.538 Study[14118:5408921] {{0, 0}, {100, 200}}
constructor / destructor
意思是: 构造器和析构器;constructor修饰的函数会在main函数之前执行,destructor修饰的函数会在程序exit前调用.
示例如下:
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"main");
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
__attribute__((constructor))
void before(){
NSLog(@"before main");
}
__attribute__((destructor))
void after(){
NSLog(@"after main");
}
//在viewController中调用exit
- (void)viewDidLoad {
[super viewDidLoad];
exit(0);
}
输出如下:
2016-07-21 21:49:17.446 Study[14162:5415982] before main
2016-07-21 21:49:17.447 Study[14162:5415982] main
2016-07-21 21:49:17.534 Study[14162:5415982] after main
注意点:
- 程序退出的时候才会调用after函数,经测试,手动退出程序会执行
- 上面两个函数不管写在哪个类里,哪个文件中效果都一样
- 如果存在多个修饰的函数,那么都会执行,顺序不定
实际上如果存在多个修饰过的函数,可以它们的调整优先级
代码如下:
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"main");
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
__attribute__((constructor(101)))
void before1(){
NSLog(@"before main - 1");
}
__attribute__((constructor(102)))
void before2(){
NSLog(@"before main - 2");
}
__attribute__((destructor(201)))
void after1(){
NSLog(@"after main - 1");
}
__attribute__((destructor(202)))
void after2(){
NSLog(@"after main - 2");
}
输出结果如下:
2016-07-21 21:59:35.622 Study[14171:5418393] before main - 1
2016-07-21 21:59:35.624 Study[14171:5418393] before main - 2
2016-07-21 21:59:35.624 Study[14171:5418393] main
2016-07-21 21:59:35.704 Study[14171:5418393] after main - 2
2016-07-21 21:59:35.704 Study[14171:5418393] after main - 1
注意点:
- 括号内的值表示优先级,[0,100]这个返回时系统保留的,自己千万别调用.
- 根据输出结果可以看出,main函数之前的,数值越小,越先调用;main函数之后的数值越大,越先调用.
当函数声明和函数实现分开写时,格式如下:
static void before() __attribute__((constructor));
static void before() {
printf("before\n");
}
讨论:+load,constructor,main的执行顺序,代码如下:
+ (void)load{
NSLog(@"load");
}
__attribute__((constructor))
void before(){
NSLog(@"before main");
}
输出结果如下:
2016-07-21 22:13:58.591 Study[14185:5421811] load
2016-07-21 22:13:58.592 Study[14185:5421811] before main
2016-07-21 22:13:58.592 Study[14185:5421811] main
可以看出执行顺序为:
load->constructor->main
为什么呢?
因为 dyld(动态链接器,程序的最初起点)在加载 image(可以理解成 Mach-O 文件)时会先通知 objc runtime 去加载其中所有的类,每加载一个类时,它的 +load 随之调用,全部加载完成后,dyld 才会调用这个 image 中所有的 constructor 方法,然后才调用main函数.
enable_if
用来检查参数是否合法,只能用来修饰函数:
void printAge(int age)
__attribute__((enable_if(age > 0 && age < 120, "你丫太监?")))
{
NSLog(@"%d",age);
}
表示只能输入的参数只能是 0 ~ 120左右,否则编译报错
报错如下:
Paste_Image.png
cleanup
声明到一个变量上,当这个变量作用域结束时,调用指定的一个函数.如果不知道什么是作用域,请先学习一下.例子:
//这里传递的参数是变量的地址
void intCleanup(int *num){
NSLog(@"cleanup------%d",*num);
}
- (void)test{
int a __attribute__((cleanup(intCleanup))) = 10;
}
输出结果为:
2016-07-22 09:59:09.139 Study[14293:5495713] cleanup------10
注意点:
- 指定的函数传递的参数是变量的地址
- 作用域的结束包括:大括号结束、return、goto、break、exception等情况
- 当作用域内有多个cleanup的变量时,遵守 先入后出 的栈式结构.
示例代码:
void intCleanup(int *num){
NSLog(@"cleanup------%d",*num);
}
void stringCleanup(NSString **str){
NSLog(@"cleanup------%@",*str);
}
void rectCleanup(CGRect *rect){
CGRect temp = *rect;
NSString *str = NSStringFromCGRect(temp);
NSLog(@"cleanup------%@",str);
}
int a __attribute__((cleanup(intCleanup))) = 10;
{
NSString *string __attribute__((cleanup(stringCleanup))) = @"string";
CGRect rect __attribute__((cleanup(rectCleanup))) = {0,0,1,1};
}
输出结果为:
2016-07-22 10:09:36.621 Study[14308:5498861] cleanup------{{0, 0}, {1, 1}}
2016-07-22 10:09:36.622 Study[14308:5498861] cleanup------string
2016-07-22 10:09:36.622 Study[14308:5498861] cleanup------10
讨论:如果修饰了某个对象,那么cleanup和dealloc,谁先执行?
测试代码如下:
void objectCleanup(NSObject **obj){
NSLog(@"cleanup------%@",*obj);
}
- (void)viewDidLoad {
[super viewDidLoad];
ViewController *vc __attribute__((cleanup(objectCleanup))) = [[ViewController alloc] init];
}
- (void)dealloc{
NSLog(@"dealloc");
}
输出结果如下:
2016-07-22 10:23:08.839 Study[14319:5502769] cleanup------<ViewController: 0x13fe881e0>
2016-07-22 10:23:08.840 Study[14319:5502769] dealloc
可以明显看出,cleanup先于对象的dealloc执行.
- 在block中的用法:在block中使用,先看例子:
//指向block的指针,觉得不好理解可以用typeof
void blockCleanUp(void(^*block)()){
(*block)();
}
void (^block)(void) __attribute__((cleanup(blockCleanUp))) = ^{
NSLog(@"finish block");
};
这个好处就是,不用等到block最后才写某些代码,我们可以把它放在block的任意位置,防止忘记.
overloadable
用于c语言函数,可以定义若干个函数名相同,但参数不同的方法,调用时编译器会自动根据参数选择函数原型:
__attribute__((overloadable)) void print(NSString *string){
NSLog(@"%@",string);
}
__attribute__((overloadable)) void print(int num){
NSLog(@"%d",num);
}
//调用
print(10);
print(@"哈哈");
objc_runtime_name
看到runtime是不是就感觉高大上,没错这个也跟运行时有关.作用是将将类或协议的名字在编译时指定成另一个.示例如下:
__attribute__((objc_runtime_name("NSObject")))
@interface SGObject :NSObject
@end
//调用
NSLog(@"%@",[SGObject class]);
//输出
2016-07-22 11:18:00.934 Study[14355:5516261] NSObject
可以用来做代码混淆.
更多请看官网:
https://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Function-Attributes.html
作者:iOSSingger
链接:https://www.jianshu.com/p/29eb7b5c8b2d
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。