Runtime基本應(yīng)用

1暖眼、方法交換(Method-Swizzling黑魔法)

  + (void)load
{
    Method test = class_getInstanceMethod(self, @selector(test));

    Method otherTest = class_getInstanceMethod(self, @selector(otherTest));

    method_exchangeImplementations(test, otherTest);
}

應(yīng)用場景:替換系統(tǒng)的方法酣倾,比如viewDidLoad苗桂,viewWillAppear以及一些響應(yīng)方法矗烛,來進(jìn)行統(tǒng)計(jì)信息。
再例如更換全局UILabel默認(rèn)字體倒戏,可以通過Method Swizzling替換UILabel初始方法來修改等

因?yàn)镸ethod Swizzling的實(shí)現(xiàn)模式比較固定怠噪,所以將其抽象為一個(gè)分類,可以直接方便調(diào)用

#import <Foundation/Foundation.h>

@interface NSObject (Swizzling)

+(void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector bySwizzledSelector:(SEL)swizzledSelector;
@end

#import "NSObject+Swizzling.h"
#import <objc/runtime.h>
@implementation NSObject (Swizzling)

+(void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector
                        bySwizzledSelector:(SEL)swizzledSelector{

    Class class = [self class];
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzleMethod = class_getInstanceMethod(class, swizzledSelector);

    BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
    if (didAddMethod) {
        class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    }else{
        method_exchangeImplementations(originalMethod, swizzleMethod);
    }
}
@end
  • class_addMethod:會(huì)覆蓋父類的方法實(shí)現(xiàn)杜跷,但不會(huì)取代本類中已存在的實(shí)現(xiàn)傍念。如果本類中包含一個(gè)同名的實(shí)現(xiàn),則函數(shù)返回NO葛闷。這里為源SEL添加IMP是為了避免源SEL沒有實(shí)現(xiàn)IMP的情況憋槐。若添加成功則說明源SEL沒有實(shí)現(xiàn)IMP,將源SEL的IMP替換到交換SEL的IMP淑趾;若添加失敗則說明源SEL已經(jīng)有IMP了阳仔,直接將兩個(gè)SEL的IMP交換就可以了

  • class_replaceMethod
    該函數(shù)的行為分為兩種:如果類中不存在name指定的方法,則類似于clss_addMethod函數(shù)一樣會(huì)添加方法扣泊;
    如果類中已存在name指定的方法近范,則類似于method_setImplementation一樣代替原方法的實(shí)現(xiàn)嘶摊。

實(shí)現(xiàn)第一個(gè)場景:跟蹤程序每個(gè)ViewController展示給用戶的次數(shù),可以通過Method Swizzling替換ViewDidAppear初始方法评矩。

創(chuàng)建一個(gè)UIViewController的分類叶堆,重寫自定義的ViewDidAppear方法,并在其+load方法中實(shí)現(xiàn)ViewDidAppear方法的交換斥杜。

#import <UIKit/UIKit.h>
@interface UIViewController (Swizzling)
@end

#import "UIViewController+Swizzling.h"
#import "NSObject+Swizzling.h"

@implementation UIViewController (Swizzling)
+(void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [UIViewController methodSwizzlingWithOriginalSelector:@selector(viewDidAppear:)
                                           bySwizzledSelector:@selector(my_ViewDidAppear:)];

    });
    }
-(void) my_ViewDidAppear:(BOOL)animated{
    [self my_ViewDidAppear:animated];
    NSLog(@"===== %@ viewDidAppear=====",[self class]);
}
@end

注意:

  • Swizzling應(yīng)該總在+load中執(zhí)行虱颗。
  • 在OC中,Runtime會(huì)在類初始加載時(shí)調(diào)用+load方法蔗喂,在類第一次被調(diào)用時(shí)實(shí)現(xiàn)+initialize方法忘渔。由于Method Swizzling會(huì)影響到類的全局狀態(tài),所以要盡量避免在并發(fā)處理中出現(xiàn)競爭情況弱恒。+load方法能保證在類的初始化過程中被加載辨萍,并保證這種改變應(yīng)用級(jí)別的行為的一致性棋恼。
  • 要使用dispatch_once執(zhí)行方法交換
    方法交換要求線程安全返弹,而且保證在任何情況下只能交換一次。
//例如:image 的方法交換

#import "UIImage+swizzing.h"

 @implementation UIImage (swizzing)

+ (void)load{

    NSLog(@"imae laod");

    Method oldMethod = class_getClassMethod(self, @selector(imageNamed:));

    Method newMethod = class_getClassMethod(self, @selector(sw_imageWithName:));

  method_exchangeImplementations(oldMethod, newMethod);

}

+ (UIImage *)sw_imageWithName:(NSString *)imageName{

     // 這里調(diào)用sw_imageWithName爪飘,相當(dāng)于調(diào)用imageName

    UIImage *image = [UIImage sw_imageWithName:imageName];

    if (!image) {

        NSLog(@"imgae nil");

    }

    NSLog(@"new method");

    return image;

}

@end

2义起、給分類增加屬性

開發(fā)中常需要在不改變某個(gè)類的前提下為其添加一個(gè)新的屬性,尤其是為系統(tǒng)的類添加新的屬性师崎,這個(gè)時(shí)候就可以利用Runtime的關(guān)聯(lián)對(duì)象(Associated Objects)來為分類添加新的屬性了默终。

關(guān)聯(lián)對(duì)象是Runtime的一個(gè)特性,Runtime中定義了三個(gè)將任何鍵值在Runtime關(guān)聯(lián)到對(duì)象上的函數(shù):

  • objc_setAssociatedObject 設(shè)置關(guān)聯(lián)對(duì)象:
    void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

  • objc_getAssociatedObject 獲取關(guān)聯(lián)對(duì)象
    id objc_getAssociatedObject(id object, const void *key)

  • objc_removeAssociatedObjects移除某個(gè)對(duì)象的所有關(guān)聯(lián)對(duì)象犁罩,此方法不常用齐蔽。

為UIImage類添加一個(gè)downLoadURL的屬性。

#import <UIKit/UIKit.h>

@interface UIImage (downLoadURL)
@property (nonatomic, strong) NSString *downLoadURL;
@end

#import "UIImage+downLoadURL.h"
#import <objc/runtime.h>
@implementation UIImage (downLoadURL)

-(void)setDownLoadURL:(NSString *)downLoadURL{
    objc_setAssociatedObject(self, @selector(downLoadURL), downLoadURL, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)downLoadURL{
    return objc_getAssociatedObject(self, @selector(downLoadURL));
}
@end

有名的第三方庫LTNavigationBar在實(shí)現(xiàn)滑動(dòng)界面時(shí)導(dǎo)航欄顯隱功能時(shí)也使用了Associated Objects床估,為UINavigation類添加了一個(gè)UIView類的屬性overlay含滴,使功能實(shí)現(xiàn)起來更加簡便。大家可以閱讀一下源碼丐巫。

3谈况、實(shí)現(xiàn)字典和模型的自動(dòng)轉(zhuǎn)換

優(yōu)秀的JSON轉(zhuǎn)模型第三方庫JSONModel、YYModel等都利用runtime對(duì)屬性進(jìn)行獲取递胧,賦值等操作

閱讀YYModel的源碼可以看出碑韵,YY大神對(duì)NSObject的內(nèi)容進(jìn)行了又一次封裝,添加了許多描述內(nèi)容缎脾。其中YYClassInfo是對(duì)Class進(jìn)行了再次封裝祝闻,而YYClassIvarInfo、YYClassMethodInfo遗菠、YYClPropertyInfo分別是對(duì)Class的Ivar联喘、Method和property進(jìn)行了封裝和描述屉栓。在提取Class的相關(guān)信息時(shí)都運(yùn)用了Runtime。

源碼中提取Class的Ivar的相關(guān)代碼:
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)模型耸袜,我粗略的寫了一個(gè)極簡陋版的轉(zhuǎn)模型方案供大家理解其思路友多,更優(yōu)秀完美的思路建議大家閱讀優(yōu)秀第三方框架的源碼。

@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);
    //遍歷列表堤框,對(duì)每個(gè)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);
            //對(duì)非NS開頭的class處理為嵌套的model域滥,對(duì)model進(jìn)行遞歸,轉(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

4蜈抓、實(shí)現(xiàn)NSCoding的自動(dòng)歸檔和自動(dòng)解檔

實(shí)現(xiàn)<NSCoding>協(xié)議

//獲取所有屬性

- (NSArray *)propertyOfSelf{

     unsigned int count = 0;

    Ivar *ivarList = class_copyIvarList([self class], &count);

    NSMutableArray *propertyArr = [NSMutableArray array];

    for (int i = 0; i<count; i++) {

        //獲取成員屬性

        Ivar ivar = ivarList[i];

        //屬性名

        NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];

         //去掉屬性名前面的_

        NSString *key = [name substringFromIndex:1];

        [propertyArr addObject:key];

    }

     return [propertyArr copy];

}

//歸檔
- (void)encodeWithCoder:(NSCoder *)aCoder{

    NSArray *propertyNames = [self propertyOfSelf];

    for (NSString *properName in propertyNames) {

        id value = [self valueForKey:properName];//KVC

        [aCoder encodeObject:value forKey:properName];

    }
}

//解檔

- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super init];

    if (self) {

        NSArray *propertyNames = [self propertyOfSelf];

        for (NSString *properName in propertyNames) {

            id value = [coder decodeObjectForKey:properName];

            [self setValue:value forKey:properName];//KVC

        }

   }

    return self;
}

//使用
- (IBAction)arch:(id)sender {

    Student *pp = [Student new];

    pp.name = @"xiaoming";

    pp.age = @"11";

    NSLog(@"Name:%@--%@",pp.name,pp.age);

 NSData *data = [NSKeyedArchiver archivedDataWithRootObject:pp];

    [[NSUserDefaults standardUserDefaults] setObject:data forKey:@"sss"];

}
- (IBAction)unarch:(id)sender {

 NSData *data = [[NSUserDefaults standardUserDefaults]objectForKey:@"sss"];

  Student *stu = [NSKeyedUnarchiver unarchiveObjectWithData:data];

   NSLog(@"Name:%@--%@",stu.name,stu.age);
}

5启绰、消息轉(zhuǎn)發(fā)(熱更新)解決Bug(JSPatch)

JSPatch是個(gè)優(yōu)秀開源項(xiàng)目,在項(xiàng)目里引入極小的引擎文件就可以使用JavaScript調(diào)用任何OC的原生接口沟使,替換任意的OC原生方法委可。
目前主要用于下發(fā)JS腳本替換原生OC代碼,實(shí)時(shí)修復(fù)線上bug腊嗡,更多詳情可以閱讀JSPatch技術(shù)文檔着倾。
JSPatch能做到通過JS調(diào)用和改寫OC方法最根本的原因是OC是動(dòng)態(tài)語言,OC上所有方法的調(diào)用和類的生成都通過OC Runtime在運(yùn)行時(shí)進(jìn)行燕少,我們可以通過類名/方法名反射得到相應(yīng)的類和方法卡者。
所以JSPatch的基本原理是:JS傳遞字符串給OC,OC通過Runtime接口調(diào)用和替換OC方法客们。
在JSPatch實(shí)現(xiàn)方法替換上崇决,通過Selector調(diào)用方法時(shí),會(huì)從methodList鏈表里找到對(duì)應(yīng)的Method進(jìn)行調(diào)用底挫,這個(gè)methodList上的元素是可以動(dòng)態(tài)替換的恒傻,可以把某個(gè)Selector對(duì)應(yīng)的函數(shù)指針I(yè)MP替換成新的,也可以拿到已有的某個(gè)Selector對(duì)應(yīng)的函數(shù)指針I(yè)MP建邓,讓另一個(gè)Selector跟它對(duì)應(yīng)盈厘,Runtime提供了相應(yīng)的方法實(shí)現(xiàn)這些。
現(xiàn)在蘋果審核嚴(yán)格可能使用熱更新后審核被拒

6涝缝、防止按鈕多次點(diǎn)擊runtime

參考別人簡書詳細(xì)實(shí)現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末扑庞,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子拒逮,更是在濱河造成了極大的恐慌罐氨,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滩援,死亡現(xiàn)場離奇詭異栅隐,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門租悄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谨究,“玉大人,你說我怎么就攤上這事泣棋〗赫埽” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵潭辈,是天一觀的道長鸯屿。 經(jīng)常有香客問我,道長把敢,這世上最難降的妖魔是什么寄摆? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮修赞,結(jié)果婚禮上婶恼,老公的妹妹穿的比我還像新娘。我一直安慰自己柏副,他們只是感情好勾邦,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著搓扯,像睡著了一般检痰。 火紅的嫁衣襯著肌膚如雪包归。 梳的紋絲不亂的頭發(fā)上锨推,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音公壤,去河邊找鬼换可。 笑死,一個(gè)胖子當(dāng)著我的面吹牛厦幅,可吹牛的內(nèi)容都是我干的沾鳄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼确憨,長吁一口氣:“原來是場噩夢啊……” “哼译荞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起休弃,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤吞歼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后塔猾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體篙骡,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了糯俗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尿褪。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖得湘,靈堂內(nèi)的尸體忽然破棺而出杖玲,到底是詐尸還是另有隱情,我是刑警寧澤淘正,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布天揖,位于F島的核電站,受9級(jí)特大地震影響跪帝,放射性物質(zhì)發(fā)生泄漏今膊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一伞剑、第九天 我趴在偏房一處隱蔽的房頂上張望斑唬。 院中可真熱鬧,春花似錦黎泣、人聲如沸恕刘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽褐着。三九已至,卻和暖如春托呕,著一層夾襖步出監(jiān)牢的瞬間含蓉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國打工项郊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留馅扣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓着降,卻偏偏與公主長得像差油,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子任洞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359