之前做過App的啟動優化,遇到了+load
優化的問題,后來想一想除了initializers
代替+load
還有沒有什么好的方法,然后就搜到了運用編譯屬性__attribute__優化,於是查找了很多文章,系統的整理了下__attribute__。本文大部分內容來自引用的文章,如果想看更多更詳細內容可以查看引用文章。
__attribute__ 介紹
__attribute__
是一個編譯屬性,用於向編譯器描述特殊的標識、錯誤檢查或高級優化。它是GNU C特色之一,系統中有許多地方使用到。 __attribute__
可以設置函數屬性(Function Attribute )、變量屬性(Variable Attribute )和類型屬性(Type Attribute)等。
__attribute__ 格式
1 2 |
__attribute__ ((attribute-list)) |
__attribute__ 常用的編譯屬性及簡單應用
format
這個屬性指定一個函數比如printf,scanf作為參數,這使編譯器能夠根據代碼中提供的參數檢查格式字符串。對於追蹤難以發現的錯誤非常有幫助。
format參數的使用如下:
1 |
format (archetype, string-index, first-to-check) |
第一參數需要傳遞archetype
指定是哪種風格,這里是 NSString;string-index
指定傳入函數的第幾個參數是格式化字符串;first-to-check
指定第一個可變參數所在的索引.
C中的使用方法
1 2 |
extern int my_printf (void *my_object, const char *my_format, ...) __attribute__((format(printf, 2, 3))); |
在Objective-C 中通過使用__NSString__
格式達到同樣的效果,就像在NSString +stringWithFormat:
和NSLog()
里使用字符串格式一樣
1 2 3 |
FOUNDATION_EXPORT void NSLog(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
。當動態庫或程序在加載時,會檢查是否存在這兩個段,如果存在執行對應的代碼。
1 2 3 4 5 |
__attribute__((constructor)) static void beforeMain(void) { NSLog(@"beforeMain"); } |
1 2 |
__attribute__((constructor(101))) // 里面的數字越小優先級越高,1 ~ 100 為系統保留 |
__attribute__((destructor))
1 2 3 4 |
__attribute__((destructor)) static void afterMain(void) { NSLog(@"afterMain"); } |
確保此函數在 在main
函數被調用之后調
__attribute__((cleanup))
用於修飾一個變量,在它的作用域結束時可以自動執行一個指定的方法
關於這個Sunny在黑魔法__attribute__((cleanup))中講的很好很細,建議看看。
iOS中的應用
既然__attribute__((cleanup(...)))
可以用來修飾變量,所以也可以用來修飾block
1 2 3 4 5 |
// void(^block)(void)的指針是void(^*block)(void) static void blockCleanUp(__strong void(^*block)(void)) { (*block)(); } |
這里不得不提萬能的Reactive Cocoa中神奇的@onExit
方法,其實正是上面的寫法,簡單定義個宏:
1 2 3 |
#define onExit\ __strong void(^block)(void) __attribute__((cleanup(blockCleanUp), unused)) = ^ |
這樣的寫法可以將成對出現的代碼寫在一起,比如說一個lock,用了onExit之后,代碼更集中了:
1 2 3 |
NSRecursiveLock *aLock = [[NSRecursiveLock alloc] init]; [aLock lock]; onExit { [aLock unlock]; }; |
當我看到這段代碼的時候第一個想到就是Swift中defer關鍵字
1 |
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中的一段代碼。
1 |
#define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" "))) |
nonnull
這個屬性指定函數的的某些參數不能是空指針
1 2 3 |
extern void * my_memcpy (void *dest, const void *src, size_t len) __attribute__((nonnull (1, 2))); |
iOS中的應用
1 2 3 4 5 6 7 8 |
- (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; } |
objc_runtime_name
用於 @interface
或 @protocol
,將類或協議的名字在編譯時指定成另一個
1 |
__attribute__((objc_runtime_name("<#OtherClassName#>"))) |
iOS中的應用
1 2 3 4 5 6 |
__attribute__((objc_runtime_name("OtherTest"))) @interface Test : NSObject @end NSLog(@"%@", NSStringFromClass([Test class])); // "OtherTest" |
這個屬性可以用來做代碼混淆
noreturn
幾個標注庫函數,例如abort exit,沒有返回值。GCC能夠自動識別這種情況。noreturn
屬性指定像這樣的任何不需要返回值的函數。當遇到類似函數還未運行到return語句就需要退出來的情況,該屬性可以避免出現錯誤信息。
iOS中的運用
AFNetworking庫為它的網絡請求顯示入口函數使用了該屬性。這個在生成一個專用的線程時使用,保證分離的線程能在應用的整個生命周期繼續執行
1 2 3 4 5 6 7 |
+ (void) __attribute__((noreturn)) networkRequestThreadEntryPoint:(id)__unused object { do { @autoreleasepool { [[NSRunLoop currentRunLoop] run]; } } while (YES); } |
noinline & always_inline
內聯函數:內聯函數從源代碼層看,有函數的結構,而在編譯后,卻不具備函數的性質。內聯函數不是在調用時發生控制轉移,而是在編譯時將函數體嵌入在每一個調用處。編譯時,類似宏替換,使用函數體替換調用處的函數名。一般在代碼中用inline修飾,但是能否形成內聯函數,需要看編譯器對該函數定義的具體處理
noinline
不內聯always_inline
總是內聯- 這兩個都是用在函數上
內聯的本質是用代碼塊直接替換掉函數調用處,好處是:快代碼的執行,減少系統開銷.適用場景:
- 這個函數更小
- 這個函數不被經常調用
1 |
void test(int a) __attribute__((always_inline)); |
這個在Swift有類似用法
1 2 3 4 5 6 7 8 9 10 11 12 |
extension NSLock { @inline(__always) func executeWithLock(_ block: () -> Void) { lock() block() unlock() } } |
warn_unused_result
當函數或者方法的返回值很重要時,要求調用者必須檢查或者使用返回值,否則編譯器會發出警告提示。
1 2 3 4 5 6 |
- (BOOL)availiable __attribute__((warn_unused_result)) { return 10; } |
在Swift中應該是幾乎所有方法都是warn_unused_result
,可以通過@discardableResult
去掉警告提示。
Clang特有的
就像GCC的許多特性一樣,Clang支持__attribute__
,而且添加了一些自己的小擴展。為了檢查一個特殊屬性的可用性,你可以使用__has_attribute
指令。
availability
Clang引入了可用性屬性,這個屬性可以在聲明中描述跟系統版本有關的生命周期。例如:
官方例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
- (CGSize)sizeWithFont:(UIFont *)font NS_DEPRECATED_IOS(2_0, 7_0, "Use -sizeWithAttributes:") __TVOS_PROHIBITED; //來看一下 后邊的宏 #define NS_DEPRECATED_IOS(_iosIntro, _iosDep, ...) CF_DEPRECATED_IOS(_iosIntro, _iosDep, __VA_ARGS__) define CF_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*
命令行指定的。
1 2 3 4 5 6 7 8 9 |
//如果經常用,建議定義成類似系統的宏 - (void)oldMethod:(NSString *)string __attribute__((availability(ios,introduced=2_0,deprecated=7_0,message="用 -newMethod: 這個方法替代 "))){ NSLog(@"我是舊方法,不要調我"); } - (void)newMethod:(NSString *)string{ NSLog(@"我是新方法"); } |
在swift中也有類似的用法
1 2 |
@available(iOS 6.0, *) public var minimumScaleFactor: CGFloat // default is 0.0 |
unavailable
告訴編譯器該方法不可用,如果強行調用編譯器會提示錯誤。比如某個類在構造的時候不想直接通過init
來初始化,只能通過特定的初始化方法()比如單例,就可以將init
方法標記為unavailable
。
1 2 3 4 |
//系統的宏,可以直接拿來用 #define UNAVAILABLE_ATTRIBUTE __attribute__((unavailable)) #define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE |
1 2 3 4 5 6 7 8 9 10 11 12 |
@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 |
實際上unavailable后面可以跟參數,顯示一些信息,如:
1 2 3 |
//系統的 #define NS_AUTOMATED_REFCOUNT_UNAVAILABLE __attribute__((unavailable("not available in automatic reference counting mode"))) |
overloadable
Clang在C中提供對C++標准函數重載的支持。函數重載在C中是通過overloadable屬性引入的。例如:你可以重載tgsin函數,寫出sin函數在入參不同時的不同版本。用於c語言函數,可以定義若干個函數名相同,但參數不同的方法,調用時編譯器會自動根據參數選擇函數原型。
1 2 3 4 5 6 7 8 |
__attribute__((overloadable)) void print(NSString *string){ NSLog(@"%@",string); } __attribute__((overloadable)) void print(int num){ NSLog(@"%d",num); } |
__attribute__ 在iOS開發中的復雜應用
說了這么多重點來了,那么這些屬性在iOS上有哪些奇妙的運用呢?有些比較簡單的運用在介紹屬性的時候就說了,這里主要講一些比較復雜的運用。
Swift沒有+load方法的替代帶方案
1 2 3 4 5 6 7 8 9 10 11 12 |
static void __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]; } |
1 2 3 4 5 6 |
class MyInitThingy: NSObject { @objc static func appWillLaunch(_: Notification) { print("App Will Launch") } } |
BeeHive模塊注冊
模塊注冊有三種方式:Annotation方式注冊、讀取本地plist方式注冊、Load方法注冊。
首先把數據放在可執行文件的自定義數據段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// 通過BeeHiveMod宏進行Annotation標記 #ifndef BeehiveModSectName #define BeehiveModSectName "BeehiveMods" #endif #ifndef BeehiveServiceSectName #define BeehiveServiceSectName "BeehiveServices" #endif #define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" "))) // 這里我們就把數據存在data數據段里面的"BeehiveMods"段中 #define BeeHiveMod(name) \ class BeeHive; char * k##name##_mod BeeHiveDATA(BeehiveMods) = ""#name""; #define BeeHiveService(servicename,impl) \ class BeeHive; char * k##servicename##_service BeeHiveDATA(BeehiveServices) = "{ \""#servicename"\" : \""#impl"\"}"; @interface BHAnnotation : NSObject @end |
從Mach-O section中讀取數據
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
SArray<NSString *>* BHReadConfiguration(char *sectionName,const struct mach_header *mhp); static void dyld_callback(const struct mach_header *mhp, intptr_t vmaddr_slide) { NSArray *mods = BHReadConfiguration(BeehiveModSectName, mhp); for (NSString *modName in mods) { Class cls; if (modName) { cls = NSClassFromString(modName); if (cls) { [[BHModuleManager sharedManager] registerDynamicModule:cls]; } } } //register services NSArray<NSString *> *services = BHReadConfiguration(BeehiveServiceSectName,mhp); for (NSString *map in services) { NSData *jsonData = [map dataUsingEncoding:NSUTF8StringEncoding]; NSError *error = nil; id json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&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)) void initProphet() { _dyld_register_func_for_add_image(dyld_callback); } NSArray<NSString *>* BHReadConfiguration(char *sectionName,const struct mach_header *mhp) { NSMutableArray *configs = [NSMutableArray array]; unsigned long size = 0; #ifndef __LP64__ uintptr_t *memory = (uintptr_t*)getsectiondata(mhp, SEG_DATA, sectionName, &size); #else const struct mach_header_64 *mhp64 = (const struct mach_header_64 *)mhp; uintptr_t *memory = (uintptr_t*)getsectiondata(mhp64, SEG_DATA, sectionName, &size); #endif unsigned long counter = size/sizeof(void*); for(int idx = 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]; } return configs; } @implementation BHAnnotation @end |
__attribute__((constructor))
就是保證在main
之前讀取所有注冊信息。
使用
1 2 3 4 5 |
@BeeHiveMod(ShopModule) @interface ShopModule() <BHModuleProtocol> @end @implementation ShopModule |
延遲 premain code
把+load等main函數之前的代碼移植到了main函數之后。是探一種延遲 premain code 的方法這篇文章提出的,我還沒有嘗試過。
原理是把函數地址放到QWLoadable段中,然后主程序在啟動時獲取QWLoadable的內容,並逐個調用。
庫的地址 LoadableMacro
作者測試下來,100個函數地址的讀取,在iPhone5的設備上讀取不到1ms。新增了這不到1ms的耗時(這1ms也是可審計的),帶來了所有啟動階段行為的可審計,以及最重要的Patch能力。
msgSend observe
這個來自於質量監控-卡頓檢測 這篇文章 OC方法的調用最終轉換成msgSend的調用執行,通過在函數前后插入自定義的函數調用,維護一個函數棧結構可以獲取每一個OC方法的調用耗時,以此進行性能分析與優化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
#define save() \ __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"); #define resume() \ __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" ); #define call(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__)) static void hook_Objc_msgSend() { save() __asm volatile ("mov x2, lr\n"); __asm volatile ("mov x3, x4\n"); call(blr, &push_msgSend) resume() call(blr, orig_objc_msgSend) save() call(blr, &pop_msgSend) __asm volatile ("mov lr, x0\n"); resume() __asm volatile ("ret\n"); } |
everettjf 同樣封裝了一個庫FishhookObjcMsgSend
這里是另一個實現方案 objc_msgSend
參考文章
探索 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))
質量監控-卡頓檢測
https://woshiccm.github.io/posts/__attribute__詳解及應用/