<簡書 — 劉小壯> https://www.jianshu.com/p/4a22a39b69c5html
__attribute__
是一套編譯器指令,被GNU
和LLVM
編譯器所支持,容許對於__attribute__
增長一些參數,作一些高級檢查和優化。ios
__attribute__
的語法是,在後面加兩個括號,而後寫屬性列表,屬性列表以逗號分隔。在iOS中,不少例如NS_CLASS_AVAILABLE_IOS
的宏定義,內部也是經過__attribute__
實現的。git
__attribute__((attribute1, attribute2));
下面是一些__attribute__
的經常使用屬性,更完整的屬性列表能夠到llvm的官網查看。github
objc_subclassing_restricted
屬性表示被修飾的類不能被其餘類繼承,不然會報下面的錯誤。面試
__attribute__((objc_subclassing_restricted)) @interface TestObject : NSObject @property (nonatomic, strong) NSObject *object; @property (nonatomic, assign) NSInteger age; @end @interface Child : TestObject @end 錯誤信息: Cannot subclass a class that was declared with the 'objc_subclassing_restricted' attribute
objc_requires_super
屬性表示子類必須調用被修飾的方法super
,不然報黃色警告。數組
@interface TestObject : NSObject - (void)testMethod __attribute__((objc_requires_super)); @end @interface Child : TestObject @end 警告信息:(不報錯) Method possibly missing a [super testMethod] call
constructor
屬性表示在main
函數執行以前,能夠執行一些操做。destructor
屬性表示在main
函數執行以後作一些操做。constructor
的執行時機是在全部load
方法都執行完以後,纔會執行全部constructor
屬性修飾的函數。app
__attribute__((constructor)) static void beforeMain() { NSLog(@"before main"); } __attribute__((destructor)) static void afterMain() { NSLog(@"after main"); } int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"execute main"); } return 0; } 執行結果: debug-objc[23391:1143291] before main debug-objc[23391:1143291] execute main debug-objc[23391:1143291] after main
在有多個constructor
或destructor
屬性修飾的函數時,能夠經過設置優先級來指定執行順序。格式是__attribute__((constructor(101)))
的方式,在屬性後面直接跟優先級。框架
__attribute__((constructor(103))) static void beforeMain3() { NSLog(@"after main 3"); } __attribute__((constructor(101))) static void beforeMain1() { NSLog(@"after main 1"); } __attribute__((constructor(102))) static void beforeMain2() { NSLog(@"after main 2"); }
在constructor
中根據優先級越低,執行順序越高。而destructor
則相反,優先級越高則執行順序越高。函數
overloadable
屬性容許定義多個同名但不一樣參數類型的函數,在調用時編譯器會根據傳入參數類型自動匹配函數。這個有點相似於C++
的函數重載,並且都是發生在編譯期的行爲。佈局
__attribute__((overloadable)) void testMethod(int age) {} __attribute__((overloadable)) void testMethod(NSString *name) {} __attribute__((overloadable)) void testMethod(BOOL gender) {} int main(int argc, const char * argv[]) { @autoreleasepool { testMethod(18); testMethod(@"lxz"); testMethod(YES); } return 0; }
objc_runtime_name
屬性能夠在編譯時,將Class
或Protocol
指定爲另外一個名字,而且新名字不受命名規範制約,能夠以數字開頭。
__attribute__((objc_runtime_name("TestObject"))) @interface Object : NSObject @end NSLog(@"%@", NSStringFromClass([TestObject class])); 執行結果: TestObject
這個屬性能夠用來作代碼混淆,例如寫一個宏定義,宏定義內部實現混淆邏輯。例如經過MD5
對Object
作混淆,32位的混淆結果就是497031794414a552435f90151ac3b54b
,誰能看出來這是什麼類。若是怕彩虹表匹配出來,再增長加鹽邏輯。
經過cleanup
屬性,能夠指定給一個變量,當變量釋放以前執行一個函數。指定的函數執行的時間,是在dealloc
以前的。在指定的函數中,能夠傳入一個形參,參數就是cleanup
修飾的變量,形參是一個地址。
static void releaseBefore(NSObject **object) { NSLog(@"%@", *object); } int main(int argc, const char * argv[]) { @autoreleasepool { TestObject *object __attribute__((cleanup(releaseBefore))) = [[TestObject alloc] init]; } return 0; }
若是遇到同一個代碼塊中,同時出現多個cleanup
屬性時,在代碼塊做用域結束時,會以添加的順序進行調用。
還有一個屬性很實用,在項目裏常常會有未使用的變量,會報一個黃色警告。有時候可能會經過其餘方式獲取這個對象,因此不想出現這個警告,能夠經過unused
屬性消除這個警告。
NSObject *object __attribute__((unused)) = [[NSObject alloc] init];
在系統裏也大量使用了__attribute__
關鍵字,只不過系統不會直接在外部使用__attribute__
,通常都是將其定義爲宏定義,以宏定義的形式出如今外面。
// 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))) // 必須調用父類的方法 #define NS_REQUIRES_SUPER __attribute__((objc_requires_super)) // 指定初始化方法,必須直接或間接調用修飾的方法 #define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
對象關係映射(Object Relational Mapping)
,簡稱ORM
,用於面嚮對象語言中不一樣系統數據之間的轉換。 能夠經過對象關係映射來實現JSON
轉模型,使用比較多的是Mantle
、MJExtension
、YYKit
、JSONModel
等框架,這些框架在進行轉換的時候,都是使用Runtime
的方式實現的。
Mantle
使用和MJExtension
有些相似,只不過MJExtension
使用起來更加方便。Mantle
在使用時主要是經過繼承的方式處理,而MJExtension
是經過Category
處理,代碼依賴性更小,無侵入性。
這些第三方中Mantle
功能最強大,可是太臃腫,使用起來性能比其餘第三方都差一些。JSONModel
、MJExtension
這些第三方几乎都在一個水平級,YYKit
相對來講性能能夠比肩手寫賦值代碼,性價比最高。
對於模型轉換需求不是太大的工程來講,儘可能用YYKit
來進行轉換性能會更好一些。功能可能略遜於MJExtension
,我我的仍是比較習慣用MJExtension
。
也能夠本身實現模型轉換的邏輯,以字典轉模型爲例,大致邏輯以下:
Category
用來作模型轉換,對外提供方法並傳入字典對象。Runtime
對應的函數,獲取屬性列表並遍歷,根據屬性名從字典中取出對應的對象。KVC
將從字典中取出的值,賦值給對象。下面簡單實現了一個字典轉模型的代碼,經過Runtime
遍歷屬性列表,並根據屬性名取出字典中的對象,而後經過KVC
進行賦值操做。調用方式和MJExtension
、YYModel
相似,直接經過模型類調用類方法便可。若是想在其餘類中也使用的話,應該把下面的實現寫在NSObject
的Category
中,這樣全部類均可以調用。
// 調用部分 NSDictionary *dict = @{@"name" : @"lxz", @"age" : @18, @"gender" : @YES}; TestObject *object = [TestObject objectWithDict:dict]; // 實現代碼 @interface TestObject : NSObject @property (nonatomic, copy ) NSString *name; @property (nonatomic, assign) NSInteger age; @property (nonatomic, assign) BOOL gender; + (instancetype)objectWithDict:(NSDictionary *)dict; @end @implementation TestObject + (instancetype)objectWithDict:(NSDictionary *)dict { return [[TestObject alloc] initWithDict:dict]; } - (instancetype)initWithDict:(NSDictionary *)dict { self = [super init]; if (self) { unsigned int count = 0; objc_property_t *propertys = class_copyPropertyList([self class], &count); for (int i = 0; i < count; i++) { objc_property_t property = propertys[i]; const char *name = property_getName(property); NSString *nameStr = [[NSString alloc] initWithUTF8String:name]; id value = [dict objectForKey:nameStr]; [self setValue:value forKey:nameStr]; } free(propertys); } return self; } @end
經過Runtime
能夠獲取到對象的Method List
、Property List
等,不僅能夠用來作字典模型轉換,還能夠作不少工做。例如還能夠經過Runtime
實現自動歸檔和反歸檔,下面是自動進行歸檔操做。
// 1.獲取全部的屬性 unsigned int count = 0; Ivar *ivars = class_copyIvarList([NJPerson class], &count); // 遍歷全部的屬性進行歸檔 for (int i = 0; i < count; i++) { // 取出對應的屬性 Ivar ivar = ivars[i]; const char * name = ivar_getName(ivar); // 將對應的屬性名稱轉換爲OC字符串 NSString *key = [[NSString alloc] initWithUTF8String:name]; // 根據屬性名稱利用KVC獲取數據 id value = [self valueForKeyPath:key]; [encoder encodeObject:value forKey:key]; } free(ivars);
我寫了一個簡單的Category
,能夠自動實現NSCoding
、NSCopying
協議。這是開源地址:EasyNSCoding
下面的代碼輸出什麼?
@implementation Son : Father - (id)init { self = [super init]; if (self) { NSLog(@"%@", NSStringFromClass([self class])); NSLog(@"%@", NSStringFromClass([super class])); } return self; } @end
答案:都輸出Son
。
第一個NSLog
輸出Son
確定是不用說的。
第二個輸出中,[super class]
會被轉換爲下面代碼。
struct objc_super objcSuper = { self, class_getSuperclass([self class]), }; id (*sendSuper)(struct objc_super*, SEL) = (void *)objc_msgSendSuper; sendSuper(&objcSuper, @selector(class));
super
的調用會被轉換爲objc_msgSendSuper
的調用,並傳入一個objc_super
類型的結構體。結構體有兩個參數,第一個就是接受消息的對象,第二個是[super class]
對應的父類。
struct objc_super { __unsafe_unretained _Nonnull id receiver; __unsafe_unretained _Nonnull Class super_class; };
由此可知,雖然調用的是[super class]
,可是接受消息的對象仍是self
。而後來到父類Father
的class
方法中,輸出self
對應的類Son
。
下面代碼的結果?
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]]; BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
答案: 除了第一個是YES
,其餘三個都是NO
。
在推測結果以前,首先要明白兩個問題。isKindOfClass
和isMemberOfClass
的區別是什麼? isKindOfClass:class
,調用該方法的對象所屬的類,繼承者鏈中包含傳入的class
則返回YES
。 isMemberOfClass:class
,調用改方法的對象所屬的類,必須是傳入的class
則返回YES
。
咱們從Runtime
源碼的角度來分析一下結果。
+ (BOOL)isMemberOfClass:(Class)cls { return object_getClass((id)self) == cls; } - (BOOL)isMemberOfClass:(Class)cls { return [self class] == cls; } + (BOOL)isKindOfClass:(Class)cls { for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; } return NO; } - (BOOL)isKindOfClass:(Class)cls { for (Class tcls = [self class]; tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; } return NO; }
平時開發過程當中只會接觸到對象方法的isKindOfClass
和isMemberOfClass
,可是在NSObject
類中還隱式的實現了類方法版本。不僅這兩個方法,其餘NSObject
中的對象方法,都有其對應的類方法版本。由於在OC中,類和元類也都是對象。這四個調用因爲都是類對象發起調用的,因此最終執行的都是類方法版本。
先把Runtime
的對象模型拿出來,方便後面的分析。
第一次調用方是NSObject
類對象,調用isKindOfClass
方法傳入的也是類對象。由於調用類的class
方法,會把類自身直接返回,因此仍是類對象本身。
而後進入到for
循環中,會從NSObject
的元類開始遍歷,因此第一次NSObject meta class != NSObject class
,匹配失敗。第二次循環將tcls
設置爲superclass
的NSObject class
,NSObject class == NSObject class
,匹配成功。
NSObject
能匹配成功,是由於這個類比較特殊,在第二次獲取superclass
的時候,NSObject
元類的superclass
就是NSObject
的類對象,因此會匹配成功。而其餘三種匹配,則都會失敗,各位同窗能夠去本身分析一下剩下三種。
下面的代碼會?Compile Error
/ Runtime Crash
/ NSLog…
?
@interface NSObject (Sark) + (void)foo; @end @implementation NSObject (Sark) - (void)foo { NSLog(@"IMP: -[NSObject (Sark) foo]"); } @end // 測試代碼 [NSObject foo]; [[NSObject new] performSelector:@selector(foo)];
答案: 全都正常輸出,編譯和運行都沒有問題。
這道題和上一道題很類似,第二個調用確定沒有問題,第一個調用後會從元類中查找方法,然而方法並不在元類中,因此找元類的superclass
。方法定義在是NSObject
的Category
,因爲NSObject
的對象模型比較特殊,元類的superclass
是類對象,因此從類對象中找到了方法並調用。
下面的代碼會?Compile Error
/ Runtime Crash
/ NSLog…
?
@interface Sark : NSObject @property (nonatomic, copy) NSString *name; @end @implementation Sark - (void)speak { NSLog(@"my name's %@", self.name); } @end // 測試代碼 @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; id cls = [Sark class]; void *obj = &cls; [(__bridge id)obj speak]; } @end
答案: 正常執行,不會致使Crash
。
執行[Sark class]
後獲取到類對象,而後經過obj
指針指向獲取到的類對象首地址,這就構成了對象的基本結構,能夠進行正常調用。
爲何MRC
下沒有weak
?
其實MRC
下並非沒有weak
,在MRC
環境下也能夠經過Runtime
源碼調用weak
源碼的。weak
源碼定義在Private Headers
私有文件夾下,須要引入#import "objc-internal.h"
文件。
以如下ARC
的源碼爲例,定義了一個TestObject
類型的對象,並用一個weak
指針指向已建立對象。
int main(int argc, const char * argv[]) { @autoreleasepool { TestObject *object = [[TestObject alloc] init]; __weak TestObject *newObject = object; } return 0; }
這段代碼會被編譯器轉移爲下面代碼,這段代碼中的兩個函數就是weak
的實現函數,在MRC
下也能夠調用這兩個函數。
objc_initWeak(&newObject, object); objc_destroyWeak(&newObject);
相同的一個類,建立不一樣的對象,怎樣實現指定的某個對象在dealloc
時打印一段文字?
這個問題最簡單的方法就是在類的.h
文件裏,定義一個標記屬性,若是屬性被賦值爲YES
,則在dealloc
中打印文字。可是,這種實現方式顯然不是面試官想要的,會被直接pass~
能夠參考KVO
的實現方案,在運行時動態建立一個類,這個類是對象的子類,將新建立類的dealloc
實現指向自定義的IMP
,並在IMP
中打印一段文字。將對象的isa
設置爲新建立的類,當執行dealloc
方法時就會執行isa
所指向的新類。
什麼叫作技術大牛,怎樣就表示技術強?
我前段時間看過一句話,我感受能夠解釋上面的問題:「市面上全部應用的功能,產品提出來我都能作」。 這句話並不夠全面,應該不僅是作出來,而是更好的作出來。這個好要從不少方面去評估,性能、可維護性、完成時間、產品效果等,若是這些都作的很好,那足以證實這我的技術很強大。
Runtime
是比較偏底層的,可是研究這麼深有什麼用嗎,有什麼實際意義嗎?
Runtime
固然是由實際用處的,先不說整個OC都是經過Runtime
實現的。例如如今須要實現消息轉發的功能,這時候就須要用到Runtime
,或者是攔截方法,也須要用到Method Swizzling
,除了這些,還有更多的用法待咱們去發掘。
不僅是使用,其實最重要的是,經過Runtime瞭解一個語言的設計。Runtime中不僅是各類函數調用,從總體來看,能夠明白OC的對象模型是什麼樣的。
簡書因爲排版的問題,閱讀體驗並很差,佈局、圖片顯示、代碼等不少問題。因此建議到我Github
上,下載Runtime PDF
合集。把全部Runtime
文章總計九篇,都寫在這個PDF
中,並且左側有目錄,方便閱讀。
下載地址:Runtime PDF 麻煩各位大佬點個贊,謝謝!