【转】__attribute__详解及应用

转载链接:https://www.jianshu.com/p/965f6f903114

之前做过App的启动优化,遇到了+load优化的问题,后来想一想除了initializers代替+load还有没有什么好的方法,然后就搜到了运用编译属性__attribute__优化,于是查找了很多文章,系统的整理了下__attribute__。本文大部分内容来自引用的文章,如果想看更多更详细内容可以查看引用文章。

__attribute__ 介绍

__attribute__是一个编译属性,用于向编译器描述特殊的标识、错误检查或高级优化。它是GNU C特色之一,系统中有许多地方使用到。__attribute__可以设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute)等。

__attribute__ 格式

__attribute__((attribute-list))

__attribute__ 常用的编译属性及简单应用

format

这个属性指定一个函数比如printf,scanf作为参数,这使编译器能够根据代码中提供的参数检查格式字符串。对于追踪难以发现的错误非常有帮助。

format参数的使用如下:

format(archetype,string-index,first-to-check)

第一参数需要传递archetype指定是哪种风格,这里是 NSString;string-index指定传入函数的第几个参数是格式化字符串;first-to-check指定第一个可变参数所在的索引.

C中的使用方法

externintmy_printf(void*my_object,constchar*my_format,...)__attribute__((format(printf,2,3)));

在Objective-C 中通过使用__NSString__格式达到同样的效果,就像在NSString +stringWithFormat:和NSLog()里使用字符串格式一样

FOUNDATION_EXPORTvoidNSLog(NSString*format,...)NS_FORMAT_FUNCTION(1,2);+(instancetype)stringWithFormat:(NSString*)format,...NS_FORMAT_FUNCTION(1,2);

__attribute__((constructor))

确保此函数在 在main函数被调用之前调用,iOS中在+load之后main之前执行。

constructor和destructor会在ELF文件中添加两个段-.ctors和.dtors。当动态库或程序在加载时,会检查是否存在这两个段,如果存在执行对应的代码。

__attribute__((constructor))staticvoidbeforeMain(void){NSLog(@"beforeMain");}

__attribute__((constructor(101)))// 里面的数字越小优先级越高,1 ~ 100 为系统保留

__attribute__((destructor))

__attribute__((destructor))staticvoidafterMain(void){NSLog(@"afterMain");}

确保此函数在 在main函数被调用之后调

__attribute__((cleanup))

用于修饰一个变量,在它的作用域结束时可以自动执行一个指定的方法

关于这个Sunny黑魔法__attribute__((cleanup))中讲的很好很细,建议看看。

iOS中的应用

既然__attribute__((cleanup(...)))可以用来修饰变量,所以也可以用来修饰block

// void(^block)(void)的指针是void(^*block)(void)staticvoidblockCleanUp(__strongvoid(^*block)(void)){(*block)();}

这里不得不提万能的Reactive Cocoa中神奇的@onExit方法,其实正是上面的写法,简单定义个宏:

#defineonExit\__strongvoid(^block)(void)__attribute__((cleanup(blockCleanUp),unused))=^

这样的写法可以将成对出现的代码写在一起,比如说一个lock,用了onExit之后,代码更集中了:

NSRecursiveLock*aLock=[[NSRecursiveLock alloc]init];[aLock lock];onExit{[aLock unlock];};

当我看到这段代码的时候第一个想到就是Swift中defer关键字

lock.lock();defer{lock.unlock()}

used

used的作用是告诉编译器,我声明的这个符号是需要保留的。被used修饰以后,意味着即使函数没有被引用,在Release下也不会被优化。如果不加这个修饰,那么Release环境链接器会去掉没有被引用的段。gun的官方文档

This attribute, attached to a variable with static storage, means that the variable must be emitted even if it appears that the variable is not referenced.

When applied to a static data member of a C++ class template, the attribute also means that the member is instantiated if the class itself is instantiated.

iOS中的运用,BeeHive中的一段代码。

#defineBeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" ")))

nonnull

这个属性指定函数的的某些参数不能是空指针

externvoid*my_memcpy(void*dest,constvoid*src,size_t len)__attribute__((nonnull(1,2)));

iOS中的应用

-(int)addNum1:(int*)num1 num2:(int*)num2__attribute__((nonnull(1,2))){//1,2表示第一个和第二个参数不能为空return*num1+*num2;}-(NSString*)getHost:(NSURL*)url__attribute__((nonnull(1))){//第一个参数不能为空returnurl.host;}

objc_runtime_name

用于@interface或@protocol,将类或协议的名字在编译时指定成另一个

__attribute__((objc_runtime_name("<#OtherClassName#>")))

iOS中的应用

__attribute__((objc_runtime_name("OtherTest")))@interfaceTest:NSObject@endNSLog(@"%@",NSStringFromClass([Test class]));// "OtherTest"

这个属性可以用来做代码混淆

noreturn

几个标注库函数,例如abort exit,没有返回值。GCC能够自动识别这种情况。noreturn属性指定像这样的任何不需要返回值的函数。当遇到类似函数还未运行到return语句就需要退出来的情况,该属性可以避免出现错误信息。

iOS中的运用

AFNetworking库为它的网络请求显示入口函数使用了该属性。这个在生成一个专用的线程时使用,保证分离的线程能在应用的整个生命周期继续执行

+(void)__attribute__((noreturn))networkRequestThreadEntryPoint:(id)__unused object{do{@autoreleasepool{[[NSRunLoop currentRunLoop]run];}}while(YES);}

noinline & always_inline

内联函数:内联函数从源代码层看,有函数的结构,而在编译后,却不具备函数的性质。内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。编译时,类似宏替换,使用函数体替换调用处的函数名。一般在代码中用inline修饰,但是能否形成内联函数,需要看编译器对该函数定义的具体处理

noinline不内联

always_inline总是内联

这两个都是用在函数上

内联的本质是用代码块直接替换掉函数调用处,好处是:快代码的执行,减少系统开销.适用场景:

这个函数更小

这个函数不被经常调用

voidtest(inta)__attribute__((always_inline));

这个在Swift有类似用法

extensionNSLock{@inline(__always)funcexecuteWithLock(_block:()->Void){lock()block()unlock()}}

warn_unused_result

当函数或者方法的返回值很重要时,要求调用者必须检查或者使用返回值,否则编译器会发出警告提示。

-(BOOL)availiable__attribute__((warn_unused_result)){return10;}

在Swift中应该是几乎所有方法都是warn_unused_result,可以通过@discardableResult去掉警告提示。

Clang特有的

就像GCC的许多特性一样,Clang支持__attribute__,而且添加了一些自己的小扩展。为了检查一个特殊属性的可用性,你可以使用__has_attribute指令。

availability

Clang引入了可用性属性,这个属性可以在声明中描述跟系统版本有关的生命周期。例如:

官方例子

-(CGSize)sizeWithFont:(UIFont*)fontNS_DEPRECATED_IOS(2_0,7_0,"Use -sizeWithAttributes:")__TVOS_PROHIBITED;//来看一下 后边的宏#defineNS_DEPRECATED_IOS(_iosIntro, _iosDep, ...) CF_DEPRECATED_IOS(_iosIntro, _iosDep, __VA_ARGS__)defineCF_DEPRECATED_IOS(_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    警告的消息

introduced: 声明被引入的第一个版本信息。

deprecated: 第一次不建议使用的版本,意味着使用者应该移除这个方法的使用。

obsoleted: 第一次被废弃的版本,意味着已经被移除,不能够使用了。

unavailable: 意味着这个平台不支持使用。

message: 当Clang发出一些关于废弃或不建议使用的警告时的文本。用于引导使用者不要使用改接口了。

支持的平台有:

ios: 苹果的iOS操作系统。最小部署目标平台版本是通过-mios-version-min=*version*或-miphoneos-version-min=*version*命令行指定的。

macosx: 苹果的OS X操作系统。最小部署目标平台版本是通过-mmacosx-version-min=*version*命令行指定的。

//如果经常用,建议定义成类似系统的宏-(void)oldMethod:(NSString*)string__attribute__((availability(ios,introduced=2_0,deprecated=7_0,message="用 -newMethod: 这个方法替代 "))){NSLog(@"我是旧方法,不要调我");}-(void)newMethod:(NSString*)string{NSLog(@"我是新方法");}

在swift中也有类似的用法

@available(iOS6.0,*)publicvarminimumScaleFactor:CGFloat// default is 0.0

unavailable

告诉编译器该方法不可用,如果强行调用编译器会提示错误。比如某个类在构造的时候不想直接通过init来初始化,只能通过特定的初始化方法()比如单例,就可以将init方法标记为unavailable。

//系统的宏,可以直接拿来用#defineUNAVAILABLE_ATTRIBUTE __attribute__((unavailable))#defineNS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE

@interfacePerson:NSObject@property(nonatomic,copy)NSString*name;@property(nonatomic,assign)NSUInteger age;-(instancetype)init NS_UNAVAILABLE;-(instancetype)initWithName:(NSString*)name age:(NSUInteger)age;@end

实际上unavailable后面可以跟参数,显示一些信息,如:

//系统的#defineNS_AUTOMATED_REFCOUNT_UNAVAILABLE __attribute__((unavailable("not available in automatic reference counting mode")))

overloadable

Clang在C中提供对C++标准函数重载的支持。函数重载在C中是通过overloadable属性引入的。例如:你可以重载tgsin函数,写出sin函数在入参不同时的不同版本。用于c语言函数,可以定义若干个函数名相同,但参数不同的方法,调用时编译器会自动根据参数选择函数原型。

__attribute__((overloadable))voidprint(NSString*string){NSLog(@"%@",string);}__attribute__((overloadable))voidprint(intnum){NSLog(@"%d",num);}

__attribute__ 在iOS开发中的复杂应用

说了这么多重点来了,那么这些属性在iOS上有哪些奇妙的运用呢?有些比较简单的运用在介绍属性的时候就说了,这里主要讲一些比较复杂的运用。

Swift没有+load方法的替代带方案

staticvoid__attribute__((constructor))Initer(){Class class=NSClassFromString(@"AnnotationDemo.MyInitThingy");SEL selector=NSSelectorFromString(@"appWillLaunch:");NSNotificationCenter*center=[NSNotificationCenter defaultCenter];[center addObserver:class              selector:selector                  name:UIApplicationDidFinishLaunchingNotification                object:nil];}

classMyInitThingy:NSObject{@objcstaticfuncappWillLaunch(_:Notification){print("App Will Launch")}}

BeeHive模块注册

模块注册有三种方式:Annotation方式注册、读取本地plist方式注册、Load方法注册。

首先把数据放在可执行文件的自定义数据段

// 通过BeeHiveMod宏进行Annotation标记#ifndefBeehiveModSectName#defineBeehiveModSectName "BeehiveMods"#endif#ifndefBeehiveServiceSectName#defineBeehiveServiceSectName "BeehiveServices"#endif#defineBeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" ")))// 这里我们就把数据存在data数据段里面的"BeehiveMods"段中#defineBeeHiveMod(name) \class BeeHive; char * k##name##_mod BeeHiveDATA(BeehiveMods) = ""#name"";#defineBeeHiveService(servicename,impl) \class BeeHive; char * k##servicename##_service BeeHiveDATA(BeehiveServices) = "{ \""#servicename"\" : \""#impl"\"}";@interfaceBHAnnotation:NSObject@end

从Mach-O section中读取数据

SArray<NSString*>*BHReadConfiguration(char*sectionName,conststructmach_header*mhp);staticvoiddyld_callback(conststructmach_header*mhp,intptr_t vmaddr_slide){NSArray*mods=BHReadConfiguration(BeehiveModSectName,mhp);for(NSString*modNameinmods){Class cls;if(modName){cls=NSClassFromString(modName);if(cls){[[BHModuleManager sharedManager]registerDynamicModule:cls];}}}//register servicesNSArray<NSString*>*services=BHReadConfiguration(BeehiveServiceSectName,mhp);for(NSString*mapinservices){NSData*jsonData=[map dataUsingEncoding:NSUTF8StringEncoding];NSError*error=nil;id json=[NSJSONSerialization JSONObjectWithData:jsonData options:0error:&error];if(!error){if([json isKindOfClass:[NSDictionary class]]&&[json allKeys].count){NSString*protocol=[json allKeys][0];NSString*clsName=[json allValues][0];if(protocol&&clsName){[[BHServiceManager sharedManager]registerService:NSProtocolFromString(protocol)implClass:NSClassFromString(clsName)];}}}}}__attribute__((constructor))voidinitProphet(){_dyld_register_func_for_add_image(dyld_callback);}NSArray<NSString*>*BHReadConfiguration(char*sectionName,conststructmach_header*mhp){NSMutableArray*configs=[NSMutableArray array];unsignedlongsize=0;#ifndef__LP64__uintptr_t*memory=(uintptr_t*)getsectiondata(mhp,SEG_DATA,sectionName,&size);#elseconststructmach_header_64*mhp64=(conststructmach_header_64*)mhp;uintptr_t*memory=(uintptr_t*)getsectiondata(mhp64,SEG_DATA,sectionName,&size);#endifunsignedlongcounter=size/sizeof(void*);for(intidx=0;idx<counter;++idx){char*string=(char*)memory[idx];NSString*str=[NSString stringWithUTF8String:string];if(!str)continue;BHLog(@"config = %@",str);if(str)[configs addObject:str];}returnconfigs;}@implementationBHAnnotation@end

__attribute__((constructor))就是保证在main之前读取所有注册信息。

使用

@BeeHiveMod(ShopModule)@interfaceShopModule()<BHModuleProtocol>@end@implementationShopModule

延迟 premain code

把+load等main函数之前的代码移植到了main函数之后。是探一种延迟 premain code 的方法这篇文章提出的,我还没有尝试过。

原理是把函数地址放到QWLoadable段中,然后主程序在启动时获取QWLoadable的内容,并逐个调用。

库的地址LoadableMacro

作者测试下来,100个函数地址的读取,在iPhone5的设备上读取不到1ms。新增了这不到1ms的耗时(这1ms也是可审计的),带来了所有启动阶段行为的可审计,以及最重要的Patch能力。

msgSend observe

这个来自于质量监控-卡顿检测这篇文章

OC方法的调用最终转换成msgSend的调用执行,通过在函数前后插入自定义的函数调用,维护一个函数栈结构可以获取每一个OC方法的调用耗时,以此进行性能分析与优化:

#definesave() \__asm volatile ( \    "stp x8, x9, [sp, #-16]!\n" \    "stp x6, x7, [sp, #-16]!\n" \    "stp x4, x5, [sp, #-16]!\n" \    "stp x2, x3, [sp, #-16]!\n" \    "stp x0, x1, [sp, #-16]!\n");#defineresume() \__asm volatile ( \    "ldp x0, x1, [sp], #16\n" \    "ldp x2, x3, [sp], #16\n" \    "ldp x4, x5, [sp], #16\n" \    "ldp x6, x7, [sp], #16\n" \    "ldp x8, x9, [sp], #16\n" );#definecall(b, value) \    __asm volatile ("stp x8, x9, [sp, #-16]!\n"); \    __asm volatile ("mov x12, %0\n" :: "r"(value)); \    __asm volatile ("ldp x8, x9, [sp], #16\n"); \    __asm volatile (#b " x12\n");__attribute__((__naked__))staticvoidhook_Objc_msgSend(){save()__asmvolatile("mov x2, lr\n");__asmvolatile("mov x3, x4\n");call(blr,&push_msgSend)resume()call(blr,orig_objc_msgSend)save()call(blr,&pop_msgSend)__asmvolatile("mov lr, x0\n");resume()__asmvolatile("ret\n");}

everettjf同样封装了一个库FishhookObjcMsgSend

参考文章

探索 facebook iOS 客户端 - section FBInjectable

探一种延迟 premain code 的方法

探一种延迟 premain code 的方法

__attribute__

Specifying Attributes of Variables

gnu-c-attributes

BeeHive —— 一个优雅但还在完善中的解耦框架

Declaring Attributes of Functions

__attribute__ 总结

OC中的 __attribute__

Clang 拾遗之objc_designated_initializer

Macro

Clang Attributes 黑魔法小记

黑魔法__attribute__((cleanup))

质量监控-卡顿检测

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容