當前位置:
首頁 > 最新 > Runtime的應用

Runtime的應用

attribute

__attribute__是一套編譯器指令,被GNU和LLVM編譯器所支持,允許對於__attribute__增加一些參數,做一些高級檢查和優化。

__attribute__的語法是,在後面加兩個括弧,然後寫屬性列表,屬性列表以逗號分隔。在iOS中,很多例如NS_CLASS_AVAILABLE_IOS的宏定義,內部也是通過__attribute__實現的。

__attribute__((attribute1, attribute2));

下面是一些__attribute__的常用屬性,更完整的屬性列表可以到llvm的官網查看。

官網示例

objc_subclassing_restricted

objc_subclassing_restricted屬性表示被修飾的類不能被其他類繼承,否則會報下面的錯誤。

__attribute__((objc_subclassing_restricted))

@interfaceTestObject : NSObject

@property(nonatomic, strong) NSObject *object;

@property(nonatomic, assign) NSInteger age;

@end

@interfaceChild : TestObject

@end

錯誤信息:

Cannot subclass aclassthatwasdeclaredwiththe"objc_subclassing_restricted"attribute

objc_requires_super

objc_requires_super屬性表示子類必須調用被修飾的方法super,否則報黃色警告。

@interfaceTestObject:NSObject

- (void)testMethod __attribute__((objc_requires_super));

@end

@interfaceChild:TestObject

@end

警告信息:(不報錯)

Method possibly missing a [supertestMethod] call

constructor / destructor

constructor屬性表示在main函數執行之前,可以執行一些操作。destructor屬性表示在main函數執行之後做一些操作。constructor的執行時機是在所有load方法都執行完之後,才會執行所有constructor屬性修飾的函數。

__attribute__((constructor))staticvoidbeforeMain(){

NSLog(@"before main");

}

__attribute__((destructor))staticvoidafterMain(){

NSLog(@"after main");

}

intmain(intargc,constchar* argv[]){

@autoreleasepool{

NSLog(@"execute main");

}

return;

}

執行結果:

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)))staticvoidbeforeMain3(){

NSLog(@"after main 3");

}

__attribute__((constructor(101)))staticvoidbeforeMain1(){

NSLog(@"after main 1");

}

__attribute__((constructor(102)))staticvoidbeforeMain2(){

NSLog(@"after main 2");

}

在constructor中根據優先順序越低,執行順序越高。而destructor則相反,優先順序越高則執行順序越高。

overloadable

overloadable屬性允許定義多個同名但不同參數類型的函數,在調用時編譯器會根據傳入參數類型自動匹配函數。這個有點類似於C++的函數重載,而且都是發生在編譯期的行為。

__attribute__((overloadable))voidtestMethod(intage){}

__attribute__((overloadable))voidtestMethod(NSString *name){}

__attribute__((overloadable))voidtestMethod(BOOL gender){}

intmain(intargc,constchar* argv[]){

@autoreleasepool{

testMethod(18);

testMethod(@"lxz");

testMethod(YES);

}

return;

}

objc_runtime_name

objc_runtime_name屬性可以在編譯時,將Class或Protocol指定為另一個名字,並且新名字不受命名規範制約,可以以數字開頭。

__attribute__((objc_runtime_name("TestObject")))

@interfaceObject:NSObject

@end

NSLog(@"%@",NSStringFromClass([TestObjectclass]));

執行結果:

TestObject

這個屬性可以用來做代碼混淆,例如寫一個宏定義,宏定義內部實現混淆邏輯。例如通過MD5對Object做混淆,32位的混淆結果就是497031794414a552435f90151ac3b54b,誰能看出來這是什麼類。如果怕彩虹表匹配出來,再增加加鹽邏輯。

cleanup

通過cleanup屬性,可以指定給一個變數,當變數釋放之前執行一個函數。指定的函數執行的時間,是在dealloc之前的。在指定的函數中,可以傳入一個形參,參數就是cleanup修飾的變數,形參是一個地址。

staticvoidreleaseBefore(NSObject **object){

NSLog(@"%@", *object);

}

intmain(intargc,constchar* argv[]){

@autoreleasepool {

TestObject *object__attribute__((cleanup(releaseBefore))) = [[TestObject alloc] init];

}

return;

}

如果遇到同一個代碼塊中,同時出現多個cleanup屬性時,在代碼塊作用域結束時,會以添加的順序進行調用。

unused

還有一個屬性很實用,在項目里經常會有未使用的變數,會報一個黃色警告。有時候可能會通過其他方式獲取這個對象,所以不想出現這個警告,可以通過unused屬性消除這個警告。

NSObject*object __attribute__((unused)) = [[NSObjectalloc] init];

系統定義

在系統里也大量使用了__attribute__關鍵字,只不過系統不會直接在外部使用__attribute__,一般都是將其定義為宏定義,以宏定義的形式出現在外面。

// NSLog

FOUNDATION_EXPORTvoidNSLog(NSString *format, ...)NS_FORMAT_FUNCTION(1,2)NS_NO_TAIL_CALL;

#defineNS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A)))

// 必須調用父類的方法

#defineNS_REQUIRES_SUPER __attribute__((objc_requires_super))

// 指定初始化方法,必須直接或間接調用修飾的方法

#defineNS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))

ORM

對象關係映射(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];

// 實現代碼

@interfaceTestObject:NSObject

@property(nonatomic,copy)NSString*name;

@property(nonatomic,assign)NSIntegerage;

@property(nonatomic,assign)BOOLgender;

+ (instancetype)objectWithDict:(NSDictionary*)dict;

@end

@implementationTestObject

+ (instancetype)objectWithDict:(NSDictionary*)dict {

return[[TestObject alloc] initWithDict:dict];

}

- (instancetype)initWithDict:(NSDictionary*)dict {

self= [superinit];

if(self) {

unsignedintcount =;

objc_property_t *propertys = class_copyPropertyList([selfclass], &count);

for(inti =; i

objc_property_t property = propertys[i];

constchar*name = property_getName(property);

NSString*nameStr = [[NSStringalloc] initWithUTF8String:name];

idvalue = [dict objectForKey:nameStr];

[selfsetValue:value forKey:nameStr];

}

free(propertys);

}

returnself;

}

@end

通過Runtime可以獲取到對象的Method List、Property List等,不只可以用來做字典模型轉換,還可以做很多工作。例如還可以通過Runtime實現自動歸檔和反歸檔,下面是自動進行歸檔操作。

// 1.獲取所有的屬性

unsignedintcount =;

Ivar *ivars = class_copyIvarList([NJPersonclass], &count);

// 遍歷所有的屬性進行歸檔

for(inti =; i

// 取出對應的屬性

Ivar ivar = ivars[i];

constchar* name = ivar_getName(ivar);

// 將對應的屬性名稱轉換為OC字元串

NSString*key = [[NSStringalloc] initWithUTF8String:name];

// 根據屬性名稱利用KVC獲取數據

idvalue = [selfvalueForKeyPath:key];

[encoder encodeObject:value forKey:key];

}

free(ivars);

Runtime面試題

題1

下面的代碼輸出什麼?

@implementationSon:Father

- (id)init {

self= [superinit];

if(self) {

NSLog(@"%@",NSStringFromClass([selfclass]));

NSLog(@"%@",NSStringFromClass([superclass]));

}

returnself;

}

@end

答案:都輸出Son。

第一個NSLog輸出Son肯定是不用說的。

第二個輸出中,[super class]會被轉換為下面代碼。

structobjc_super objcSuper = {

self,

class_getSuperclass([selfclass]),

};

id(*sendSuper)(structobjc_super*, SEL) = (void*)objc_msgSendSuper;

sendSuper(&objcSuper,@selector(class));

super的調用會被轉換為objc_msgSendSuper的調用,並傳入一個objc_super類型的結構體。結構體有兩個參數,第一個就是接受消息的對象,第二個是[super class]對應的父類。

structobjc_super {

__unsafe_unretained_Nonnullidreceiver;

__unsafe_unretained_Nonnull Class super_class;

};

由此可知,雖然調用的是[super class],但是接受消息的對象還是self。然後來到父類Father的class方法中,輸出self對應的類Son。

題2

下面代碼的結果?

BOOLres1 = [(id)[NSObjectclass] isKindOfClass:[NSObjectclass]];

BOOLres2 = [(id)[NSObjectclass] isMemberOfClass:[NSObjectclass]];

BOOLres3 = [(id)[Sarkclass] isKindOfClass:[Sarkclass]];

BOOLres4 = [(id)[Sarkclass] isMemberOfClass:[Sarkclass]];

答案:

除了第一個是YES,其他三個都是NO。

在推測結果之前,首先要明白兩個問題。isKindOfClass和isMemberOfClass的區別是什麼?

isKindOfClass:class,調用該方法的對象所屬的類,繼承者鏈中包含傳入的class則返回YES。

isMemberOfClass:class,調用改方法的對象所屬的類,必須是傳入的class則返回YES。

我們從Runtime源碼的角度來分析一下結果。

+ (BOOL)isMemberOfClass:(Class)cls {

returnobject_getClass((id)self) == cls;

}

- (BOOL)isMemberOfClass:(Class)cls {

return[selfclass] == cls;

}

+ (BOOL)isKindOfClass:(Class)cls {

for(Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {

if(tcls == cls)returnYES;

}

returnNO;

}

- (BOOL)isKindOfClass:(Class)cls {

for(Class tcls = [selfclass]; tcls; tcls = tcls->superclass) {

if(tcls == cls)returnYES;

}

returnNO;

}

平時開發過程中只會接觸到對象方法的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的類對象,所以會匹配成功。而其他三種匹配,則都會失敗,各位同學可以去自己分析一下剩下三種。

題3

下面的代碼會?Compile Error / Runtime Crash / NSLog…?

@interfaceNSObject(Sark)

+ (void)foo;

@end

@implementationNSObject(Sark)

- (void)foo {

NSLog(@"IMP: -[NSObject (Sark) foo]");

}

@end

// 測試代碼

[NSObjectfoo];

[[NSObjectnew] foo];

答案:

全都正常輸出,編譯和運行都沒有問題。

這道題和上一道題很相似,第二個調用肯定沒有問題,第一個調用後會從元類中查找方法,然而方法並不在元類中,所以找元類的superclass。方法定義在是NSObject的Category,由於NSObject的對象模型比較特殊,元類的superclass是類對象,所以從類對象中找到了方法並調用。

題4

下面的代碼會?Compile Error / Runtime Crash / NSLog…?

@interfaceSark:NSObject

@property(nonatomic,copy)NSString*name;

@end

@implementationSark

- (void)speak {

NSLog(@"my name"s %@",self.name);

}

@end

// 測試代碼

@implementationViewController

- (void)viewDidLoad {

[superviewDidLoad];

idcls = [Sarkclass];

void*obj = &cls;

[(__bridgeid)obj speak];

}

@end

答案:

正常執行,不會導致Crash。

執行[Sark class]後獲取到類對象,然後通過obj指針指向獲取到的類對象首地址,這就構成了對象的基本結構,可以進行正常調用。

原題出處

題5

為什麼MRC下沒有weak?

其實MRC下並不是沒有weak,在MRC環境下也可以通過Runtime源碼調用weak源碼的。weak源碼定義在Private Headers私有文件夾下,需要引入#import "objc-internal.h"文件。

以以下ARC的源碼為例,定義了一個TestObject類型的對象,並用一個weak指針指向已創建對象。

intmain(intargc,constchar* argv[]){

@autoreleasepool {

TestObject *object= [[TestObject alloc] init];

__weak TestObject *newObject =object;

}

return;

}

這段代碼會被編譯器轉移為下面代碼,這段代碼中的兩個函數就是weak的實現函數,在MRC下也可以調用這兩個函數。

objc_initWeak(&newObject,object);

objc_destroyWeak(&newObject);

題6

相同的一個類,創建不同的對象,怎樣實現指定的某個對象在dealloc時列印一段文字?

這個問題最簡單的方法就是在類的.h文件里,定義一個標記屬性,如果屬性被賦值為YES,則在dealloc中列印文字。但是,這種實現方式顯然不是面試官想要的,會被直接pass~

可以參考KVO的實現方案,在運行時動態創建一個類,這個類是對象的子類,將新創建類的dealloc實現指向自定義的IMP,並在IMP中列印一段文字。將對象的isa設置為新創建的類,當執行dealloc方法時就會執行isa所指向的新類。

思考

小問題

什麼叫做技術大牛,怎樣就表示技術強?

我前段時間看過一句話,我感覺可以解釋上面的問題:「市面上所有應用的功能,產品提出來我都能做」。

這句話並不夠全面,應該不只是做出來,而是更好的做出來。這個好要從很多方面去評估,性能、可維護性、完成時間、產品效果等,如果這些都做的很好,那足以證明這個人技術很強大。

Runtime有什麼用?

Runtime是比較偏底層的,但是研究這麼深有什麼用嗎,有什麼實際意義嗎?

Runtime當然是由實際用處的,先不說整個OC都是通過Runtime實現的。例如現在需要實現消息轉發的功能,這時候就需要用到Runtime,或者是攔截方法,也需要用到Method Swizzling,除了這些,還有更多的用法待我們去發掘。

不只是使用,其實最重要的是,通過Runtime了解一個語言的設計。Runtime中不只是各種函數調用,從整體來看,可以明白OC的對象模型是什麼樣的。

簡書由於排版的問題,閱讀體驗並不好,布局、圖片顯示、代碼等很多問題。所以建議到我Github上,下載Runtime PDF合集。把所有Runtime文章總計九篇,都寫在這個PDF中,而且左側有目錄,方便閱讀。

Runtime PDF

麻煩各位大佬點個贊,謝謝!

作者:劉小壯

鏈接:https://www.jianshu.com/p/ce97c66027cd


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Cocoa開發者社區 的精彩文章:

iOS crash 日誌堆棧解析
蘋果、Google、同花順等傳統互聯網巨頭相繼增加數字貨幣行情……

TAG:Cocoa開發者社區 |