你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> runtime變奏曲,那些藏在runtime中的接口(一)

runtime變奏曲,那些藏在runtime中的接口(一)

編輯:IOS開發基礎

C本身是一個靜態語言,數據類型和代碼運行的結果都是在編譯的時候確定的。而Objective-C的runtime機制賦予了C一個新的活力,即運行時機制。這也就是說,OC代碼或者C代碼在編譯過後的機器碼並不能得出運行結果。而這個結果需要在運行的時候才能獲得,這樣就給了我們一個新的操縱代碼的空間,也就是運行時。在OC中,運行時是一段提前寫完的一個模塊的代碼。可以這麼說,OC的運行時就是這段代碼賦予的。
前幾篇文章中,我提到了objc_msgSend的流程,是為了讓大家對runtime的過程有一個大致的了解。但是,對於大部分人來說,比起原理,更關注的是怎麼用。所以本章的內容就是我在runtime小序曲,從運行時多態看這股神秘力量中提到的runtime的除了objc_msgSend的另外兩種應用:NSObject的方法和runtime的函數。
學習進度:
runtime小序曲,從運行時多態看這股神秘力量
runtime進行曲,objc_msgSend的前世今生(一)
runtime進行曲,objc_msgSend的前世今生(二)
runtime變奏曲,那些藏在runtime中的接口(一)
runtime變奏曲,那些藏在runtime中的接口(二)
一、NSObject的方法
前幾天,和群裡的一位騷年討論了runtime的問題。他的看法是,runtime並沒有什麼用,不用runtime照樣可以開發。其實,前半句並沒有什麼毛病,runtime確實沒有什麼用,因為大部分開發工作基本用不著(其實我們公司用的蠻多的)。問題出在第二句,不用runtime就可以開發。OC作為一種高級語言,能讓你方便的使用它的一些接口。比如:

- (BOOL)respondsToSelector:(SEL)aSelector;

這個方法大家應該經常使用,尤其是在使用delegate的場合,基本是必用的。那麼,我們從邏輯上看這個方法。從runtime小序曲,從運行時多態看這股神秘力量中我就說過,OC沒法在編譯時刻確定一個對象的類型。而這個方法是判斷一個繼承自NSObject的class有沒有實現一個SEL。很明顯,編譯時刻做不了這件事,它是在運行時刻做的。所以,不用runtime就可以開發是錯誤的。其實,這種事情並不罕見,大部分人不想學習runtime的原因也是如此。
runtime是C和匯編寫的,看不懂。
runtime開發用不著。
換個方式再說一下,runtime賦予C面向對象的能力,所以有了OC。那麼說實話,只要你用到class,其實都是和runtime相關的,怎麼可能避開。好像跑題了,現在我們回來。其實,和respondsToSelector:類似的OC方法還有很多,下述我會整理一些常用的。

// 在usr/include中的objc/runtime.h可以查看
// 獲取對象對應的class
- (Class)class;
// 判斷一個對象或者類是不是某個class或者這個class的派生類
- (BOOL)isKindOfClass:(Class)aClass;
// 判斷一個對象或者類是不是某個class
- (BOOL)isMemberOfClass:(Class)aClass;
// 判斷一個對象或者類對應的objc_class裡面是否實現了某個協議
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
// 判斷一個對象或者類對應的objc_class裡面有沒有某個方法
- (BOOL)respondsToSelector:(SEL)aSelector;

二、runtime中的數據結構
一中舉出了一些OC的runtime方法,很易懂,因為OC就是我們的開發語言,下面我會一個個解讀runtime中的C語言的接口。因為C中沒有class的概念,只有struct,所以在介紹C語言接口之前,這裡我將挨個介紹在runtime中使用到的常見結構體。
1、objc_class
類結構體,對應class。前幾篇文章提到最多的一個結構。
objc_class

struct objc_class {
    // 指向元類的的指針,如果本身是元類,則指向rootMeta
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    // 指向父類
    Class super_class                                        OBJC2_UNAVAILABLE;
    // 類名
    const char *name                                         OBJC2_UNAVAILABLE;
    // 版本號,可以用runtime方法set,get
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    // 成員變量列表,存儲成員變量
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    // 方法列表
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    // 方法cache,msgSend遍歷繼承鏈的時候需要輔助使用
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    // 協議列表
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
OBJC2_UNAVAILABLE宏定義是蘋果在 Objc 中對系統運行版本進行約束的操作,為的是兼容非Objective-C 2.0的遺留邏輯,但我們仍能從中獲得一些有價值的信息,有興趣的可以查看源代碼,見底部文獻。
2、與objc_class直接相關
這裡面舉出了存儲於上述objc_class結構體之中的一些相關結構體。
objc_method

// 存在objc_method_list裡面
struct objc_method {
    // 方法名
    SEL method_name                                          OBJC2_UNAVAILABLE;
    // 參數,返回值編碼
    char *method_types                                       OBJC2_UNAVAILABLE;
    // 方法地址指針
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

objc_method_description

// 方法描述
struct objc_method_description {
    // 方法名
    SEL name;     
    // 參數,返回值編碼             
    char *types;           
};

objc_protocol_list

struct objc_protocol_list {
    // 指向下一個objc_protocol_list的指針
    struct objc_protocol_list *next;
    long count;
    // 協議結構
    Protocol *list[1];
};

objc_ivar

struct objc_ivar {
    // 成員變量名
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    // 成員變量的類型,比如@"NSString" 代表NSString
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}

3、與分類相關
分類是不會在編譯時刻綁定到對應的類裡的,只能在運行時動態綁定。所以這裡把它拿出來。
objc_category

struct objc_category {
    // 分類名稱
    char *category_name                                      OBJC2_UNAVAILABLE;
    // 綁定的類名
    char *class_name                                         OBJC2_UNAVAILABLE;
    // 分類中的實例方法
    struct objc_method_list *instance_methods                OBJC2_UNAVAILABLE;
    // 分類中的類方法
    struct objc_method_list *class_methods                   OBJC2_UNAVAILABLE;
    // 分類實現的協議
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
}

分類和類擴展區別:
extension看起來很像一個匿名的category,但是extension和有名字的category幾乎完全是兩個東西。 extension在編譯期決議,它就是類的一部分,在編譯期和頭文件裡的@interface以及實現文件裡的@implement一起形成一個完整的類,它伴隨類的產生而產生,亦隨之一起消亡。但是category則完全不一樣,它是在運行期決議的。如想學習更多category相關底層,參見:http://tech.meituan.com/DiveIntoCategory.html。
4、屬性相關
這裡為什麼把屬性拿出來呢?因為上述的兼容OC1.0的objc_class裡面沒有property。看了runtime的源碼之後,發現property有一個具體的存儲地點,也在objc_class(實質是繼承了objc_object)中,至於上述objc_class沒有,只是因為apple不想讓我們看見。如想了解更多,參考:runtime源碼的objc-runtime-new.mm和objc-private.h。
objc_property_t

// 屬性對應的結構體objc_property,至於具體結構,一般看不著
typedef struct objc_property *objc_property_t;
// 在objc-runtime-old.h中的結構體作為參考吧
struct old_property {
    // 屬性名
    const char *name;
    // objc_property_attribute_t的char*表示
    const char *attributes;
};

objc_property_attribute_t

// 這是用來修飾表示屬性的一些修飾詞,比如nonatomic、copy等等
typedef struct {
    const char *name;          
    const char *value;         
} objc_property_attribute_t;

objc_property_attribute_t具體參考:https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW1。
三、runtime的函數
到這也就到了正菜了。本章我會把runtime庫中public部分所有和class相關的api都調用一遍,解釋一遍。至於ivar相關、method相關、protocol相關、property相關和具體應用,且待下回分解。
前提

// 為獲取class的protocol准備
@protocol AProtocol - (void)aProtocolMethod;
@end
// 為獲取class的相關信息
@interface A : NSObject {
    NSString *strA;
}
@property (nonatomic, assign) NSUInteger uintA;
@end
@implementation A
@end
// 為為class添加方法准備
void aNewMethod() {
    NSLog(@"aNewMethod");
}
void aReplaceMethod() {
    NSLog(@"aReplaceMethod");
}

class相關使用場景1(class一些基礎信息獲取):

// 代碼
// 獲取類名
const char *a = class_getName([A class]); 
NSLog(@"%s", a);  // a
// 獲取父類
Class aSuper = class_getSuperclass([A class]);
NSLog(@"%s", class_getName(aSuper));  // b
// 判斷是否是元類
BOOL aIfMeta = class_isMetaClass([A class]);
BOOL aMetaIfMeta = class_isMetaClass(objc_getMetaClass("A"));
NSLog(@"%i  %i", aIfMeta, aMetaIfMeta);   // c
// 類大小
size_t aSize = class_getInstanceSize([A class]);
NSLog(@"%zu", aSize);  // d
// 獲取和設置類版本號
class_setVersion([A class], 1);
NSLog(@"%d", class_getVersion([A class]));  // e
// 獲取工程中所有的class,包括系統class
unsigned int count3;
int classNum = objc_getClassList(NULL, count3);
NSLog(@"%d", classNum);    // f
// 獲取工程中所有的class的數量
objc_copyClassList(&count3);
NSLog(@"%d", classNum);    // g
Class aClass;
// 獲取name為"A"的class
aClass = objc_getClass("A");    
NSLog(@"%s", class_getName(aClass));    // h
// 獲取name為"A"的class,比getClass少了一次檢查
aClass = objc_lookUpClass("A");
NSLog(@"%s", class_getName(aClass));    // i
// 獲取name為"A"的class,找不到會crash
aClass = objc_getRequiredClass("A");
NSLog(@"%s", class_getName(aClass));    // j
// 獲取name為"A"的class元類
Class aMetaClass = objc_getMetaClass("A");
NSLog(@"%d", class_isMetaClass(aMetaClass));    // k
// 輸出
2017-01-21 12:15:55.909 block[2493:1919841] A    // a
2017-01-21 12:15:55.912 block[2493:1919841] NSObject    // b
2017-01-21 12:15:55.913 block[2493:1919841] 0  1    // c
2017-01-21 12:15:55.913 block[2493:1919841] 24    // d
2017-01-21 12:15:55.914 block[2493:1919841] 1    // e
2017-01-21 12:44:32.401 block[5103:1948802] 4733    // f
2017-01-21 12:44:32.402 block[5103:1948802] 4733    // g
2017-01-21 12:44:32.402 block[5103:1948802] A    // h 
2017-01-21 12:44:32.403 block[5103:1948802] A    // i
2017-01-21 12:44:32.403 block[5103:1948802] A    // j
2017-01-21 12:44:32.403 block[5103:1948802] 1    // k

class相關使用場景2(class中的ivar和property)

// 代碼
// 獲取類實例成員變量,只能取到本類的,父類的訪問不到
Ivar aInstanceIvar = class_getInstanceVariable([A class], "strA");
NSLog(@"%s", ivar_getName(aInstanceIvar));    // a
// 獲取類成員變量,相當於class_getInstanceVariable(cls->isa, name),感覺除非給metaClass添加成員,否則不會獲取到東西
Ivar aClassIvar = class_getClassVariable([A class], "strA");
NSLog(@"%s", ivar_getName(aClassIvar));    // b
// 往A類添加成員變量不會成功的。因為class_addIvar不能給現有的類添加成員變量,也不能給metaClass添加成員變量,那怎麼添加,且往後看
if (class_addIvar([A class], "intA", sizeof(int), log2(sizeof(int)), @encode(int))) {
    NSLog(@"綁定成員變量成功");    // c
}
// 獲取類中的ivar列表,count為ivar總數
unsigned int count;
Ivar *ivars = class_copyIvarList([A class], &count);
NSLog(@"%i", count);    // d
// 獲取某個名為"uIntA"的屬性
objc_property_t aPro = class_getProperty([A class], "uintA");
NSLog(@"%s", property_getName(aPro));    // e
// 獲取類的全部屬性
class_copyPropertyList([A class], &count);
NSLog(@"%i", count);    // f
// 創建objc_property_attribute_t,然後動態添加屬性
objc_property_attribute_t type = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([NSString class])] UTF8String] }; //type
objc_property_attribute_t ownership0 = { "C", "" }; // C = copy
objc_property_attribute_t ownership = { "N", "" }; //N = nonatomic
objc_property_attribute_t backingivar  = { "V", [[NSString stringWithFormat:@"_%@", @"aNewProperty"] UTF8String] };  //variable name
objc_property_attribute_t attrs[] = { type, ownership0, ownership, backingivar };
if(class_addProperty([A class], "aNewProperty", attrs, 4)) {
    // 只會增加屬性,不會自動生成set,get方法
    NSLog(@"綁定屬性成功");    // g
}
// 創建objc_property_attribute_t,然後替換屬性
objc_property_attribute_t typeNew = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([NSString class])] UTF8String] }; //type
objc_property_attribute_t ownership0New = { "C", "" }; // C = copy
objc_property_attribute_t ownershipNew = { "N", "" }; //N = nonatomic
objc_property_attribute_t backingivarNew  = { "V", [[NSString stringWithFormat:@"_%@", @"uintA"] UTF8String] };  //variable name
objc_property_attribute_t attrsNew[] = { typeNew, ownership0New, ownershipNew, backingivarNew };
class_replaceProperty([A class], "uintA", attrsNew, 4);
// 這有個很大的坑。替換屬性指的是替換objc_property_attribute_t,而不是替換name。如果替換的屬性class裡面不存在,則會動態添加這個屬性
objc_property_t pro = class_getProperty([A class], "uintA");
NSLog(@"123456   %s", property_getAttributes(pro));    // h
// class_getIvarLayout、class_setIvarLayout、class_getWeakIvarLayout、class_setWeakIvarLayout用來設定和獲取成員變量的weak、strong。參見http://blog.sunnyxx.com/2015/09/13/class-ivar-layout/
// 輸出
2017-01-21 12:44:32.377 block[5103:1948802] strA    // a
2017-01-21 12:44:32.377 block[5103:1948802] (null)    // b
2017-01-21 12:44:32.377 block[5103:1948802] 2    // d
2017-01-21 12:44:32.377 block[5103:1948802] uintA    // e
2017-01-21 12:44:32.378 block[5103:1948802] 1    // f
2017-01-21 12:44:32.378 block[5103:1948802] 綁定屬性成功    // g
2017-01-21 12:44:32.379 block[5103:1948802] 123456   T@"NSString",C,N,V_uintA    // h

class相關使用場景3(class中的method)

// 代碼
// 動態添加方法
class_addMethod([A class], @selector(aNewMethod), (IMP)aNewMethod, "v");
// 向元類動態添加類方法
class_addMethod(objc_getMetaClass("A"), @selector(aNewMethod), (IMP)aNewMethod, "v");
// 獲取類實例方法
Method aMethod = class_getInstanceMethod([A class], @selector(aNewMethod));
// 獲取元類中類方法
Method aClassMethod = class_getClassMethod([A class], @selector(aNewMethod));
NSLog(@"%s", method_getName(aMethod));    // a
NSLog(@"%s", method_getName(aClassMethod));    // b
// 獲取類中的method列表
unsigned int count1;
Method *method = class_copyMethodList([A class], &count1);
// 多了一個方法,打印看出.cxx_destruct,只在arc下有,析構函數
NSLog(@"%i", count1);    // c
NSLog(@"%s", method_getName(method[2]));    // d
 // 替換方法,其實是替換IMP
class_replaceMethod([A class], @selector(aNewMethod), (IMP)aReplaceMethod, "v");
 // 調用aNewMethod,其實是調用了aReplaceMethod
[[A new] performSelector:@selector(aNewMethod)];    // aReplaceMethod會輸出 e
// 獲取類中某個SEL的IMP
IMP aNewMethodIMP = class_getMethodImplementation([A class], @selector(aNewMethod));
aNewMethodIMP();    // 會調用aReplaceMethod的輸出 f
// 獲取類中某個SEL的IMP
IMP aNewMethodIMP_stret = class_getMethodImplementation_stret([A class], @selector(aNewMethod));
aNewMethodIMP_stret();    // 會調用aReplaceMethod的輸出 g
// 判斷A類中有沒有一個SEL
if(class_respondsToSelector([A class], @selector(aNewMethod))) {
    NSLog(@"存在這個方法");    // h
}
// 輸出
2017-01-21 12:44:32.379 block[5103:1948802] aNewMethod    // a
2017-01-21 12:44:32.379 block[5103:1948802] aNewMethod    // b
2017-01-21 12:44:32.380 block[5103:1948802] 4    // c 
2017-01-21 12:44:32.380 block[5103:1948802] setUintA:    // d
2017-01-21 12:44:32.380 block[5103:1948802] aReplaceMethod    // e
2017-01-21 12:44:32.381 block[5103:1948802] aReplaceMethod    // f
2017-01-21 12:44:32.381 block[5103:1948802] aReplaceMethod    // g
2017-01-21 12:44:32.381 block[5103:1948802] 存在這個方法    // h

class相關使用場景4(動態創建類)

// 代碼

// 動態創建一個類和其元類
Class aNewClass = objc_allocateClassPair([NSObject class], "aNewClass", 0);
// 添加成員變量
if (class_addIvar(aNewClass, "intA", sizeof(int), log2(sizeof(int)), @encode(int))) {
    NSLog(@"綁定成員變量成功");    // a
}
// 注冊這個類,之後才能用
objc_registerClassPair(aNewClass);
// 銷毀這個類和元類
objc_disposeClassPair(aNewClass);    

// 輸出
2017-01-21 12:44:32.382 block[5103:1948802] 綁定成員變量成功    // a
class相關使用場景5(class中的protocol)
// 代碼
// 添加protocol到class
if(class_addProtocol([A class], @protocol(AProtocol))) {
    NSLog(@"綁定Protocol成功");    // a
}
// 查看類是不是遵循protocol
if(class_conformsToProtocol([A class], @protocol(AProtocol))) {
    NSLog(@"A遵循AProtocol");    // b
}
// 獲取類中的protocol
unsigned int count2;
Protocol *__unsafe_unretained  *aProtocol = class_copyProtocolList([A class], &count2);
NSLog(@"%s", protocol_getName(aProtocol[0]));    // c

// 輸出
2017-01-21 12:44:32.381 block[5103:1948802] 綁定Protocol成功    // a
2017-01-21 12:44:32.381 block[5103:1948802] A遵循AProtocol    // b
2017-01-21 12:44:32.382 block[5103:1948802] AProtocol    // c

四、小結
本章介紹了runtime的一些OC方法和runtime的數據結構,另外把runtime庫public部分中所有與class相關的C方法都介紹了。下一節,將繼續介紹runtime庫public部分中中的其他的api。(其實,我覺得runtime學習不要太關注應用,准確的說,每一個api都是一種應用)
五、文獻
1、https://developer.apple.com/reference/objectivec/1657527-objective_c_runtime?language=objc
2、https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
3、http://blog.sunnyxx.com/2015/09/13/class-ivar-layout/
4、http://tech.meituan.com/DiveIntoCategory.html
5、https://github.com/opensource-apple/objc4

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved