runtime知識(shí)點(diǎn)及項(xiàng)目中的應(yīng)用

概論:Runtime是Objective-C中底層的一套C語(yǔ)言API,是一個(gè)將C語(yǔ)言轉(zhuǎn)化為面向?qū)ο笳Z(yǔ)言的拓展。OC是一種面向?qū)ο蟮膭?dòng)態(tài)語(yǔ)言,動(dòng)態(tài)語(yǔ)言就是在運(yùn)行時(shí)執(zhí)行靜態(tài)語(yǔ)言的編譯連接的工作。OC編寫(xiě)的程序不能直接編譯為及其讀懂的機(jī)器語(yǔ)言,在程序運(yùn)行時(shí),須通過(guò)Runtime來(lái)轉(zhuǎn)換。

而在某些在特定的場(chǎng)景下,運(yùn)用runtime的知識(shí),往往可以大大減少我們的工作量。并能使代碼更加統(tǒng)一、簡(jiǎn)潔。

本文將以一下幾個(gè)方面逐一講解runtime:

目錄:
一、系統(tǒng)方法中的應(yīng)用
? 1、消息機(jī)制
? 2、KVO的底層實(shí)現(xiàn)
二、runtime常見(jiàn)作用
? 1、動(dòng)態(tài)獲取成員屬性
? 2、動(dòng)態(tài)獲得成員變量
? 3、動(dòng)態(tài)獲得實(shí)例方法
? 4、動(dòng)態(tài)添加方法實(shí)現(xiàn)
? 5、方法交換
三、實(shí)際開(kāi)發(fā)中的應(yīng)用
? 1、給控件添加block事件回調(diào)方法
? 2、自定義類(lèi)型自動(dòng)歸檔、解歸檔
? 3、數(shù)組越界crash處理

首先定義一個(gè)Dog類(lèi),本篇文章的講解將圍繞這個(gè)類(lèi)展開(kāi)。

@interface Dog : NSObject
{
    float _weight;
}
@property(nonatomic,copy) NSString *name;
- (void)run:(NSInteger)runLong street:(NSString *)streetName;
@end

@interface Dog ()
{
    NSInteger  _age;
}
@property(nonatomic,copy) NSString *address;
@end

@implementation Dog
- (void)run:(NSInteger)runLong street:(NSString *)streetName{
     NSLog(@"在%@跑了%li米",streetName,runLong);
}
- (void)_sleep{
     NSLog(@"%s",__func__);
}
@end

一、系統(tǒng)方法運(yùn)用runtime

1、發(fā)送消息

在OC中,調(diào)用一個(gè)對(duì)象的方法,實(shí)際上是給對(duì)象發(fā)了一條消息,在編譯Objective-C函數(shù)調(diào)用的語(yǔ)法時(shí),會(huì)被翻譯成一個(gè)C的函數(shù)調(diào)用:objc_msgSend()
當(dāng)我們dog,run的方法時(shí):
[dog run:100 street:@"華爾街"];
實(shí)際上會(huì)轉(zhuǎn)換為:
objc_msgSend(dog, @selector(run:street:),100,@"華爾街");
注:由于xcode5.0開(kāi)始蘋(píng)果不建議我們使用底層的代碼,所以我們需要在target->build setting->搜索msg->將YES改為NO之后import <objc/message.h>才能使用objc_msgSend方法。

2、KVO的底層實(shí)現(xiàn)

概述:KVO是通過(guò)"isa-swizzling"技術(shù)來(lái)實(shí)現(xiàn)的,當(dāng)一個(gè)對(duì)象注冊(cè)觀察者時(shí),這個(gè)對(duì)象的isa指針被修改指向一個(gè)中間類(lèi)。
當(dāng)觀察A類(lèi)型的對(duì)象時(shí),在運(yùn)行時(shí)會(huì)創(chuàng)建了一個(gè)繼成自A類(lèi)的NSKVONotifying_A類(lèi),且為NSKVONotifying_A重寫(xiě)觀察屬性的setter 方法,setter 方法會(huì)負(fù)責(zé)在調(diào)用原 setter 方法之前和之后,通知所有觀察者屬性值的更改情況。
因?yàn)楸疚闹饕v解runtime的作用以及項(xiàng)目中的應(yīng)用,所以這里就不做過(guò)多的陳述,關(guān)于KVO和isa指針不了解的同學(xué)可以移步我的另一篇Objective-C isa指針及KVO實(shí)現(xiàn)原理進(jìn)行學(xué)習(xí)。

二、runtime常見(jiàn)作用

1、動(dòng)態(tài)獲取成員屬性
通過(guò)class_copyPropertyList獲取屬性列表,此方法會(huì)獲得所有通過(guò)@property展開(kāi)的成員屬性,包括私有屬性。

unsigned int count = 0;
NSMutableArray *nameArray = [NSMutableArray array];
/**
     @param class 你想要檢測(cè)的類(lèi)  Class類(lèi)型
     @param count 數(shù)組的個(gè)數(shù)
     @return 返回c語(yǔ)言數(shù)組  數(shù)組里元素是  objc_property_t 類(lèi)型
     */
objc_property_t *properList =  class_copyPropertyList([Dog class],&count);
for (int i = 0; i<count; i++) {
    const char *name =  property_getName(properList[i]);
    NSString *ocName = [NSString stringWithUTF8String:name];
    [nameArray addObject:ocName];
}
    NSLog(@"屬性名:%@",nameArray);

2、動(dòng)態(tài)獲得成員變量
通過(guò)class_copyIvarList獲取成員變量列表,此方法會(huì)獲得所有通過(guò)@property展開(kāi)的成員屬性以及聲明的成員變量,包括私有成員變量。

 unsigned int count = 0;
    NSMutableArray *nameArray = [NSMutableArray array];
    NSMutableArray *typeArray = [NSMutableArray array];
     /**
     @param class 你想要檢測(cè)的類(lèi)  Class類(lèi)型
     @param count 數(shù)組的個(gè)數(shù)
     @return 返回c語(yǔ)言數(shù)組  數(shù)組里元素是  Ivar 類(lèi)型
     */
    Ivar *vars = class_copyIvarList([Dog class], &count);
    for (int i = 0; i<count; i++) {
        
        const char *name = ivar_getName(vars[i]);
        const char *type = ivar_getTypeEncoding(vars[i]);
        NSString *ocName = [NSString stringWithUTF8String:name];
        NSString *ocType = [NSString stringWithUTF8String:type];
        [nameArray addObject:ocName];
        [typeArray addObject:ocType];
        
        /*
         類(lèi)型說(shuō)明:基本類(lèi)型 q:NSInteger   d:double   f:float  i:int  c:BOOL
         引用類(lèi)型:以字符串顯示  如:NSString:"NSString"  NSArray:"NSArray"
         不同類(lèi)型編碼請(qǐng)參考:
         https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
         */
        
    }
    NSLog(@"變量名:%@-----變量類(lèi)型:%@",nameArray,typeArray);

3、動(dòng)態(tài)獲得實(shí)例方法
通過(guò)class_copyMethodList獲得實(shí)例方法列表,通過(guò)method_copyReturnType獲得返回類(lèi)型,通過(guò)method_copyArgumentType獲得各個(gè)參數(shù)類(lèi)型,代碼如下

 unsigned int count = 0;
    NSMutableArray *methodArray = [NSMutableArray array];
    Method *methodList = class_copyMethodList([Dog class],&count);
    for (int i = 0 ; i<count; i++) {
        NSMutableDictionary *methodDic = [NSMutableDictionary dictionary];
        // 方法名
        SEL method = method_getName(methodList[i]);
        // 返回類(lèi)型
        const char *returnTypeName = method_copyReturnType(methodList[i]);
        // 參數(shù)個(gè)數(shù)  默認(rèn)會(huì)多兩個(gè)  第一個(gè)接受消息的對(duì)象  第二個(gè)selector  原因:objc_msgSend(id, SEL)前兩個(gè)參數(shù)
        int argumentCount = method_getNumberOfArguments(methodList[i]);
        
        methodDic[@"methodName"] = NSStringFromSelector(method);
        methodDic[@"returnType"] = [NSString stringWithUTF8String:returnTypeName];
        for (int j = 0; j<argumentCount; j++) {
            NSString *key = [NSString stringWithFormat:@"param%i",j+1];
            methodDic[key] = [NSString stringWithUTF8String:method_copyArgumentType(methodList[i], j)];
        }
        [methodArray addObject:methodDic];
        
    }
    //  @對(duì)象類(lèi)型  v為void   :為selector
    NSLog(@"%@",methodArray);

4、動(dòng)態(tài)添加方法實(shí)現(xiàn)
通過(guò)class_addMethod動(dòng)態(tài)添加方法:通過(guò)[dog performSelector:@selector(haha)];執(zhí)行haha的方法,因?yàn)镈og類(lèi)中并沒(méi)有haha方法,所以執(zhí)行時(shí)會(huì)crash,我們可以通過(guò)如下代碼,動(dòng)態(tài)添加方法實(shí)現(xiàn):

// class: 給哪個(gè)類(lèi)添加方法
// SEL: 方法選擇器,即調(diào)用時(shí)候的名稱(chēng)(只是一個(gè)編號(hào))
// IMP: 方法的實(shí)現(xiàn)(函數(shù)指針)
// type: 方法類(lèi)型,(返回值+隱式參數(shù)) v:void @對(duì)象->self :表示SEL->_cmd
class_addMethod([Dog class], @selector(haha),(IMP)abc, "v@:");
// 任何方法默認(rèn)都有兩個(gè)隱式參數(shù),self,_cmd
void abc(id self, SEL _cmd) {
    NSLog(@"哈哈大笑");
}

5、方法交換
通過(guò)method_exchangeImplementations實(shí)現(xiàn)方法的交換:在Dog類(lèi)的load方法里添加如下方法,實(shí)現(xiàn)方法的交換。(寫(xiě)在load方法中的原因:當(dāng)類(lèi)被加載到內(nèi)存中是會(huì)調(diào)用,執(zhí)行比較早,并且不會(huì)多次調(diào)用)

+(void)load{
     Method orginMethod = class_getInstanceMethod([Dog class], @selector(run:street:));
     Method myMethod = class_getInstanceMethod([Dog class], @selector(myRun:street:));
    method_exchangeImplementations(orginMethod, myMethod);
}

- (NSString *)myRun:(NSInteger)runLong street:(NSString *)streetName{
    NSLog(@"交換方法了");
    return nil;
}

三、實(shí)際開(kāi)發(fā)中的應(yīng)用

說(shuō)明:runtime在開(kāi)發(fā)中的應(yīng)用有很多,但知識(shí)點(diǎn)都是大致相同,為了控制篇幅這里只給出了某個(gè)知識(shí)點(diǎn)的一種應(yīng)用,其他的應(yīng)用可自己嘗試實(shí)現(xiàn),如果想看其他實(shí)現(xiàn)的代碼可以給我留言。

1、給控件添加block回調(diào)方法
知識(shí)點(diǎn): 動(dòng)態(tài)關(guān)聯(lián)兩個(gè)對(duì)象
需求:給UIButton添加點(diǎn)擊點(diǎn)擊事件回調(diào)的block方法
思路:給UIButton添加一個(gè)擴(kuò)展方法,傳入一個(gè)block,再在button的點(diǎn)擊事件里執(zhí)行block;
還可以做:給類(lèi)的catagory添加屬性
實(shí)現(xiàn)代碼:

@interface UIButton (Blocks)
- (void)handleControlEvent:(UIControlEvents)event withBlock:(void (^)(UIButton *btn))block;
@end
@implementation UIButton (Blocks)
static char overviewKey;
- (void)handleControlEvent:(UIControlEvents)event withBlock:(void (^)(UIButton *btn))block {
// 關(guān)聯(lián)對(duì)象方法
// object:給哪個(gè)對(duì)象添加關(guān)聯(lián)
// key:被關(guān)聯(lián)者的索引key
// value:被關(guān)聯(lián)者
// policy:關(guān)聯(lián)時(shí)采用的協(xié)議,有assign,retain,copy等協(xié)議,一般使用OBJC_ASSOCIATION_RETAIN_NONATOMIC
    objc_setAssociatedObject(self, &overviewKey, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
    [self addTarget:self action:@selector(callActionBlock:) forControlEvents:event];
}
- (void)callActionBlock:(id)sender {
// 通過(guò)key 獲取關(guān)聯(lián)對(duì)象
    void (^block)(UIButton *btn) = objc_getAssociatedObject(self, &overviewKey);
    if (block) {
        block(sender);
    }
}
@end

2、自定義類(lèi)型自動(dòng)歸檔、解歸檔
概述: 歸檔,持久化保存數(shù)據(jù)的一種方法。系統(tǒng)的類(lèi)如果遵循了NSCoding協(xié)議都可以直接進(jìn)行歸檔(如:NSString,NSArray等),但是自定義對(duì)象如果要進(jìn)行歸檔,需要:
1、遵循NSCoding協(xié)議
2、實(shí)現(xiàn)解檔方法:-(id)initWithCoder:(NSCoder *)aDecoder和歸檔方法:-(void)encodeWithCoder:(NSCoder *)aCoder

知識(shí)點(diǎn): 獲取一個(gè)類(lèi)的成員變量
需求:一勞永逸的給自定義類(lèi)型實(shí)現(xiàn)自動(dòng)歸檔
思路:獲取自定義類(lèi)的成員變量,獲取成員變量字符串,再通過(guò)KVC取值,實(shí)現(xiàn)歸/解檔。
還可以做:字典轉(zhuǎn)模型、獲取系統(tǒng)類(lèi)的私有屬性,進(jìn)行操作。如 UITextField占位文字的顏色 [self setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
實(shí)現(xiàn)代碼:

// 歸檔
- (void)encodeWithCoder:(NSCoder *)aCoder{
    // 遍歷,對(duì)父類(lèi)的屬性執(zhí)行歸檔方法
    Class c = self.class;
    while (c &&c != [NSObject class]) {
        unsigned int outCount = 0;
        Ivar *ivars = class_copyIvarList([self class], &outCount);
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            // 通過(guò)KVC取值
            id value = [self valueForKeyPath:key];
            [aCoder encodeObject:value forKey:key];
        }
        free(ivars);
        c = [c superclass];
    }


}

// 解檔
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
    if (self =  [super init]) {
        // 遍歷,對(duì)父類(lèi)的屬性執(zhí)行解檔方法
        Class c = self.class;
        while (c &&c != [NSObject class]) {
            unsigned int outCount = 0;
            Ivar *ivars = class_copyIvarList(c, &outCount);
            for (int i = 0; i < outCount; i++) {
                Ivar ivar = ivars[i];
                NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
                id value = [aDecoder decodeObjectForKey:key];
                [self setValue:value forKey:key];
            }
            free(ivars);
            c = [c superclass];
        }

    }
    return self;
}

3、數(shù)組越界crash處理
知識(shí)點(diǎn):獲取某個(gè)類(lèi)的方法、方法交換
需求:如果數(shù)組越界,避免程序閃退
思路:首先看一下數(shù)組越界后所報(bào)的原因

數(shù)組越界

報(bào)錯(cuò)原因提到了"__NSArrayI"而不是"NSArray"這是因?yàn)?NSArray這個(gè)類(lèi)在設(shè)計(jì)的時(shí)候采用了“抽象工廠”模式,內(nèi)部是個(gè)類(lèi)簇,真正起作用的是內(nèi)部的:"__NSArray0"(空數(shù)組)、"__NSSingleObjectArrayI"(個(gè)數(shù)為1的數(shù)組),"__NSArrayI"(不可變數(shù)組)以及"__NSArrayM"(可變數(shù)組)。所以我們只需要給NSArray添加個(gè)category替換這幾個(gè)類(lèi)的objectAtIndex:方法即可。
還可以做:截獲系統(tǒng)方法,替換成自己的方法。如:字典添加nil的crash,按鈕暴力點(diǎn)擊重復(fù)響應(yīng),字體大小適配等
代碼:

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        @autoreleasepool {
            [self exchangeMethodWithClass:objc_getClass("__NSArray0") orginSelector:@selector(objectAtIndex:) customSelector:@selector(emptyObjectIndex:)];
            [self exchangeMethodWithClass:objc_getClass("__NSSingleObjectArrayI") orginSelector:@selector(objectAtIndex:) customSelector:@selector(singleArrObjectIndex:)];
            [self exchangeMethodWithClass:objc_getClass("__NSArrayI") orginSelector:@selector(objectAtIndex:) customSelector:@selector(arrObjectIndex:)];
            [self exchangeMethodWithClass:objc_getClass("__NSArrayM") orginSelector:@selector(objectAtIndex:) customSelector:@selector(mutableObjectIndex:)];
            [self exchangeMethodWithClass:objc_getClass("__NSArrayM") orginSelector:@selector(insertObject:atIndex:) customSelector:@selector(mutableInsertObject:atIndex:)];
        }
    });
}

+ (void)exchangeMethodWithClass:(Class)class orginSelector:(SEL)orginS customSelector:(SEL)customS{
    Method orginMethod = class_getInstanceMethod(class, orginS);
    Method customMethod = class_getInstanceMethod(class, customS);
    method_exchangeImplementations(orginMethod, customMethod);
}

- (id)emptyObjectIndex:(NSInteger)index{
    return nil;
}

- (id)singleArrObjectIndex:(NSInteger)index{
    @autoreleasepool {
        if (index >= self.count || index < 0) {
            return nil;
        }
        return [self singleArrObjectIndex:index];
    }
}

- (id)arrObjectIndex:(NSInteger)index{
    @autoreleasepool {
        if (index >= self.count || index < 0) {
            return nil;
        }
        return [self arrObjectIndex:index];
    }
}

- (id)mutableObjectIndex:(NSInteger)index{
    @autoreleasepool {
        if (index >= self.count || index < 0) {
            return nil;
        }
        return [self mutableObjectIndex:index];
    }
}

- (void)mutableInsertObject:(id)object atIndex:(NSUInteger)index{
    @autoreleasepool {
        if (object) {
            [self mutableInsertObject:object atIndex:index];
        }
    }
}
- (id)myObjectAtIndex:(NSUInteger)index
{
    @autoreleasepool {
        if (index < self.count) {
            return [self myObjectAtIndex:index];
        } else {
            return nil;
        }
    }
}

到這里本篇文章基本結(jié)束,文中所涉及代碼都在這里
最后:喜歡我文章的可以多多點(diǎn)贊和關(guān)注,您的鼓勵(lì)是我寫(xiě)作的動(dòng)力。O(∩_∩)O~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容