iOS-Runtime

作為一名iOS開發(fā)者,runtime是必須要了解滴!

先來給大家講講什么是runtime
runtime到底是個什么東西呢?
我們都知道Objective-C是 C 語言的擴展,并加入了面向?qū)ο筇匦院偷南鬟f機制。而這個擴展的核心是一個用 C 和 編譯語言 寫的 runtime 庫。
所以簡單說runtime是一個用C和編譯語言寫的一個庫

而且我們都知道 Objective-C 是一門動態(tài)語言,它會將一些工作放在代碼運行時才處理而并非編譯時。也就是說,有很多類和成員變量在我們編譯的時是不知道的,而在運行時,我們所編寫的代碼會轉(zhuǎn)換成完整的確定的代碼運行。這一切都是因為runtime庫的存在

runtime消息傳遞

OC中任何方法的調(diào)用都用到了消息傳遞機制。

[receiver message];
// 底層運行時會被編譯器轉(zhuǎn)化為:
objc_msgSend(receiver, selector)
// 如果其還有參數(shù)比如:
[receiver message:(id)arg...];
// 底層運行時會被編譯器轉(zhuǎn)化為:
objc_msgSend(receiver, selector, arg1, arg2, ...)

如圖我們所看到的編譯器發(fā)送消息的時候會攜帶兩個隱式參數(shù),分別是調(diào)用者本身和方法編號。為什么要攜帶這兩個參數(shù)呢?我們看一下runtime的執(zhí)行流程就知道啦~!
舉例
[people run]

1、通過people的isa指針找到他的class
2、在class的方法列表中找到run方法
3、如果class中沒有找到會到superclass中繼續(xù)找,直到找到為止
4、找到這個函數(shù)并實現(xiàn)

其實我們最關(guān)心的還是在平時的開發(fā)中我們要怎么去使用runtime呢?

1、獲取class中所有屬性、成員變量和方法列表

如獲取UITextField所有屬性列表,因為有些屬性我們在UITextField的類里面是找不到的,我們想要用卻不知道屬性的名稱,那么我們就可以將屬性打印出來,然后根據(jù)需求去使用它們。

Ivar *ivars = class_copyIvarList([UITextField class], &count);
    for (int i = 0; i < count; i++) {
        Ivar ivar = ivars[i];
        NSLog(@"---name=%s",ivar_getName(ivar));
    }
//通過KVC修改 textField的placeholder的顏色
[textField setValue:[UIColor greenColor] forKeyPath:@"_placeholderLabel.textColor"];

2、交換方法(主要會被用來和系統(tǒng)方法進行交換)

用法:
先給要替換的方法的類添加一個Category,然后在Category中的+(void)load方法中實現(xiàn)方法交換。(由于load方法在程序運行時就會被加載到內(nèi)存中)

+ (void)load {
    Method fromMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
    Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(cm_objectAtIndex:));
    method_exchangeImplementations(fromMethod, toMethod);
}

//實現(xiàn)要交換的方法
- (id)cm_objectAtIndex:(NSUInteger)index {
}

OK,知道怎么用了,下面我們來說一說實際的使用場景:
2.1 比如說有一個項目,已經(jīng)開發(fā)了2年,忽然項目負責(zé)人添加一個功能,每次UIImage加載圖片,告訴我是否加載成功。


+ (void)load
{
    Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));
    Method jyc_imageNamedMethod = class_getClassMethod(self, @selector(jyc_imageNamed:));
    method_exchangeImplementations(imageNamedMethod, jyc_imageNamedMethod);
}
// 1.加載圖片
// 2.判斷是否加載成功
+ (UIImage *)jyc_imageNamed:(NSString *)name
{
    // 圖片
   UIImage *image = [UIImage xmg_imageNamed:name];
    
    if (image) {
        NSLog(@"加載成功");
    } else {
        NSLog(@"加載失敗");
    }
    
    return image;
}

2.2
防崩潰處理:數(shù)組越界問題
在這里需要注意的一點是,數(shù)組的classname到底是什么呢?
經(jīng)過研究發(fā)現(xiàn),NSArray只有一個元素時其class為__NSSingleObjectArrayI,當(dāng)一個元素都沒有的時候其class為__NSArray0,其他情況的class才是__NSArrayI。不確定的小伙伴們可以輸出一下,如下:

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

因此判斷數(shù)組是否越界,我們需要交換的方法需要寫多個

+ (void)load {
    Method fromMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
    Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(cm_objectAtIndex:));
    method_exchangeImplementations(fromMethod, toMethod);

    Method fromMethod1 = class_getInstanceMethod(objc_getClass("__NSSingleObjectArrayI"), @selector(objectAtIndex:));
    Method toMethod1 = class_getInstanceMethod(objc_getClass("__NSSingleObjectArrayI"), @selector(cm1_objectAtIndex:));
    method_exchangeImplementations(fromMethod1, toMethod1);

    Method fromMethod2 = class_getInstanceMethod(objc_getClass("__NSArray0"), @selector(objectAtIndex:));
    Method toMethod2 = class_getInstanceMethod(objc_getClass("__NSArray0"), @selector(cm2_objectAtIndex:));
    method_exchangeImplementations(fromMethod2, toMethod2);
}

//實現(xiàn)交換的方法
- (id)cm_objectAtIndex:(NSUInteger)index {
    // 判斷下標(biāo)是否越界,如果越界就進入異常攔截
    if (self.count-1 < index) {
        @try {
            return [self cm_objectAtIndex:index];
        }
        @catch (NSException *exception) {
            // 在崩潰后會打印崩潰信息。如果是線上,可以在這里將崩潰信息發(fā)送到服務(wù)器
            NSLog(@"---------- %s Crash Because Method %s  ----------\n", class_getName(self.class), __func__);
            NSLog(@"%@", [exception callStackSymbols]);
            return nil;
        }
        @finally {}
    } // 如果沒有問題,則正常進行方法調(diào)用
    else {
        return [self cm_objectAtIndex:index];
    }
}

2.3
攔截點擊事件
如,app多個地方需要先登錄才可以,那么我們可以攔截按鈕的點擊事件,沒有登錄的話我們就去登錄。
上代碼:

  static const char *UIControl_isNeedLogin="UIControl_isNeedLogin";

#pragma mark - isNeedLogin
-(void)setIsNeedLogin:(BOOL)isNeedLogin{
    objc_setAssociatedObject(self, UIControl_isNeedLogin, @(isNeedLogin), OBJC_ASSOCIATION_ASSIGN);
}

-(BOOL)isNeedLogin{
    return [objc_getAssociatedObject(self, UIControl_isNeedLogin) boolValue];
}

+(void)load{
    Method method1 = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
    Method method2 = class_getInstanceMethod(self, @selector(jyc_sendAction:to:forEvent:));
    method_exchangeImplementations(method1, method2);
}

-(void)jyc_sendAction:(SEL)action to:(id)target forEvent:(UIEvent*)event{
    if (self.isNeedLogin) {
        NSLog(@"哼!先登錄去");
        return;
    }
    [self jyc_sendAction:action to:target forEvent:event];
}


-------------------------------------
//用到的時候添加一個屬性就可以了
 UIButton* btn = [[UIButton alloc]initWithFrame:CGRectMake(100, 100, 100, 30)];
[btn setTitle:@"點我" forState:UIControlStateNormal];
btn.backgroundColor = UIColor.redColor;
btn.isNeedLogin = YES;
[btn addTarget:self action:@selector(click) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];

當(dāng)然還有一些其他的作用,如按鈕三秒內(nèi)不能重復(fù)點擊、網(wǎng)絡(luò)加載數(shù)據(jù)時按鈕不能點擊等,我們可以根據(jù)項目的具體情況去應(yīng)用。

因此,通過上面的案例,我們得出的結(jié)論是,runtime交換方法的開發(fā)使用場景是當(dāng)系統(tǒng)自帶的方法功能不夠時,給系統(tǒng)自帶的方法擴展一些功能,并且保持原有的功能。

最后編輯于
?著作權(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)容

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