iOS開發Runtime的簡單使用及封裝

一:簡介

Runtime是屬于Object-C的底層,是一套比較底層的純C語言API,屬于C語言庫, 包含了很多底層的C語言API,可以進行一些非常底層的操作。 RuntimeAPI的實現是用 C++ 開發的(源碼中的實現文件都是mm),是一套蘋果開源的框架。在我們平時編寫的Object-C代碼中, 程序運行過程時, 其實最終都是轉成了Runtime的C語言代碼, Runtime算是Object-C的幕后工作者。

二:Runtime的具體實現

我們寫的Object-C代碼,它在運行的時候也是轉換成了Runtime方式運行的,更好的理解Runtime,也能幫我們更深的掌握Object-C語言。
每一個Object-C的方法,底層必然有一個與之對應的Runtime方法。

當我們用Object-C寫下這樣一段代碼

[tableView cellForRowAtIndexPath:indexPath];

在編譯時RunTime會將上述代碼轉化成[發送消息]

objc_msgSend(tableView, @selector(cellForRowAtIndexPath:),indexPath);

三:常見方法

  • 獲取屬性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i=0; i<count; i++) {
    const char *propertyName = property_getName(propertyList[i]);
    NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
}
  • 獲取方法列表
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i; i<count; i++) {
    Method method = methodList[i];
    NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
}
  • 獲取成員變量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i; i<count; i++) {
    Ivar myIvar = ivarList[i];
    const char *ivarName = ivar_getName(myIvar);
    NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
}
  • 獲取協議列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i; i<count; i++) {
    Protocol *myProtocal = protocolList[i];
    const char *protocolName = protocol_getName(myProtocal);
    NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
}
  • 獲得類方法
Class PersonClass = object_getClass([Person class]);
SEL oriSEL = @selector(test1);
Method oriMethod = class_getInstanceMethod(xiaomingClass, oriSEL);
  • 獲得實例方法
Class PersonClass = object_getClass([xiaoming class]);
SEL oriSEL = @selector(test2);
Method cusMethod = class_getInstanceMethod(xiaomingClass, oriSEL);
  • 添加方法
BOOL addSucc = class_addMethod(XiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
  • 替換原方法實現
class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
  • 交換兩個方法
method_exchangeImplementations(oriMethod, cusMethod);

四:常見作用

  • 動態的添加對象的成員變量和方法
  • 動態交換兩個方法
  • 攔截并替換方法
  • 實現NSCoding的自動歸檔和解檔
  • 實現字典轉模型的自動轉換

五:參數概念

objc_msgSend

/* Basic Messaging Primitives
*
* On some architectures, use objc_msgSend_stret for some struct return types.
* On some architectures, use objc_msgSend_fpret for some float return types.
* On some architectures, use objc_msgSend_fp2ret for some float return types.
*
* These functions must be cast to an appropriate function pointer type 
* before being called. 
*/

從這個函數的注釋可以看出來了,這是個最基本的用于發送消息的函數。另外,這個函數并不能發送所有類型的消息,只能發送基本的消息。比如,在一些處理器上,我們必須使用objc_msgSend_stret來發送返回值類型為結構體的消息,使用objc_msgSend_fpret來發送返回值類型為浮點類型的消息,而又在一些處理器上,還得使用objc_msgSend_fp2ret來發送返回值類型為浮點類型的消息。

最關鍵的一點:無論何時,要調用objc_msgSend函數,必須要將函數強制轉換成合適的函數指針類型才能調用。

objc_msgSend函數的聲明來看,它應該是不帶返回值的,但是我們在使用中卻可以強制轉換類型,以便接收返回值。另外,它的參數列表是可以任意多個的,前提也是要強制函數指針類型。

其實編譯器會根據情況在objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, 或 objc_msgSendSuper_stret四個方法中選擇一個來調用。如果消息是傳遞給超類,那么會調用名字帶有Super的函數;如果消息返回值是數據結構而不是簡單值時,那么會調用名字帶有stret的函數。

SEL

SEL是selector在 Object-C 中的表示(Swift 中是 Selector 類)。selector 是方法選擇器,其實作用就和名字一樣,日常生活中,我們通過人名辨別誰是誰,注意 Object-C 在相同的類中不會有命名相同的兩個方法。selector 對方法名進行包裝,以便找到對應的方法實現。它的數據結構是:

typedef struct objc_selector *SEL;

我們可以看出它是個映射到方法的 C 字符串,你可以通過 Object-C 編譯器器命令@selector()或者 Runtime 系統的 sel_registerName 函數來獲取一個 SEL 類型的方法選擇器。

不同類中相同名字的方法所對應的 selector 是相同的,由于變量的類型不同,所以不會導致它們調用方法實現混亂。

id

id 是一個參數類型,它是指向某個類的實例的指針。定義如下:

typedef struct objc_object *id;
struct objc_object { Class isa; };

以上定義,看到 objc_object 結構體包含一個 isa 指針,根據 isa 指針就可以找到對象所屬的類。

isa指針不總是指向實例對象所屬的類,不能依靠它來確定類型,而是應該用class方法來確定實例對象的類。因為KVO的實現機理就是將被觀察對象的isa指針指向一個中間類而不是真實的類,這是一種叫做 isa-swizzling 的技術。

Class

之所以說isa是指針是因為Class其實是一個指向objc_class結構體的指針:

typedef struct objc_class *Class;

objc_class 的數據結構如下:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if  !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    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;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

從 objc_class 可以看到,一個運行時類中關聯了它的父類指針、類名、成員變量、方法、緩存以及附屬的協議。

其中 objc_ivar_list 和 objc_method_list 分別是成員變量列表和方法列表:

  • 成員變量列表
struct objc_ivar_list {
    int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;
  • 方法列表
struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}

在objc_class結構體中:ivars是objc_ivar_list指針;methodLists是指向objc_method_list指針的指針。也就是說可以動態修改 *methodLists 的值來添加成員方法,這也是Category實現的原理。

Method

Method 代表類中某個方法的類型

typedef struct objc_method *Method;

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

objc_method 存儲了方法名,方法類型和方法實現:

  • 方法名類型為 SEL。
  • 方法類型 method_types 是個 char 指針,存儲方法的參數類型和返回值類型。
  • method_imp 指向了方法的實現,本質是一個函數指針。

Ivar

Ivar 是表示成員變量的類型。

typedef struct objc_ivar *Ivar;

struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}

其中 ivar_offset 是基地址偏移字節。

IMP

IMP在objc.h中的定義是:

typedef id (*IMP)(id, SEL, ...);

它就是一個函數指針,這是由編譯器生成的。當你發起一個 Object-C 消息之后,最終它會執行的那段代碼,就是由這個函數指針指定的。而 IMP 這個函數指針就指向了這個方法的實現。

如果得到了執行某個實例某個方法的入口,我們就可以繞開消息傳遞階段,直接執行方法,這在后面 Cache 中會提到。

你會發現 IMP 指向的方法與 objc_msgSend 函數類型相同,參數都包含 id 和 SEL 類型。每個方法名都對應一個 SEL 類型的方法選擇器,而每個實例對象中的 SEL 對應的方法實現肯定是唯一的,通過一組 id和 SEL 參數就能確定唯一的方法實現地址。

而一個確定的方法也只有唯一的一組 id 和 SEL 參數。

Cache

Cache 定義如下:

typedef struct objc_cache *Cache

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};

Cache 為方法調用的性能進行優化,每當實例對象接收到一個消息時,它不會直接在 isa 指針指向的類的方法列表中遍歷查找能夠響應的方法,因為每次都要查找效率太低了,而是優先在 Cache 中查找。

Runtime 系統會把被調用的方法存到 Cache 中,如果一個方法被調用,那么它有可能今后還會被調用,下次查找的時候就會效率更高。就像計算機組成原理中 CPU 繞過主存先訪問 Cache 一樣。

Property

typedef struct objc_property *Property;
typedef struct objc_property *objc_property_t;//這個更常用

可以通過class_copyPropertyListprotocol_copyPropertyList 方法獲取類和協議中的屬性:

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)

返回的是屬性列表,列表中每個元素都是一個 objc_property_t 指針。

六:消息

一些 Runtime 術語講完了,接下來就要說到消息了。體會蘋果官方文檔中的messages aren’t bound to method implementations until Runtime。消息直到運行時才會與方法實現進行綁定。

這里要清楚一點,objc_msgSend 方法看清來好像返回了數據,其實objc_msgSend 從不返回數據,而是你的方法在運行時實現被調用后才會返回數據。下面詳細敘述消息發送的步驟(如下圖):

消息發送的步驟

  1. 首先檢測這個 selector 是不是要忽略。比如 Mac OS X 開發,有了垃圾回收就不理會 retain,release 這些函數。
  2. 檢測這個 selector 的 target 是不是 nil,Object-C 允許我們對一個 nil 對象執行任何方法不會 Crash,因為運行時會被忽略掉。
  3. 如果上面兩步都通過了,那么就開始查找這個類的實現 IMP,先從 cache 里查找,如果找到了就運行對應的函數去執行相應的代碼。
  4. 如果 cache 找不到就找類的方法列表中是否有對應的方法。
  5. 如果類的方法列表中找不到就到父類的方法列表中查找,一直找到 NSObject 類為止。
  6. 如果還找不到,就要開始進入動態方法解析了,后面會提到。

在消息的傳遞中,編譯器會根據情況在 objc_msgSendobjc_msgSend_stretobjc_msgSendSuperobjc_msgSendSuper_stret 這四個方法中選擇一個調用。如果消息是傳遞給父類,那么會調用名字帶有 Super 的函數,如果消息返回值是數據結構而不是簡單值時,會調用名字帶有 stret 的函數。

方法中的隱藏參數

我們經常用到關鍵字 self ,但是 self 是如何獲取當前方法的對象呢?
其實,這也是 Runtime 系統的作用,self 實在方法運行時被動態傳入的。

當 objc_msgSend 找到方法對應實現時,它將直接調用該方法實現,并將消息中所有參數都傳遞給方法實現,同時,它還將傳遞兩個隱藏參數:

  • 接受消息的對象(self 所指向的內容,當前方法的對象指針)
  • 方法選擇器(_cmd 指向的內容,當前方法的 SEL 指針)

因為在源代碼方法的定義中,我們并沒有發現這兩個參數的聲明。它們時在代碼被編譯時被插入方法實現中的。盡管這些參數沒有被明確聲明,在源代碼中我們仍然可以引用它們。

這兩個參數中, self更實用。它是在方法實現中訪問消息接收者對象的實例變量的途徑。

這時我們可能會想到另一個關鍵字 super ,實際上 super 關鍵字接收到消息時,編譯器會創建一個 objc_super 結構體:

struct objc_super { id receiver; Class class; };

這個結構體指明了消息應該被傳遞給特定的父類。 receiver 仍然是 self 本身,當我們想通過 [super class] 獲取父類時,編譯器其實是將指向 self 的 id 指針和 class 的 SEL 傳遞給了 objc_msgSendSuper 函數。只有在 NSObject 類中才能找到 class 方法,然后 class 方法底層被轉換為 object_getClass(), 接著底層編譯器將代碼轉換為 objc_msgSend(objc_super->receiver, @selector(class)),傳入的第一個參數是指向 self 的 id 指針,與調用 [self class] 相同,所以我們得到的永遠都是 self 的類型。因此你會發現:

NSLog(@"%@", NSStringFromClass([super class]));

這句話并不能獲取父類的類型,只能獲取當前類的類型名。

獲取方法地址

NSObject 類中有一個實例方法:methodForSelector,你可以用它來獲取某個方法選擇器對應的 IMP ,舉個例子:

void (*setter)(id, SEL, BOOL);
int i;

setter = (void (*)(id, SEL, BOOL))[target
    methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
    setter(targetList[i], @selector(setFilled:), YES);

當方法被當做函數調用時,兩個隱藏參數也必須明確給出,上面的例子調用了1000次函數,你也可以嘗試給 target 發送1000次 setFilled: 消息會花多久。

雖然可以更高效的調用方法,但是這種做法很少用,除非時需要持續大量重復調用某個方法的情況,才會選擇使用以免消息發送泛濫。

動態方法解析

你可以動態提供一個方法實現。如果我們使用關鍵字 @dynamic 在類的實現文件中修飾一個屬性,表明我們會為這個屬性動態提供存取方法,編譯器不會再默認為我們生成這個屬性的 setter 和 getter 方法了,需要我們自己提供。

@dynamic propertyName;

這時,我們可以通過分別重載 resolveInstanceMethod: 和 resolveClassMethod: 方法添加實例方法實現和類方法實現。

當 Runtime 系統在 Cache 和類的方法列表(包括父類)中找不到要執行的方法時,Runtime 會調用 resolveInstanceMethod: 或 resolveClassMethod: 來給我們一次動態添加方法實現的機會。我們需要用 class_addMethod 函數完成向特定類添加特定方法實現的操作:

void dynamicMethodIMP(id self, SEL _cmd) {
    // implementation ....
}
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(resolveThisMethodDynamically)) {
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}
@end

上面的例子為 resolveThisMethodDynamically 方法添加了實現內容,就是 dynamicMethodIMP 方法中的代碼。其中 "v@:" 表示返回值和參數,這個符號表示的含義見:Type Encoding

動態方法解析會在消息轉發機制侵入前執行,動態方法解析器將會首先給予提供該方法選擇器對應的 IMP 的機會。如果你想讓該方法選擇器被傳送到轉發機制,就讓 resolveInstanceMethod: 方法返回 NO。

消息轉發

image

重定向

消息轉發機制執行前,Runtime 系統允許我們替換消息的接收者為其他對象。通過 - (id)forwardingTargetForSelector:(SEL)aSelector 方法。

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if(aSelector == @selector(mysteriousMethod:)){
        return alternateObject;
    }
    return [super forwardingTargetForSelector:aSelector];
}

如果此方法返回 nil 或者 self,則會計入消息轉發機制(forwardInvocation:),否則將向返回的對象重新發送消息。

轉發

當動態方法解析不做處理返回 NO 時,則會觸發消息轉發機制。這時 forwardInvocation: 方法會被執行,我們可以重寫這個方法來自定義我們的轉發邏輯:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([someOtherObject respondsToSelector:
            [anInvocation selector]])
        [anInvocation invokeWithTarget:someOtherObject];
    else
        [super forwardInvocation:anInvocation];
}

唯一參數是個 NSInvocation 類型的對象,該對象封裝了原始的消息和消息的參數。我們可以實現 forwardInvocation: 方法來對不能處理的消息做一些處理。也可以將消息轉發給其他對象處理,而不拋出錯誤。

在 forwardInvocation: 消息發送前,Runtime 系統會向對象發送methodSignatureForSelector: 消息,并取到返回的方法簽名用于生成 NSInvocation 對象。所以重寫 forwardInvocation: 的同時也要重寫 methodSignatureForSelector: 方法,否則會拋異常。

當一個對象由于沒有相應的方法實現而無法相應某消息時,運行時系統將通過 forwardInvocation: 消息通知該對象。每個對象都繼承了 forwardInvocation: 方法。但是, NSObject 中的方法實現只是簡單的調用了 doesNotRecognizeSelector:。通過實現自己的 forwardInvocation: 方法,我們可以將消息轉發給其他對象。

forwardInvocation: 方法就是一個不能識別消息的分發中心,將這些不能識別的消息轉發給不同的接收對象,或者轉發給同一個對象,再或者將消息翻譯成另外的消息,亦或者簡單的“吃掉”某些消息,因此沒有響應也不會報錯。這一切都取決于方法的具體實現。

forwardInvocation:方法只有在消息接收對象中無法正常響應消息時才會被調用。所以,如果我們向往一個對象將一個消息轉發給其他對象時,要確保這個對象不能有該消息的所對應的方法。否則,forwardInvocation:將不可能被調用。

轉發和多繼承

轉發和繼承相似,可用于為 Objc 編程添加一些多繼承的效果。就像下圖那樣,一個對象把消息轉發出去,就好像它把另一個對象中的方法接過來或者“繼承”過來一樣。


這使得在不同繼承體系分支下的兩個類可以實現“繼承”對方的方法,在上圖中 Warrior 和 Diplomat 沒有繼承關系,但是 Warrior 將 negotiate 消息轉發給了 Diplomat 后,就好似 Diplomat 是 Warrior 的超類一樣。

消息轉發彌補了 Objc 不支持多繼承的性質,也避免了因為多繼承導致單個類變得臃腫復雜。

轉發與繼承

雖然轉發可以實現繼承的功能,但是 NSObject 還是必須表面上很嚴謹,像 respondsToSelector: 和 isKindOfClass: 這類方法只會考慮繼承體系,不會考慮轉發鏈。

如果上圖中的 Warrior 對象被問到是否能響應 negotiate消息:

if ( [aWarrior respondsToSelector:@selector(negotiate)] )
    ...

回答當然是 NO, 盡管它能接受 negotiate 消息而不報錯,因為它靠轉發消息給 Diplomat 類響應消息。

如果你就是想要讓別人以為 Warrior 繼承到了 Diplomat 的 negotiate 方法,你得重新實現 respondsToSelector: 和 isKindOfClass: 來加入你的轉發算法:

- (BOOL)respondsToSelector:(SEL)aSelector
{
    if ( [super respondsToSelector:aSelector] )
        return YES;
    else {
        /* Here, test whether the aSelector message can     *
         * be forwarded to another object and whether that  *
         * object can respond to it. Return YES if it can.  */
    }
    return NO;
}

除了 respondsToSelector: 和 isKindOfClass: 之外,instancesRespondToSelector: 中也應該寫一份轉發算法。如果使用了協議,conformsToProtocol: 同樣也要加入到這一行列中。

如果一個對象想要轉發它接受的任何遠程消息,它得給出一個方法標簽來返回準確的方法描述 methodSignatureForSelector:,這個方法會最終響應被轉發的消息。從而生成一個確定的 NSInvocation 對象描述消息和消息參數。這個方法最終響應被轉發的消息。它需要像下面這樣實現:

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
    NSMethodSignature* signature = [super methodSignatureForSelector:selector];
    if (!signature) {
       signature = [surrogate methodSignatureForSelector:selector];
    }
    return signature;
}

七:健壯的實例變量(Non Fragile ivars)

在 Runtime 的現行版本中,最大的特點就是健壯的實例變量了。當一個類被編譯時,實例變量的內存布局就形成了,它表明訪問類的實例變量的位置。實例變量一次根據自己所占空間而產生位移:

上圖左是 NSObject 類的實例變量布局。右邊是我們寫的類的布局。這樣子有一個很大的缺陷,就是缺乏拓展性。哪天蘋果更新了 NSObject 類的話,就會出現問題:

我們自定義的類的區域和父類的區域重疊了。只有蘋果將父類改為以前的布局才能拯救我們,但這樣導致它們不能再拓展它們的框架了,因為成員變量布局被固定住了。在脆弱的實例變量(Fragile ivar)環境下,需要我們重新編譯繼承自 Apple 的類來恢復兼容。

在健壯的實例變量下,編譯器生成的實例變量布局跟以前一樣,但是當 Runtime 系統檢測到與父類有部分重疊時它會調整你新添加的實例變量的位移,那樣你再子類中新添加的成員變量就被保護起來了。

八:Runtime的封裝

接下來我們對Runtime常用的方法進行了簡單的封裝:

#import <Foundation/Foundation.h>

@interface NSObject (Runtime)

/** 獲取成員變量,包括屬性生成的成員變量 */
+ (NSArray *)fetchIvarList;

/** 獲取類的屬性列表,包括私有和公有屬性,也包括分類中的屬性 */
+ (NSArray *)fetchPropertyList;

/** 獲取對象方法列表:包括getter, setter, 分類中的方法等 */
+ (NSArray *)fetchInstanceMethodList;

/** 獲取類方法列表 包括分類里面的 */
+ (NSArray *)fetchClassMethodList;

/** 獲取協議列表,包括.h .m 和分類里的 */
+ (NSArray *)fetchProtocolList;

/** 添加一個方法 */
+ (void)addMethod:(SEL)methodSel methodImp:(SEL)methodImp;

/** 實例方法交換 */
+ (void)swapMethod:(SEL)originMethod currentMethod:(SEL)currentMethod;

/** 類方法交換 */
+ (void)swapClassMethod:(SEL)originMethod currentMethod:(SEL)currentMethod;

@end
  1. 獲取成員變量
    下面這個方法就是獲取類的成員變量列表,其中包括屬性生成的成員變量。我們可以用ivar_getTypeEncoding()來獲取成員變量的類型,用ivar_getName()來獲取成員變量的名稱:
+ (NSArray *)fetchIvarList
{
    unsigned int count = 0;
    Ivar *ivarList = class_copyIvarList(self, &count);
    NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
    for (unsigned int i = 0; i < count; i++ )
    {
        NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithCapacity:2];
        const char *ivarName = ivar_getName(ivarList[i]);
        const char *ivarType = ivar_getTypeEncoding(ivarList[i]);
        dic[@"type"] = [NSString stringWithUTF8String: ivarType];
        dic[@"ivarName"] = [NSString stringWithUTF8String: ivarName];
        [mutableList addObject:dic];
    }
    free(ivarList);
    return [NSArray arrayWithArray:mutableList];
}
  1. 獲取屬性列表
    下面這個方法獲取的是屬性列表,包括私有和公有屬性,也包括分類中的屬性:
+ (NSArray *)fetchPropertyList
{
    unsigned int count = 0;
    objc_property_t *propertyList = class_copyPropertyList(self, &count);
    NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
    for (unsigned int i = 0; i < count; i++)
    {
        const char *propertyName = property_getName(propertyList[i]);
        [mutableList addObject:[NSString stringWithUTF8String:propertyName]];
    }
    free(propertyList);
    return [NSArray arrayWithArray:mutableList];
}
  1. 獲取實例方法
    下面這個方法就是獲取類的實例方法列表,包括getter, setter, 分類中的方法等:
+ (NSArray *)fetchInstanceMethodList
{
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(self, &count);
    NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
    for (unsigned int i = 0; i < count; i++)
    {
        Method method = methodList[i];
        SEL methodName = method_getName(method);
        [mutableList addObject:NSStringFromSelector(methodName)];
    }
    free(methodList);
    return [NSArray arrayWithArray:mutableList];
}
  1. 獲取類方法列表
    下方這個方法就是獲取類的類方法列表:
+ (NSArray *)fetchClassMethodList
{
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(object_getClass(self), &count);
    NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
    for (unsigned int i = 0; i < count; i++)
    {
        Method method = methodList[i];
        SEL methodName = method_getName(method);
        [mutableList addObject:NSStringFromSelector(methodName)];
    }
    free(methodList);
    return [NSArray arrayWithArray:mutableList];
}
  1. 獲取協議列表
    下面是獲取類所遵循協議列表的方法:
+ (NSArray *)fetchProtocolList
{
    unsigned int count = 0;
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList(self, &count);
    NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
    for (unsigned int i = 0; i < count; i++ )
    {
        Protocol *protocol = protocolList[i];
        const char *protocolName = protocol_getName(protocol);
        [mutableList addObject:[NSString stringWithUTF8String:protocolName]];
    }
    return [NSArray arrayWithArray:mutableList];
}
  1. 給類添加一個方法
    下面的方法就是給類添加方法。第一個參數是方法的SEL,第二個參數則是提供方法實現的SEL。這個可以用在找不到某個方法時就添加一個,不然有可能會崩潰。
+ (void)addMethod:(SEL)methodSel methodImp:(SEL)methodImp;
{
    Method method = class_getInstanceMethod(self, methodImp);
    IMP methodIMP = method_getImplementation(method);
    const char *types = method_getTypeEncoding(method);
    class_addMethod(self, methodSel, methodIMP, types);
}
  1. 交換實例方法
    下面的方法就是將類的兩個實例方法進行交換。如果將originMethod與currentMethod的方法實現進行交換的話,調用originMethod時就會執行currentMethod的內容。
+ (void)swapMethod:(SEL)originMethod currentMethod:(SEL)currentMethod;
{
    Method firstMethod = class_getInstanceMethod(self, originMethod);
    Method secondMethod = class_getInstanceMethod(self, currentMethod);
    method_exchangeImplementations(firstMethod, secondMethod);
}
  1. 交換類方法
    下面的方法就是將類的兩個類方法進行交換,與交換實例方法類似。
+ (void)swapClassMethod:(SEL)originMethod currentMethod:(SEL)currentMethod;
{
    Method firstMethod = class_getClassMethod(self, originMethod);
    Method secondMethod = class_getClassMethod(self, currentMethod);
    method_exchangeImplementations(firstMethod, secondMethod);
}

詳見Demo

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容