1. 程式人生 > >Objective-C 中的Runtime的詳細使用

Objective-C 中的Runtime的詳細使用

enc ring 博客 document 每次 tps htm lec guid

Runtime全面了解

一直以來,OC被大家冠以動態語言的稱謂,其實是因為OC中包含的runtime機制。Runtime 又叫運行時,是一套底層的 C 語言 API,其為 iOS 內部的核心之一,我們平時編寫的 OC 代碼,底層都是基於它來實現的。這一組API可以在Xcode的runtime.h文檔中看到。

關於Runtime的深層次的東西,在很多其他開發者的博客中都有介紹。比如下面這些。

http://www.cnblogs.com/ioshe/ 這篇文章對與初識runtime做了很多基礎性的介紹,並就runtime一些特性做了深入的講解。

https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01《招聘一個靠譜的iOS》面試題參考答案/《招聘一個靠譜的iOS》面試題參考答案 (上).md 這裏針對一些高質量的iOS面試題做的講解。 其中包含了很多關於runtime 的知識。 看完之後大有裨益。

在本文中,不會很會很深入的進入到Runtime,而是就我們的開發過程中,如何使用runtime來簡便的實現一些功能。主要包含以下方面的內容:

  • runtime 獲取類與對象的信息。
  • 如何動態給對象添加成員變量。
  • 如何動態給成員變量添加屬性。
  • 如何動態的給對象添加方法。
  • categroy關聯屬性。
  • 消息轉發如何實現。
  • 如何替換一個已有的方法的實現。

一、runtime 獲取對象的信息。

通過簡單的使用runtime可以獲取到有關於類和對象的一些信息。

@interface GetClassAndIvarInfo ()

//屬性
@property (nonatomic,copy) NSString* name;
@property (nonatomic,assign) 
int age; @property (nonatomic,assign) BOOL isMan; @end @implementation GetClassAndIvarInfo{ //添加的變量 NSString* _adr; } /** 獲取類相關的信息 */ - (void)getRegisteredClassInfo{ int bufferCount = 0; bufferCount = objc_getClassList(NULL, bufferCount); //開辟一段空間 用於存儲即將獲取的類。 //類型的目的是: 告訴編譯器我需要多大的空間
__unsafe_unretained Class *buffer = ( __unsafe_unretained Class *)malloc(sizeof(Class) * bufferCount); objc_getClassList(buffer, bufferCount); for (int i = 0; i < bufferCount; i++) { //查找本類是不是在裏面 if(strcmp(class_getName([self class]), class_getName(buffer[i])) == 0){ NSLog(@"%s", class_getName(buffer[i])); } } } /** 獲取所有的屬性 */ - (void)getAllProp{ unsigned int outCount = 0; NSLog(@"屬性"); objc_property_t *props = class_copyPropertyList([self class], &outCount); for (int i = 0; i < outCount; i++) { NSLog(@"%s",property_getName(props[i])); }
free(props); }
/** 獲取所有的變量 */ - (void)getAllIvar{ unsigned int outCount = 0; Ivar *ivars = class_copyIvarList( object_getClass(self),&outCount); NSLog(@"變量"); for (int i = 0; i < outCount; i++) { NSLog(@"%s",ivar_getName(ivars[i])); }
free(ivars); }
/** 獲取所有的方法 */ - (void)getAllMethod{ unsigned int outCount = 0; Method *methods = class_copyMethodList(object_getClass(self),&outCount); NSLog(@"方法名"); for (int i = 0; i < outCount; i++) { NSLog(@"%s",sel_getName(method_getName(methods[i]))); }
free(methods);
}

通過上面的這些方法。 我們可以方便的做一些有關方法屬性的工作了。 比如,當對某個類進行歸檔的時候,如果能獲取累類的所有屬性,運用KVC進行賦值和取值。就能用很簡短的代碼實現整個類的歸檔動作。

除了上面的幾個簡單的方法之外,還有很多非常實用的runtime的API:

  • OBJC_EXPORT id object_getIvar(id obj, Ivar ivar) //給變量設置值 KVC通過這個方式做
  • OBJC_EXPORT void object_setIvar(id obj, Ivar ivar, id value) //獲取成員變量的值 KVC
  • OBJC_EXPORT Class objc_getMetaClass(const char *name) //獲取該類的元類, 用於分析isa指針
  • Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, unsigned int *outCount) //獲取類遵循的協議中的方法列表
  • OBJC_EXPORT Class objc_getFutureClass(const char *name) //toll-free bridging. 分析中,獲取轉換類的名字

二、runtime 給類添加成員變量

oc中,我們還可以給一個類動態的添加成員變量。 但是有一個前提是:被添加成員變量的類必須是動態創建的類。曾經有個人問我,對於已經編譯的類,能否使用運行時添加成員變量, 答案是不行的。 好,下面的代碼演示如何創建一個動態的類。

/*
     1. 創建一個類。比如: Car,繼承自NSObjest
     2. 給這個類添加兩個成員變量,分別是: 車身的顏色 bodyColor 和 車的最高速度 maxSpeed
     3. 添加一些方法。以便可以訪問兩個成員變量。
     3. 使用這個類創建對象,並對對象的成員屬性進行訪問。
     */
    NSString *bodyColorName = @"bodyColor";   //類型為  UIcolor
    NSString *maxSpeedName = @"maxSpeed";     // 類型為 NSString
    NSString *className = @"Car";
    Class Car = objc_getClass([className UTF8String]);
    if (!Car)
    {
        Class superClass = [NSObject class];
        Car = objc_allocateClassPair(superClass, [className UTF8String], 0);

//添加成員變量的代碼必須放在這裏
objc_registerClassPair(Car);
//註冊到運行時 }

這些代碼演示添加成員變量

 if(class_addIvar([Car class],[maxSpeedName UTF8String], sizeof(NSString *), log2(_Alignof(NSString *)), @encode(NSString *))){
            
            NSLog( @"添加最高速度成功。");
  }
 if(class_addIvar([Car class],[bodyColorName UTF8String], sizeof(UIColor *), log2(_Alignof(UIColor *)), @encode(UIColor *))){
            
            NSLog( @"添加車身速顏色成功。");
  }

雖然每次添加成功之後,會打印相關的提示文字,何不驗證一下呢?運用上一節的內容,打印一下car的所有的成員變量吧

 id car = [[Car alloc]init];
        unsigned int outCount = 0;
        Ivar *ivars = class_copyIvarList( object_getClass(car),&outCount);
        
        NSLog(@"變量");
        for (int i = 0; i < outCount; i++) {
            NSLog(@"%s",ivar_getName(ivars[i]));
        }
        free(ivars);

結果是:

2017-05-12 11:11:31.784 runtimeTest[3072:90675] 添加最高速度成功。

2017-05-12 11:11:31.784 runtimeTest[3072:90675] 添加車身速顏色成功。

2017-05-12 11:11:31.784 runtimeTest[3072:90675] maxSpeed

2017-05-12 11:11:31.784 runtimeTest[3072:90675] bodyColor

看來對了。

如何訪問添加的變量? 通過runtime可以做到。

  unsigned int outCount = 0;
        Ivar *ivars = class_copyIvarList( object_getClass(car),&outCount);
        
        NSLog(@"變量");
        //runtime 賦值
        for (int i = 0; i < outCount; i++) {
            NSLog(@"%s",ivar_getName(ivars[i]));
            
            if(strcmp(ivar_getName(ivars[i]), [bodyColorName UTF8String])){
               object_setIvar(car, ivars[i] , [UIColor blueColor]);
            }
            if(strcmp(ivar_getName(ivars[i]), [maxSpeedName UTF8String])){
                object_setIvar(car, ivars[i] ,@"205.5 km/h");
            }
        }
        
        //runtime 取值
        for (int i = 0; i < outCount; i++) {
            if(strcmp(ivar_getName(ivars[i]), [bodyColorName UTF8String])){
        
                 NSLog(@"車的顏色是%@", object_getIvar(car, ivars[i]));
            }
            if(strcmp(ivar_getName(ivars[i]), [maxSpeedName UTF8String])){
                object_getIvar(car, ivars[i]);
                 NSLog(@"速度是%@",object_getIvar(car, ivars[i]));
            }
        }
        free(ivars);
        

當然這樣每次寫起來實在是不怎麽友好。 其實又個更簡單的辦法,利用KVC。

 //利用KVC賦值 取值
        [car setValue:[UIColor redColor] forKey:bodyColorName];
        [car setValue:@"199.6 km/h" forKey:maxSpeedName];
        NSLog(@"車的顏色是%@, 速度是%@",[car valueForKey:bodyColorName],[car valueForKey:maxSpeedName]);

三、如何動態的給成員變量添加屬性。

剛才我創建了一個類,並給他添加了成員變量,並且做到了如何進行訪問。 接下來我還希望能給這些成員變量添加添加的屬性,以便編譯器更好的幫我們做內存的管理等。比如nonatomic、copy之類的屬性。

比如我們要為成員變量 maxSpeedName 添加 nonatomic、copy屬性。看這些代碼.

       /* 添加成員變量的屬性 */
        
        //在添加之前,需要先編輯屬性。  //這裏給 maxSpeed成員變量添加屬性。 這些屬性的 encode 可以官網看到。
        objc_property_attribute_t type = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([car class])] UTF8String] };
        objc_property_attribute_t ownership = { "&", "N" };
        objc_property_attribute_t ownership0 = { "C", "" }; // C = copy
        objc_property_attribute_t ownership1 = { "N", "" }; // N = nonatomic
        objc_property_attribute_t backingivar  = { "V", [[NSString stringWithFormat:@"_%@", maxSpeedName] UTF8String] };
        
        //這裏需要註意的是: type和backingivar 必須放在頭部和尾部 不然會有意想不到的後果
        objc_property_attribute_t attrs[] = { type, ownership,ownership0,ownership1,backingivar};

        //參數的描述分別是: 對象的類,屬性的預設名字,屬性數組,屬性的個數
        if(class_addProperty([car class], [maxSpeedName UTF8String], attrs, 5)){
            NSLog(@"添加屬性maxSpeedName 成功");
            //打印下
            unsigned int outCount = 0;
            NSLog(@"屬性");
            objc_property_t *props = class_copyPropertyList([car class], &outCount);
            for (int i = 0; i < outCount; i++) {
                
                NSLog(@"名字:%s",property_getName(props[i]));
                //屬性值
                NSLog(@"屬性值:%s",property_getAttributes(props[i]));
            }
            free(props);

添加屬性之後,如果設置setter和getter方法,那麽這些操作需要根據不同的屬性進行設置,比如,storeWeak 就表示對帶有weak屬性的變量進行存儲。

四、如何動態的給對象添加方法。

添加屬性之後,我們最好還是能添加響應的個體和set方法,這是OC一貫的風格。

  //添加get和set方法
            class_addMethod([car class], NSSelectorFromString(maxSpeedName), (IMP)getter, "@@:");
            class_addMethod([car class], NSSelectorFromString([NSString stringWithFormat:@"set%@:",[maxSpeedName capitalizedString]]), (IMP)setter, "v@:@");
            outCount = 0;
            Method *methods = class_copyMethodList(object_getClass(car),&outCount);
            NSLog(@"方法名");
            for (int i = 0; i < outCount; i++) {
                NSLog(@"%s",sel_getName(method_getName(methods[i])));
                NSLog(@"%p",sel_getName(method_getImplementation(methods[i])));

                
            }
            free(methods);
            
            //調用    
            [self setMaxSpeed:@"300km/h" target:car];
            NSLog(@"%@",[self maxSpeedWithTarget:car]);

- (void)setMaxSpeed:(NSString *)maxSpeed target:(NSObject*)car{
    // 動態添加的方法,需要使用performselector調用。  因為在註冊的類中,我們還沒有設置改變類的變量布,也沒有設置方法列表。  
    if([car respondsToSelector:NSSelectorFromString(@"setMaxspeed:" )]){
    [car performSelector:NSSelectorFromString(@"setMaxspeed:") withObject:@"300 km/h"];
    }
}

- (NSString *)maxSpeedWithTarget:(NSObject*)car{
    //這裏不僅判斷有可能報錯的  
if([car respondsTOSelector:NSSelectorFromString:(@"maxspeed")]){
return [car performSelector:NSSelectorFromString(@"maxspeed")];
} }
id getter(id self1, SEL _cmd1) { NSString *key = NSStringFromSelector(_cmd1); Ivar ivar = class_getInstanceVariable([self1 class], [key cStringUsingEncoding:NSUTF8StringEncoding]); NSString *s = object_getIvar(self1, ivar); return s; } void setter(id self1, SEL _cmd1, id newValue) { //移除set NSString *key = [NSStringFromSelector(_cmd1) stringByReplacingCharactersInRange:NSMakeRange(0, 3) withString:@""]; //首字母小寫 NSString *head = [key substringWithRange:NSMakeRange(0, 1)]; head = [head lowercaseString]; key = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:head]; //移除後綴 ":" key = [key stringByReplacingCharactersInRange:NSMakeRange(key.length - 1, 1) withString:@""]; Ivar ivar = class_getInstanceVariable([self1 class], [key cStringUsingEncoding:NSUTF8StringEncoding]); object_setIvar(self1, ivar, newValue); }


對於 BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types), 幾個參數相信很容易理解。 cls是要操作的類,name方法的名字,imp實現函數的指針,types是方法的類型。像文中的 "v@:@" 它是方法的類型,是一種縮寫,有利於編譯時提升效率。 我們還可以通過

  • method_copyReturnType
  • method_copyArgumentType

這兩個runtime API獲取對應的方法type。 當然去蘋果官網了解下相信會更明白。

這裏就不詳細介紹。

五、categroy關聯屬性。

剛開始接觸的OC的時候,大部分都會有這麽一個認知,category是不能添加屬性的,只能添加成員變量,並在私有中使用。但是可以使用runtime來添加屬性,使得屬性可在 public中使用,這種操作也即是關聯屬性。

static const NSString* addProp = @"addName";

@implementation NSObject (ClassInfo)


/**
 設置get方法
 
 @return  value
 */
- (NSString *)name{
    
    return  objc_getAssociatedObject(self, [addProp UTF8String]);
}

/**
 設置set方法
 
 @param name newVlaue
 */
- (void)setName:(NSString *)name{
    objc_setAssociatedObject(self, [addProp UTF8String], name, OBJC_ASSOCIATION_COPY_NONATOMIC);
   
}

之後就可以直接調用這個變量了。這種方法用的非常多。不僅可以使得category增加屬性,還特別的簡潔明了。

   [self setName:@"test000000"];
    NSLog(@"%@",self.name);

六、消息發送/轉發是如何實現。

我們知道。OC中所有的調用其實就是消息的傳遞。在使用OC方法的時候,實際上在runtime中是將放啊放轉化成了C語言的 API :

id objc_msgSend(id self, SEL op, ...) //這裏包含消息的發送者,方法名,方法的類型。舉個簡單的例子: 如果我們要執行一個方法:

[self setName:@"小明"]; ----> objc_msgSend(self,method_getName(method),method_getTypeEncoding(method))

除了 objc_msgSend 還有如下幾個發送消息的API

  • objc_msgSend(self,sel); // 發送著為本類的實例對象 如果返回的是常用的類型值的時候,調用
  • objc_msgSendSuper(); // 發送著是 超類的實例對象的時候 返回常用類型 調用
  • objc_msgSend_stret(); // 發送者是 本類的實例對象, 返回一個結構體 調用
  • objc_msgSendSuper_stret(); // 發送者是 超類的實例對象, 返回一個結構體 調用
  • objc_msgSend_fpret(); // 本類的實例對象, 返回一個浮點類型 調用

這些方法調用的流程是什麽呢? 通過一副圖片了解下。當一個msgSend執行的時候,經過以下幾個步驟:

技術分享

1.檢測消息類型是否被忽略,mac上的retain等操作是被忽略的。

2.檢測發送者是不是空指針,如果是,直接retrun ,這裏不會產生Crash。

3.在mothod cache 中尋找對應的IMP,有則執行。沒有進行下一步。

4.在mothod list 中尋找IMP ,有則執行,沒有則下一步。 執行之後會將IMP放入cache,以便下次訪問提升效率。

5.在父類中繼續尋找。有則執行,沒有則進行下一步/ 執行之後會將IMP放入cache,以便下次訪問提升效率。

6.進入消息轉發,或者crash 拋出異常。

在這裏詳細講一下消息的轉發。一張示意圖。

技術分享

在上面的那副圖中,消息轉發的類型有兩大類,一類是 對象方法,也就是我們說的 - 方法。另一類是 類方法,即 + 方法。-方法 有三次機會可以進行消息的轉發,但是對於 + 方法,只有一次。

我們先看看 +方法。 如果想要轉發+方法,只需要重寫 + (BOOL)resolveClassMethod:(SEL)sel 即可。如下

// runtime中的消息
- (void)testTwo{
    //我們隨便發送一個沒有定義過的方法
    [[self class] performSelector:@selector(classMethodTest)];
}

+ (BOOL)resolveClassMethod:(SEL)sel{
//針對類方法
    //第一種 使用自定義方法制作IMP 進行轉發
    class_addMethod(objc_getMetaClass([NSStringFromClass([self class]) UTF8String]), NSSelectorFromString(@"classMethodTest"), (IMP)classForwardFunc,"V@:" );
      return [super resolveClassMethod:sel];
    
    //第二種 使用制作block的方法得到IMP  進行轉發
    methodBlock ablock = ^{
        NSLog(@"使用 block的 IMP 接到消息的轉發");
    };
    IMP amethod = imp_implementationWithBlock(ablock);
    class_addMethod(objc_getMetaClass([NSStringFromClass([self class]) UTF8String]), NSSelectorFromString(@"classMethodTest"), amethod,"V@:" );
    return [super resolveClassMethod:sel];
}

void classForwardFunc(id self1, SEL _cmd1) {
    NSLog(@"類消息轉發成功");
}

classMethodTest在self 中是沒有定義的,如果我們強行調用,會提示警報,並且運行會crash. 如果重寫 resolveClassMethod ,會先進入這個方法中,我們在這裏進行 IMP的添加替換,註意這裏操作的對象是self的元類, 因為在OC的內存布局中,元類中存放靜態方法。如果這裏不進行轉發,接下來程序將回崩潰。

對於- 方法有所不同,它有三次機會進行消息的轉發。第一種有點類似的,- 方法也有一個方法用於替換IMP的。

// runtime中的消息
- (void)testTwo{
    //我們隨便發送一個沒有定義過的方法
    [self performSelector:@selector(instanceMethodTest)];
    // [[self class] performSelector:@selector(classMethodTest)];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
//針對實例方法
    
//  1.
    class_addMethod([self class], NSSelectorFromString(@"instanceMethodTest"), (IMP)instanceForwardFunc,"V@:" );
    return [super resolveInstanceMethod:sel];
    
//  2.
    //同樣可以使用block方法得到IMP 進行轉發
    methodBlock ablock = ^{
        NSLog(@"使用 block的 IMP 接到消息的轉發");
    };
    IMP amethod = imp_implementationWithBlock(ablock);
    class_addMethod(objc_getMetaClass([NSStringFromClass([self class]) UTF8String]), NSSelectorFromString(@"instanceMethodTest"), amethod,"V@:" );
    return [super resolveClassMethod:sel];
}

void instanceForwardFunc(id self1, SEL _cmd1) {
    NSLog(@"對象消息轉發成功");
}

跟+方法很類似的。

第二種情況 ,替換消息發送者轉發。 如果self 中沒有對應的方法,除了替換IMP達到轉發的目的,替換self也是可以的。這個動作將在下面的方法中實現。

//  這是消息發送者轉發階段
-(id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"FlyElephant-http://www.cnblogs.com/xiaofeixiang/");
    NSLog(@"forwardingTargetForSelector");
    if (aSelector == @selector(instanceMethodTest)) { //對象方法
        return [[Other alloc] init];
    }return self;
}

@interface Other : NSObject

- (void)instanceMethodTest;
@end

@implementation Other
- (void)instanceMethodTest{
    
    NSLog(@"更換對象轉發  對象 消息成功");
}
@end

如果前面兩種情況我們都沒有使用,蘋果還提供了一種方式用語轉發: 完整轉發! 意思就是將IMP和self都替換掉。 看下面的代碼。

//如果第二種情況還是沒有轉發  第三種情況 整體轉發
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{   // 返回一個簽名
    // 只有包含了selector方法的對象的簽名才是有效的
    //用另一個實現了seletor的對象 創建si。
    Another *another = [[Another alloc] init];

    NSMethodSignature * si = [another methodSignatureForSelector:selector];
    if(si){
        return si;
    }
    return [super methodSignatureForSelector:selector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    

    [anInvocation setSelector:anInvocation.selector];
    //這裏有點小技巧, 這裏的 selector 是可以更改的,只需要確保another的method list包含這個selecor
//    比如:
    [anInvocation setSelector:NSSelectorFromString(@"anotherFunc")];

    [anInvocation invokeWithTarget:[[Another alloc] init]];
    
}
@interface Another : NSObject
- (void)instanceMethodTest;
- (void)anotherFunc;

@end

@implementation Another
- (void)instanceMethodTest{
    NSLog(@"更換對象轉發  對象 消息成功   Another對象");
}
- (void)anotherFunc{
    NSLog(@"消息轉發 同時更改方法名字  Another對象");
}


@end

如果將Another的所有的方法都使用這種方式轉發,包括它的屬性的set和get,那麽就做到類似於繼承的效果。 再者,對於多個類做到同樣的效果, 就有了OC的多繼承實現了。

七、如何替換一個已有的方法的實現。

替換一個已有的方法的實現,使用繼承加上重寫就可以做到,但是我今天來說下使用runtime做到不繼承的情況下,實現方法實現的替換。也就是大名鼎鼎的 method swizzling的做法。

首先我們來看看metodSwizzling的原理是什麽。

在runtime中,method 的結構體大概是這樣的。

typedef struct objc_ method {

SEL method_name; 方法名 SEL

char *method_types; 方法類型, 包括了參數和返回值類型 通過method_getTypeEncoding獲得

IMP method_imp; 方法實現的函數指針 IMP

};

在runtime中有幾個API:

  • IMP method_getImplementation(Method m) //獲取某個方法的函數的實現
  • IMP method_setImplementation(Method m, IMP imp) //設置某個方法的函數的實現
  • void method_exchangeImplementations(Method m1, Method m2) //改變某個方法的函數的實現
  • Method class_getInstanceMethod(Class cls, SEL name) //通過方法名獲取 method
// runtime的方法交換
- (void)testThree{
    
    Method methodA = class_getInstanceMethod([self class], NSSelectorFromString(@"funcA"));
    Method methodB = class_getInstanceMethod([self class], NSSelectorFromString(@"funcB"));
    
    IMP impA = method_getImplementation(methodA);
    IMP impB = method_getImplementation(methodB);
  
    method_setImplementation(methodA, impB);
    method_setImplementation(methodB, impA);
    
    if([self funcA]){
        NSLog(@"執行了 方法A");
    }
    
    if([self funcB]){
        NSLog(@"執行了 方法B");
    }
}

//定義方法A
- (BOOL)funcA{

    NSLog( @"我是方法A");
    return YES;
}

//定義方法B
- (BOOL)funcB{
    
    NSLog( @"我是方法B");
    return YES;
}

打印:

2017-05-12 11:11:35.595 runtimeTest[3072:90675] 我是方法B

2017-05-12 11:11:35.595 runtimeTest[3072:90675] 執行了方法A

2017-05-12 11:11:35.595 runtimeTest[3072:90675] 我是方法A

2017-05-12 11:11:35.595 runtimeTest[3072:90675] 執行了方法B

方法被交換。

如果使用的 method_exchangeImplementations 也是等效的 ,代碼如下:

  Method methodA = class_getInstanceMethod([self class], NSSelectorFromString(@"funcA"));
    Method methodB = class_getInstanceMethod([self class], NSSelectorFromString(@"funcB"));
    
//    IMP impA = method_getImplementation(methodA);
//    IMP impB = method_getImplementation(methodB);

//    method_setImplementation(methodA, impB);
//    method_setImplementation(methodB, impA);

    //使用 method_exchangeImplementations 等效
    method_exchangeImplementations(methodA, methodB);

    
    if([self funcA]){
        NSLog(@"執行了 方法A");
    }
    
    if([self funcB]){
        NSLog(@"執行了 方法B");
    }

仔細想想,這個方式的作用非常有效,我們如果需要替換某個系統的方法的時候,盲目的重寫可能帶來無法預知的後果,並且維護起來也很困難。 使用方法替換可做到一次替換,一直有效,並可在局部進行。 正常情況下,我們會在

+(void)load{

 //執行替換

}

替換方法,原因是,再不主動調用的情況下,load只會執行一次,並且不會收到超類或者類別的影響。 當然為了防止程序員手動調用,執行了過多次數的替換,可以把替換的代碼使用 GCD 的oncetime_t中擴寫。這樣保證了絕對的一次調用。(偶數次的調用會回到沒有替換的狀態)。

以上的代碼在https://github.com/lufubinGit/runtimeTest上

相關鏈接:

http://www.cnblogs.com/ioshe/p/5489086.html

http://www.cocoachina.com/ios/20160121/15076.html

http://blog.sunnyxx.com/2015/09/13/class-ivar-layout/

Objective-C 中的Runtime的詳細使用