runtime應(yīng)用總結(jié)

runtime是OC中一個很重要的概念,通過這個機制可以幫我們做很多事情。
不給出demo的文章都是耍流氓
demo截圖如下:

首先我們先創(chuàng)建一個Person類。.h和.m文件如下:

#import <Foundation/Foundation.h>

@interface Person : NSObject <NSCoding>

@property (nonatomic, assign) int age; // 屬性變量

- (void)func1;
- (void)func2;

- (void)sayHello1:(NSString *)name;

@end


#import "Person.h"
#import <objc/runtime.h>
@interface Person ()

@property (nonatomic, copy) NSString *name;

@end

@implementation Person {
    NSString *instanceName;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        _name = @"Tom";
        instanceName = @"Jim";
        _age = 12;
    }
    return self;
}

// 需要歸檔哪些屬性! 常規(guī)方法
//- (void)encodeWithCoder:(NSCoder *)aCoder
//{
//    [aCoder encodeObject:_name forKey:@"name"];
//    [aCoder encodeInt:_age forKey:@"age"];
//}
// 解檔
//- (instancetype)initWithCoder:(NSCoder *)aDecoder
//{
//    self = [super init];
//    if (self) {
//        _name = [aDecoder decodeObjectForKey:@"name"];
//        _age = [aDecoder decodeIntForKey:@"age"];
//    }
//    return self;
//}

// 使用runtime來歸檔、解檔
- (void)encodeWithCoder:(NSCoder *)aCoder
{
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([Person class], &count);
    for (int i = 0; i < count; i++) {
        // 拿到每個成員變量
        Ivar ivar = ivars[i];
        // 拿名稱
        const char *name = ivar_getName(ivar);
        NSString *key = [NSString stringWithUTF8String:name];
        
        // 歸檔 -- 利用KVC
        id value = [self valueForKey:key];
        [aCoder encodeObject:value forKey:key];
    }
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if (self) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([Person class], &count);
        for (int i = 0; i < count; i++) {
            // 拿到每一個成員變量
            Ivar ivar = ivars[i];
            // 拿名稱
            const char * name = ivar_getName(ivar);
            NSString *key = [NSString stringWithUTF8String:name];
            
            // 解檔
            id value = [aDecoder decodeObjectForKey:key];
            // 利用KVC設(shè)置值
            [self setValue:value forKey:key];
        }
    }
    return self;
}
- (void)func1
{
    NSLog(@"執(zhí)行了func1方法");
}
- (void)func2
{
    NSLog(@"執(zhí)行了func2方法");
}
// 測試消息轉(zhuǎn)發(fā)
- (void)sayHello1:(NSString *)name
{
    NSLog(@"Hello, I am a person");
}
- (NSString *)description
{
    return [NSString stringWithFormat:@"name:%@ age:%d", self.name, self.age];
}
@end

1.獲取所有變量

Ivar、class_copyIvarList、ivar_getName、ivar_getTypeEncoding
objc_property_t、class_copyPropertyList、property_getName

- (IBAction)getAllVariable:(id)sender
{
    unsigned int count = 0;
    // 獲取類的一個包含所有變量的列表,Ivar是runtime聲明的一個宏,是實例變量的意思
    Ivar *allVariables = class_copyIvarList([Person class], &count);
    
    for (int i  = 0; i < count; i++) {
        // 遍歷每一個變量,包括名稱和類型 (此處沒有星號"*"),
        Ivar ivar = allVariables[i];
        const char *variablename = ivar_getName(ivar); // 獲取成員變量名稱
        const char *variableType = ivar_getTypeEncoding(ivar); // 獲取成員變量類型
        NSLog(@"(Name: %s)----(Type:%s)", variablename, variableType);
    }
    /*
         2017-07-05 13:07:00.735 RuntimeDemo[3353:127039] (Name: instanceName)----(Type:@"NSString")
         2017-07-05 13:07:00.735 RuntimeDemo[3353:127039] (Name: _age)----(Type:i)
         2017-07-05 13:07:00.736 RuntimeDemo[3353:127039] (Name: _name)----(Type:@"NSString")
         Ivar,一個指向objc_ivar結(jié)構(gòu)體指針,包含了變量名、變量類型等信息??梢钥吹剿接袑傩訽name instanceName都能夠訪問到了。在有些項目中,為了對某些私有屬性進行隱藏,某些.h文件中沒有出現(xiàn)相應(yīng)的顯式創(chuàng)建,而是如上面的person類中,在.m中進行私有創(chuàng)建,但是我們可以通過runtime這個有效的方法,訪問到所有包括這些隱藏的私有變量。
    */
    NSLog(@"測試一下class_copyPropertyList的區(qū)別");
    
    objc_property_t *allProperties = class_copyPropertyList([Person class], &count);
    for (int i = 0; i < count; i++) {
        objc_property_t property = allProperties[i];
        const char *char_f = property_getName(property);
        NSString *propertyName = [NSString stringWithUTF8String:char_f];
        NSLog(@"property = %@", propertyName);
    }
    /*
     2017-07-05 11:55:16.961 RuntimeDemo[3187:98751] property = name
     2017-07-05 11:55:16.961 RuntimeDemo[3187:98751] property = age
     
     如果單單需要獲取屬性列表,可以使用函數(shù):class_copyPropertyList(),instanceName作為實例變量是不被獲取的,而class_copyIvarList()函數(shù)則能夠返回實例變量和屬性變量的所有成員變量。
     */
    
    free(allVariables);
    free(allProperties);
    
}

2. 獲取Person所有方法

Method、class_copyMethodList、SEL、method_getName、sel_getName
這里獲取方法列表,所有.m文件顯式實現(xiàn)的方法都會被找到,當然也包括setter+getter方法了。Method是runtime聲明的一個宏,表示對一個方法的描述。method_getName是獲取SEL,即獲取方法選擇器@selector()。還可以通過sel_getName以字符串獲取sel的name,也即@selector()中的方法名稱。

- (IBAction)getAllMethod:(id)sender
{
    unsigned int count;
    // 獲取方法列表,所有在.m文件顯式實現(xiàn)的方法都會被找到,包括setter+getter方法;
    Method *allMethods = class_copyMethodList([Person class], &count);
    for (int i = 0; i < count; i++) {
        // Method,為runtime聲明的一個宏,表示對一個方法的描述
        Method md = allMethods[i];
        // 獲取SEL:SEL類型,即獲取方法選擇器@selector()
        SEL sel = method_getName(md);
        // 得到sel的方法名:以字符串格式獲取sel的name,也即@selector()中的方法名稱
        const char *methodname = sel_getName(sel);
        NSLog(@"(Method:%s)", methodname);
    }
}

/*
 控制臺輸出:
 2017-07-05 13:17:13.380 RuntimeDemo[3392:134673] (Method:age)
 2017-07-05 13:17:13.381 RuntimeDemo[3392:134673] (Method:func1)
 2017-07-05 13:17:13.386 RuntimeDemo[3392:134673] (Method:func2)
 2017-07-05 13:17:13.386 RuntimeDemo[3392:134673] (Method:setAge:)
 2017-07-05 13:17:13.386 RuntimeDemo[3392:134673] (Method:.cxx_destruct)
 2017-07-05 13:17:13.386 RuntimeDemo[3392:134673] (Method:description)
 2017-07-05 13:17:13.386 RuntimeDemo[3392:134673] (Method:name)
 2017-07-05 13:17:13.386 RuntimeDemo[3392:134673] (Method:setName:)
 2017-07-05 13:17:13.387 RuntimeDemo[3392:134673] (Method:init)

 控制臺輸出了包括set和get等方法名。
 分析:Method是一個指向objc_method結(jié)構(gòu)體指針,表示對類中的某個方法的描述。
 在api中的定義typedef struct objc_method *Method;
 而objc_method結(jié)構(gòu)體如下:
 struct objc_method {
 SEL method_name                                          OBJC2_UNAVAILABLE;
 char *method_types                                       OBJC2_UNAVAILABLE;
 IMP method_imp                                           OBJC2_UNAVAILABLE;
 }
 method_name:方法選擇器@selector(),類型為SEL。相同名字的方法下,即使在不同類中定義,它們的方法選擇器也相同。
 method_types:方法類型,是個char指針,存儲著方法的參數(shù)類型和返回值類型。
 method_imp: 指向方法的具體實現(xiàn)的指針,數(shù)據(jù)類型為IMP,本質(zhì)上是一個函數(shù)指針。
 
 SEL:數(shù)據(jù)類型,表示方法選擇器,可以理解為對方法的一種包裝。在每個方法都有一個與之對應(yīng)的SEL類型的數(shù)據(jù),根據(jù)一個SEL數(shù)據(jù)"@selector(方法名)"就可以找到對應(yīng)的方法地址,進而調(diào)用方法。
 因此可以通過:獲取Method結(jié)構(gòu)體->得到SEL選擇器的名稱->得到對應(yīng)的方法名,這樣的方式認識OC中關(guān)于方法的定義。
 */

3.改變person的_name變量屬性

Ivar、class_copyIvarList、object_setIvar

- (IBAction)changeVariable:(id)sender {
    NSLog(@"改變前的person:%@", self.person);
    unsigned int count = 0;
    Ivar *allList = class_copyIvarList([Person class], &count);
    Ivar ivv = allList[2];
    object_setIvar(self.person, ivv, @"Mike"); // name屬性Tom被強制改為Mike。
    NSLog(@"改變之后的person: %@", self.person);
}

4.添加新屬性

objc_setAssociatedObject、objc_getAssociatedObject
id objc_getAssociatedObject(id object, const void *key)
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
這里第一個參數(shù)是需要添加屬性的對象,第二個參數(shù)是屬性的key,必須是C語言字符串,第三個參數(shù)是屬性的值,類型為id,所以此處先將height轉(zhuǎn)為NSNumber類型,在分類中,即使使用@property定義了,也只是生產(chǎn)set+get方法,而不會生成_變量名,分類中是不允許定義變量的。

#import "Person.h"
@interface Person (PersonCategory)
@property (nonatomic, assign) float height; // 新屬性
@end

#import "Person+PersonCategory.h"
#import <objc/runtime.h>

const char * str = "myKey"; // 作為key,字符常量 必須是C語言字符串

@implementation Person (PersonCategory)

- (void)setHeight:(float)height
{
    NSNumber *num = [NSNumber numberWithFloat:height];
    
    /*
     第一個參數(shù)是需要添加屬性的對象;
     第二個參數(shù)是屬性的key;
     第三個參數(shù)是屬性的值,類型必須為id,所以此處height先轉(zhuǎn)為NSNumber類型;
     第四個參數(shù)是使用策略,是一個枚舉值,類似@property屬性創(chuàng)建時設(shè)置的關(guān)鍵字,可從命名看出各枚舉的意義
     OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
     */
    objc_setAssociatedObject(self, str, num, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

// 提取屬性的值
- (float)height
{
    NSNumber *number = objc_getAssociatedObject(self, str);
    return [number floatValue];
}
@end
- (IBAction)addVariable:(id)sender {
    self.person.height = 12;    // 給新屬性height賦值
    NSLog(@"添加的新屬性height = %f", [self.person height]); // 訪問新屬性
    
}

/*
 點擊按鈕四、再點擊按鈕一、二獲取類的屬性、方法
 2017-07-05 14:14:23.648 RuntimeDemo[3640:165606] 12.000000
 2017-07-05 14:14:28.026 RuntimeDemo[3640:165606] (Name: instanceName)----(Type:@"NSString")
 2017-07-05 14:14:28.026 RuntimeDemo[3640:165606] (Name: _age)----(Type:i)
 2017-07-05 14:14:28.026 RuntimeDemo[3640:165606] (Name: _name)----(Type:@"NSString")
 2017-07-05 14:14:28.027 RuntimeDemo[3640:165606] 測試一下class_copyPropertyList的區(qū)別
 2017-07-05 14:14:28.027 RuntimeDemo[3640:165606] property = height
 2017-07-05 14:14:28.027 RuntimeDemo[3640:165606] property = name
 2017-07-05 14:14:28.027 RuntimeDemo[3640:165606] property = age
 2017-07-05 14:14:28.886 RuntimeDemo[3640:165606] (Method:age)
 2017-07-05 14:14:28.886 RuntimeDemo[3640:165606] (Method:func1)
 2017-07-05 14:14:28.886 RuntimeDemo[3640:165606] (Method:func2)
 2017-07-05 14:14:28.887 RuntimeDemo[3640:165606] (Method:setAge:)
 2017-07-05 14:14:28.887 RuntimeDemo[3640:165606] (Method:.cxx_destruct)
 2017-07-05 14:14:28.887 RuntimeDemo[3640:165606] (Method:description)
 2017-07-05 14:14:28.887 RuntimeDemo[3640:165606] (Method:name)
 2017-07-05 14:14:28.887 RuntimeDemo[3640:165606] (Method:setName:)
 2017-07-05 14:14:28.888 RuntimeDemo[3640:165606] (Method:init)
 2017-07-05 14:14:28.888 RuntimeDemo[3640:165606] (Method:height)
 2017-07-05 14:14:28.888 RuntimeDemo[3640:165606] (Method:setHeight:)
 可以看到分類的新屬性可以在person對象中對新屬性height進行訪問賦值。
 獲取person類屬性時,依然沒有height的存在,但是卻有height和setHeight這兩個方法;因為在分類中,即使使用@property定義了,也只是生產(chǎn)set+get方法,而不會生成_變量名,分類中是不允許定義變量的。
 使用runtime中objc_setAssociatedObject()和objc_getAssociatedObject()方法,本質(zhì)上只是為對象person添加了對height的屬性關(guān)聯(lián),但是達到了新屬性的作用;
 使用場景:假設(shè)imageCategory是UIImage類的分類,在實際開發(fā)中,我們使用UIImage下載圖片或者操作過程需要增加一個URL保存一段地址,以備后期使用。這時可以嘗試在分類中動態(tài)添加新屬性MyURL進行存儲。
 */

5.添加新的方法(這種方法等價于對person類添加Category對方法進行擴展)

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
這里第一個參數(shù)表示Class cls類型,第二個參數(shù)表示待調(diào)用的方法名稱,第三個參數(shù)IMP表示一個函數(shù)指針,這里表示指定具體的實現(xiàn)。

- (IBAction)addMethod:(id)sender {
    /* 動態(tài)添加方法:
     第一個參數(shù)表示 Class cls 類型;
     第二個參數(shù)表示待調(diào)用的方法名稱;
     第三個參數(shù) (IMP)myAddingFunction,IMP一個函數(shù)指針,這里表示指定具體實現(xiàn)方法myAddingFunction;
     第四個參數(shù)表示方法的參數(shù),0代表沒有參數(shù);
     
     */
    class_addMethod([Person class], @selector(NewMethod), (IMP)myAddingFunction, 0);
    [self.person performSelector:@selector(NewMethod)];
}

// 具體的實現(xiàn)(方法的內(nèi)部都默認包含兩個參數(shù)Class和SEL方法,被稱為隱式參數(shù)。)
int myAddingFunction(id self, SEL _cmd) {
    NSLog(@"已新增方法:NewMethod");
    return 1;
}

6.交換兩種方法之后(功能對調(diào))

Method、class_getInstanceMethod、method_exchangeImplementation
要使用dispatch_once執(zhí)行方法交換,方法交換要求線程安全,而且保證在任何情況下只能交換一次。
回顧下NSObject類的+load和+initialize這兩個方法吧。在程序啟動時,Runtime會去加載所有的類。在這一時期,如果類或者類的分類實現(xiàn)了+load方法,則會去調(diào)用這個方法。而+initialize方法是在類或子類第一次接收消息之前會被調(diào)用,這包括類的實例對象或者類對象。如果類一直沒有被用到,則這個方法不會被調(diào)用?;谶@兩個方法的特殊性,我們可以將類使用時所需要的一些前置條件在這兩個方法中處理。不過,如果可能,應(yīng)該盡量放在+initialize中。因為+load方法是在程序啟動時調(diào)用,勢必會影響到程序的啟動時間。而+initialize方法可以說是懶加載調(diào)用,只有用到才會去執(zhí)行。

- (IBAction)replaceMethod:(id)sender {
    Method method1 = class_getInstanceMethod([self.person class], @selector(func1));
    Method method2 = class_getInstanceMethod([self.person class], @selector(func2));
    
    // 交換方法
    method_exchangeImplementations(method1, method2);
    [self.person func1];
}
- (void)func1
{
    NSLog(@"執(zhí)行了func1方法");
}
- (void)func2
{
    NSLog(@"執(zhí)行了func2方法");
}
/*
 輸出
 2017-07-06 17:22:39.399 RuntimeDemo[10188:825945] 執(zhí)行了func2方法
 交換方法的使用場景:項目中的某個功能,在項目中需要多次被引用,當項目的需求發(fā)生改變時,要使用另一種功能代替這個功能,且要求不改變舊的項目,也就是不改變原來方法實現(xiàn)的前提下。那么,我們可以在分類中,再寫一個新的方法,符合新的需求的方法,然后交換兩個方法的實現(xiàn)。這樣,在不改變項目的代碼,而只是增加了新的代碼的情況下,就完成了項目的改進,很好地體現(xiàn)了該項目的封裝性與利用率。
 注:交換兩個方法的實現(xiàn)一般寫在類的load方法里面,因為load方法會在程序運行前加載一次。
 
 */

7.獲取協(xié)議列表

Protocol、class_copyProtocolList、protocol_getName

- (IBAction)fetchProtocolList:(id)sender {
    unsigned int count = 0;
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self.person class], &count);
    NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
    for (unsigned i = 0; i < count; i++) {
        Protocol *protocol = protocolList[i];
        const char *protocolName = protocol_getName(protocol);
        [mutableList addObject:[NSString stringWithUTF8String:protocolName]];
    }
    NSLog(@"獲取到的協(xié)議列表為:%@", mutableList);
}
/*
 2017-07-06 17:23:36.327 RuntimeDemo[10188:825945] 獲取到的協(xié)議列表為:(
 NSCoding
 )

 */

8. 序列化相關(guān),歸檔&解檔

這里實際上還是獲取到所有屬性值,然后使用KVC來設(shè)置值。注意是KVC。

// 需要歸檔哪些屬性! 常規(guī)方法
//- (void)encodeWithCoder:(NSCoder *)aCoder
//{
//    [aCoder encodeObject:_name forKey:@"name"];
//    [aCoder encodeInt:_age forKey:@"age"];
//}
// 解檔
//- (instancetype)initWithCoder:(NSCoder *)aDecoder
//{
//    self = [super init];
//    if (self) {
//        _name = [aDecoder decodeObjectForKey:@"name"];
//        _age = [aDecoder decodeIntForKey:@"age"];
//    }
//    return self;
//}

// 使用runtime來歸檔、解檔
- (void)encodeWithCoder:(NSCoder *)aCoder
{
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([Person class], &count);
    for (int i = 0; i < count; i++) {
        // 拿到每個成員變量
        Ivar ivar = ivars[i];
        // 拿名稱
        const char *name = ivar_getName(ivar);
        NSString *key = [NSString stringWithUTF8String:name];
        
        // 歸檔 -- 利用KVC
        id value = [self valueForKey:key];
        [aCoder encodeObject:value forKey:key];
    }
}


- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if (self) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([Person class], &count);
        for (int i = 0; i < count; i++) {
            // 拿到每一個成員變量
            Ivar ivar = ivars[i];
            // 拿名稱
            const char * name = ivar_getName(ivar);
            NSString *key = [NSString stringWithUTF8String:name];
            
            // 解檔
            id value = [aDecoder decodeObjectForKey:key];
            // 利用KVC設(shè)置值
            [self setValue:value forKey:key];
        }
    }
    return self;
}

9. 實現(xiàn)字典和模型的自動轉(zhuǎn)換

優(yōu)秀的JSON轉(zhuǎn)模型第三方庫JSONModel、YYModel等都利用runtime對屬性進行獲取,賦值操作,要比KVC進行模型轉(zhuǎn)換更強大,更有效率。在YYModel的源碼可以看出,YY對NSObject的內(nèi)容進行了又一次封裝,添加了許多描述內(nèi)容。其中YYClassInfo是對Class進行了再次封裝,而YYClassIvarInfo、YYClassMethodInfo、YYCIPropertyInfo分別是對Class的Ivar、Method和property進行了封裝和描述。在提取Class的相關(guān)信息時都運用了Runtime。

unsigned int ivarCount = 0;
    Ivar *ivars = class_copyIvarList(cls, &ivarCount);
    if (ivars) {
        NSMutableDictionary *ivarInfos = [NSMutableDictionary new];
        _ivarInfos = ivarInfos;
        for (unsigned int i = 0; i < ivarCount; i++) {
            YYClassIvarInfo *info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]];
            if (info.name) ivarInfos[info.name] = info;
        }
        free(ivars);
    }

以下是一個簡陋版的字典轉(zhuǎn)模型方案

@interface NSObject (ZCModel)
+(instancetype) setModelWithDict:(NSDictionary*)dict;
@end

@implementation NSObject (ZCModel)
+(instancetype) setModelWithDict:(NSDictionary*)dict{

    Class cls = [self class];
    id Model = [[self alloc]init];

    unsigned int count;
    //提取Class的property列表
    objc_property_t *property_t = class_copyPropertyList(cls, &count);
    //遍歷列表,對每個property分別處理
    for (int i =0; i< count; i++) {
        objc_property_t property = property_t[i];
        NSString *key = [NSString stringWithUTF8String:property_getName(property)];

        id value = dict[key];
        if (!value) continue;
        //提取property的attribute信息
        NSString *attribure = [NSString stringWithUTF8String:property_getAttributes(property)];
        //從attribute信息中提取其class的信息
        if ([attribure hasPrefix:@"T@"]) {
            NSRange range =  [attribure rangeOfString:@"\","];
            NSString *typeString = [attribure substringWithRange:NSMakeRange(3, range.location  - 3)];

            NSLog(@"the property class is %@",typeString);
            //對非NS開頭的class處理為嵌套的model,對model進行遞歸,轉(zhuǎn)為模型
            if (![typeString hasPrefix:@"NS"]) {

                Class modelClass = NSClassFromString(typeString);
                value = [modelClass setModelWithDict:value];
            }
        }
        //將字典中的值設(shè)置給模型
        [Model setValue:value forKeyPath:key];
    }

    free(property_t);
    return Model;
}
@end

10. 實現(xiàn)跳轉(zhuǎn)功能,

objc_getClass、objc_allocateClassPair、objc_registerClassPair

- (IBAction)push:(id)sender {
    NSDictionary *dic = @{@"class" : @"TestViewController",
                          @"property" : @{
                                  @"name" : @"guohongwei",
                                  @"name1" : @"guohongwei",

                                  @"phoneNum" : @"1234567890"
                                  }};
    // leim
    NSString *class = [NSString stringWithFormat:@"%@", dic[@"class"]];
    const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding];
    // 從一個字符串返回一個類
    Class newClass = objc_getClass(className);
    if (!newClass) {
        // 創(chuàng)建一個類
        Class superClass = [NSObject class];
        newClass = objc_allocateClassPair(superClass, className, 0);
        // 注冊你創(chuàng)建的這個類
        objc_registerClassPair(newClass);
    }
    
    // 創(chuàng)建對象
    id instance = [[newClass alloc] init];
    
    // 對該對象賦值屬性
    NSDictionary *propertys = dic[@"property"];
    [propertys enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
         //檢測這個對象是否存在該屬性, 如果沒有檢查的話,會crash,報錯如下:reason: '[<TestViewController 0x7f88e1403f60> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name1.'
        if ([self checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {
            // 利用kvc賦值
            [instance setValue:obj forKey:key];
        }
    }];
    // 跳轉(zhuǎn)到對應(yīng)的控制器
    [self.navigationController pushViewController:instance animated:YES];
    
    
}

//    TestViewController *test = [[TestViewController alloc] init];
//    [self.navigationController pushViewController:test animated:YES];

- (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName
{
    unsigned int outCount, i;
    // 獲取對象里的屬性列表
    objc_property_t *properties = class_copyPropertyList([instance class], &outCount);
    for (i  = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        // 屬性名轉(zhuǎn)為字符串
        NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
        // 判斷該屬性是否存在
        if ([propertyName isEqualToString:verifyPropertyName]) {
            free(properties);
            return YES;
        }
    }
    free(properties);
    return NO;
}

11. 消息轉(zhuǎn)發(fā)(resolveInstanceMethod:)

一個方法的聲明必定會有與之對應(yīng)的實現(xiàn),如果調(diào)用了只有聲明沒有實現(xiàn)的方法會導致程序crash,而實現(xiàn)并非只有中規(guī)中矩的在.m里寫上相同的方法名再在內(nèi)部寫實現(xiàn)代碼。
當調(diào)用[receiver message]時,會觸發(fā)id objc_msgSend(id self, SEL op, ...)這個函數(shù)。
receiver通過isa指針找到當前對象的class,并在class中尋找op,如果找到,調(diào)用op,如果沒找到,到super_class中繼續(xù)尋找,如此循環(huán)直到NSObject(引自引文)。 如果NSObject中仍然沒找到,程序并不會立即crash,而是按照優(yōu)先級執(zhí)行下列三個方法(下列方法優(yōu)先級依次遞減,高優(yōu)先級方法消息轉(zhuǎn)發(fā)成功不會再執(zhí)行低優(yōu)先級方法):

  • 1 + resolveInstanceMethod:(SEL)sel // 對應(yīng)實例方法
    • resolveClassMethod:(SEL)sel // 對應(yīng)類方法
  • 2 - (id)forwardingTargetForSelector:(SEL)aSelector
  • 3 - (void)forwardInvocation:(NSInvocation *)anInvocation
// 注意sayHello方法并沒有實現(xiàn)哦,如果直接調(diào)用的話是會崩潰的
- (IBAction)testResolveInstanceMethod:(id)sender {
    [self sayHello:@"runtime"];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(sayHello:)) {
        class_addMethod([self class], sel, (IMP)say, "v@:@");
    }
    return [super resolveInstanceMethod:sel];
}

void say(id self, SEL _cmd, NSString *name) {
    NSLog(@"Hello %@", name);
}

12. 消息轉(zhuǎn)發(fā)(forwardingTargetForSelector:)

- (IBAction)testForwardingTargetForSelector:(id)sender {
    [self sayHello1:@"runtime"];
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(sayHello1:)) {
        return [Person new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

12. 消息轉(zhuǎn)發(fā)(forwardInvocation:)

- (IBAction)testForwardInvacation:(id)sender {
    [self sayHello1:@"runtime"];

}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
    if (!methodSignature) {
        methodSignature = [Person instanceMethodSignatureForSelector:aSelector];
    }
    return methodSignature;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    Person *person = [Person new];
    if ([person respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:person];
    }
}

13 熱修復

JSPatch是個優(yōu)秀開源項目,只需要在項目里引入極小的引擎文件就可以使用JavaScript調(diào)用任何OC的原生接口,替換任意的OC原生方法。目前主要用于下發(fā)JS腳本替換原生OC代碼,實時修復線上bug,更多詳情可以閱讀JSPatch技術(shù)文檔。JSPatch能做到通過JS調(diào)用和改寫OC方法最根本的原因是OC是動態(tài)語言,OC上所有方法的調(diào)用和類的生成都通過OC Runtime在運行時進行,我們可以通過類名/方法名反射得到相應(yīng)的類和方法。理論上你可以在運行時通過類名/方法名調(diào)用任何OC方法,替換任何類的實現(xiàn)以及新增任意類,所以JSPatch的基本原理是:JS傳遞字符串給OC,OC通過Runtime接口調(diào)用和替換OC方法。這是最基礎(chǔ)的原理。

在JSPatch實現(xiàn)方法替換上,通過Selector調(diào)用方法時,會從methodList鏈表里找到對應(yīng)的Method進行調(diào)用,這個methodList上的元素是可以動態(tài)替換的,可以把某個Selector對應(yīng)的函數(shù)指針I(yè)MP替換成新的,也可以拿到已有的某個Selector對應(yīng)的函數(shù)指針I(yè)MP,讓另一個Selector跟它對應(yīng),Runtime提供了相應(yīng)的方法實現(xiàn)這些。

參考文章

Runtime常用的幾個應(yīng)用場景
iOS開發(fā)-- Runtime的幾個小例子
iOS開發(fā)之Runtime常用示例總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,041評論 0 9
  • 一些公用類: @interface CustomClass : NSObject - (void) fun1; @...
    xh_0129閱讀 658評論 0 0
  • 一、前言 OC是一門動態(tài)語言,它將很多靜態(tài)語言在編譯和鏈接時期做的事放到了運行時來處理。即說明OC需要一個編譯器和...
    雨潤聽潮閱讀 777評論 1 0
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 828評論 0 2
  • 回顧一個人的一生, 如果非得讓我挑出一個對我的一生來說至關(guān)重要的要素,那大概是一種力爭上游的激情吧。 沃爾瑪?shù)墓适?..
    Yling525閱讀 314評論 0 0

友情鏈接更多精彩內(nèi)容