RunTime用法

一阅悍、什么是RunTime

iOS開發(fā)過程中,我們一直在與Runtime打交道渗钉,可什么是Runtime呢郭厌?

對比C語言來說:

  1. 使用C編寫的程序在編譯過程中已經(jīng)決定了應(yīng)用要調(diào)用哪個函數(shù)。
  1. 使用OC代碼編寫程序時你踩,編譯階段诅岩,我們可以定義調(diào)用任意的函數(shù)(即使它不存在),只有在程序運(yùn)行的時候才會去尋找這個函數(shù)存不存在(不存在則報錯)带膜。

RunTime算是OC代碼運(yùn)行的幕后工作者吩谦,我們可以利用這種特性做一些有趣的事!

二膝藕、RunTime的常用用法

  • 為分類添加屬性

  • 方法交換swizzle

  • 實(shí)現(xiàn)歸檔和反歸檔

  • 實(shí)現(xiàn)多播委托

  • 實(shí)現(xiàn)KVO

2.1 為分類添加屬性

我們知道在分類中是不允許額外添加屬性的式廷,但我們可以通過運(yùn)行時機(jī)制,給這個類添加一個關(guān)聯(lián)芭挽。
在分類的.h文件中添加屬性滑废,.m文件重寫settergetter方法掠剑,然后我們就能正常使用對象的這個屬性了:

#import <Foundation/Foundation.h>
@interface NSObject (KVO)
@property (nonatomic , copy)NSString *specPro;


#import "NSObject+KVO.h"
#import <objc/objc-runtime.h>
@implementation NSObject (KVO)
- (void)setSpecPro:(NSString *)specPro{
    objc_setAssociatedObject(self, @"specPro", specPro, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)specPro{
    return objc_getAssociatedObject(self, @"specPro");
}

2.2實(shí)現(xiàn)方法交換

ViewController為例怀跛,當(dāng)我們需要避開ViewController中的某個周期方法時,Runtime就可以幫我們辦到。
首先創(chuàng)建一個UIViewController的分類凑保,封裝一個方法交換的方法:

/**
 *  交換函數(shù)
 *
 *  @param cls       類
 *  @param originSEL 原函數(shù)
 *  @param newSEL    目標(biāo)函數(shù)
 */
void swizzle(Class c,SEL originSEL,SEL newSEL){
    Method origMethod = class_getInstanceMethod(c, originSEL);
    Method newMethod = nil;
    if (!origMethod) {
        origMethod = class_getClassMethod(c, originSEL);
        if (!origMethod) {
            return;
        }
        newMethod = class_getClassMethod(c, newSEL);
        if (!newMethod) {
            return;
        }
    }else{
        newMethod = class_getInstanceMethod(c, newSEL);
        if (!newMethod) {
            return;
        }
    }
    
    //自身已經(jīng)有了就添加不成功饿敲,直接交換即可
    if(class_addMethod(c, originSEL, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))){
        class_replaceMethod(c, newSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    }else{
        method_exchangeImplementations(origMethod, newMethod);
    }

}

當(dāng)我們需要使用自己的customviewWillAppear:替換系統(tǒng)的viewWillAppear:時妻导,在分類中:

//對所有的ViewController
void swizzleAllViewController(){
    swizzle([UIViewController class], @selector(viewWillAppear:), @selector(customviewWillAppear:));
}

- (void)customviewWillAppear:(BOOL)animated{
    NSLog(@"customviewWillAppear");
    [self customviewWillAppear:animated];
}

然后在main.m中調(diào)用swizzleAllViewController(),就可以在整個工程中生效。

2.3實(shí)現(xiàn)歸檔和反歸檔

給NSbject添加一個分類倔韭,并遵守NSCoding協(xié)議,然后實(shí)現(xiàn)歸檔和反歸檔的兩個方法术浪。

//遍歷類中的所有實(shí)例變量,逐個進(jìn)行歸檔和反歸檔
- (void)encodeWithCoder:(NSCoder *)aCoder{
    
    unsigned int ivarCount = 0;
    /*
        class_copyIvarList函數(shù)寿酌,它返回一個指向成員變量信息的數(shù)組胰苏,數(shù)組中每個元素是指向該成員變量信息的objc_ivar結(jié)構(gòu)體的指針。這個數(shù)組不包含在父類中聲明的變量醇疼。outCount指針返回數(shù)組的大小硕并。需要注意的是,我們必須使用free()來釋放這個數(shù)組秧荆。
     */
    Ivar *vars = class_copyIvarList(object_getClass(self), &ivarCount);
    for (int i = 0; i < ivarCount; i++) {
        Ivar var = vars[i];
        NSString *varName = [NSString stringWithUTF8String:ivar_getName(var)];
        //KVC
        id value = [self valueForKey:varName];
        //歸檔
        [aCoder encodeObject:value forKey:varName];
    }
    
    free(vars);
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder{
    self = [self init];
    if (self) {
        //遍歷實(shí)例變量鏈表倔毙,逐個進(jìn)行反歸檔
        unsigned int ivarCount = 0;
        Ivar *vars = class_copyIvarList(object_getClass(self), &ivarCount);
        for (int i = 0; i < ivarCount; i++) {
            Ivar var = vars[i];
            NSString *varName = [NSString stringWithUTF8String:ivar_getName(var)];
            //反歸檔
            id value = [aDecoder decodeObjectForKey:varName];
            //KVC
            [self setValue:value forKey:varName];
        }
        free(vars);
    }
    return self;
}

使用

//RunTime實(shí)現(xiàn)歸檔和反歸檔
    //RunTime實(shí)現(xiàn)歸檔和反歸檔
    Person *person1 = [[Person alloc]init];
    person1.name = @"jack";
    person1.age = 18;
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:person1];
    Person *person2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
    NSLog(@"%@",person2.name);//打印結(jié)果 jack

2.4實(shí)現(xiàn)多播委托

RunTime的這種應(yīng)用我其實(shí)并沒用過,但最近看到了乙濒,在這分享給大家陕赃。

什么是多播委托?

簡單的說是指允許創(chuàng)建方法的調(diào)用列表或者鏈表的能力.當(dāng)多播委托調(diào)用的時候,列表中的方法均自動執(zhí)行.
普通的delegate只能是一對一的回調(diào)颁股,無法做到一對多的回調(diào)么库。而多播委托正式對delegate的一種擴(kuò)展和延伸,多了一個注冊和取消注冊的過程甘有,但是任何需要回調(diào)的對象都必須先注冊诉儒。
最主要的應(yīng)用是作為XMPPframework架構(gòu)的核心之一,且支持多線程梧疲!

多播委托的本質(zhì)允睹?

多播委托的本質(zhì)還是消息轉(zhuǎn)發(fā),如果一個對象收到一條無法處理的消息幌氮,運(yùn)行時系統(tǒng)會在拋出錯誤前缭受,給該對象發(fā)送一條forwardInvocation:消息,該消息的唯一參數(shù)是個NSInvocation類型的對象——該對象封裝了原始的消息和消息的參數(shù)该互。

如何實(shí)現(xiàn)米者?

NSObject添加一個分類,并添加兩個方法:添加代理和移除代理
.m文件重寫消息轉(zhuǎn)發(fā)方法


// 消息轉(zhuǎn)發(fā)
/*
    消息轉(zhuǎn)發(fā)機(jī)制使用從下面這個方法中獲取的信息來創(chuàng)建NSInvocation對象宇智。因此我們必須重寫這個方法蔓搞,為給定的selector提供一個合適的方法簽名。
 */
// 獲取方法標(biāo)識
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSMutableArray *delegateArray = objc_getAssociatedObject(self, (__bridge const void *)(kMultiDelegatekey));
    for (id aDelegate in delegateArray) {
        NSMethodSignature *sig = [aDelegate methodSignatureForSelector:aSelector];
        if (sig) {
            return sig;
        }
    }
    return [[self class] instanceMethodSignatureForSelector:@selector(doNothing)];
}

/*
 運(yùn)行時系統(tǒng)會在這一步給消息接收者最后一次機(jī)會將消息轉(zhuǎn)發(fā)給其它對象随橘。對象會創(chuàng)建一個表示消息的NSInvocation對象喂分,把與尚未處理的消息有關(guān)的全部細(xì)節(jié)都封裝在anInvocation中,包括selector机蔗,目標(biāo)(target)和參數(shù)蒲祈。我們可以在forwardInvocation方法中選擇將消息轉(zhuǎn)發(fā)給其它對象甘萧。
 */
// 消息轉(zhuǎn)發(fā)給其他對象
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSMutableArray *delegateArray = objc_getAssociatedObject(self, (__bridge const void *)(kMultiDelegatekey));
    for (id aDelegate in delegateArray) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // 異步轉(zhuǎn)發(fā)消息
            [anInvocation invokeWithTarget:aDelegate];
        });
    }
    
}
@end

2.5實(shí)現(xiàn)KVO

蘋果是怎樣實(shí)現(xiàn)KVO的呢?

當(dāng)我們設(shè)置觀察一個對象的時候梆掸,會動態(tài)創(chuàng)建出一個繼承自該對象的衍生類扬卷,并重寫了觀察屬性的setter方法,該方法會在觀察屬性更改前后發(fā)出通知酸钦,因?yàn)橄到y(tǒng)會在消息發(fā)送之前更改對象的isa指針怪得,指向衍生類,而被觀察對象也就成為了衍生類的實(shí)例(多態(tài))卑硫。

實(shí)現(xiàn)步驟徒恋?

根據(jù)蘋果內(nèi)部實(shí)現(xiàn)原理,我們可以分以下幾步實(shí)施:
1欢伏、檢查對象的類有沒有相應(yīng)的 setter方法因谎。如果沒有拋出異常;
2颜懊、檢查對象 isa 指向的類是不是一個 KVO 類。如果不是风皿,新建一個繼承原來類的子類河爹,并把 isa 指向這個新建的子類;
3桐款、檢查對象的 KVO 類是否重寫過這個 setter方法咸这。如果沒有,添加重寫的 setter 方法魔眨;
4媳维、添加這個觀察者。

- (void)addObserver:(id)observer forKey:(NSString *)key withBlock:(void (^)(id, NSString *, id, id))block{
    //獲取setterName
    NSString *setName = setterName(key);
    SEL setSelector = NSSelectorFromString(setName);
    //通過SEL獲取方法
    Method setMethod = class_getInstanceMethod(object_getClass(self), setSelector);
    if (!setMethod) {
        @throw [NSException exceptionWithName:@"KVO Error" reason:@"沒有setter方法遏暴,無法KVO" userInfo:nil];
    }
    
    //創(chuàng)建當(dāng)前的類
    //判斷是否已經(jīng)創(chuàng)建了衍生類
    Class thisClass = object_getClass(self);
    NSString *thisClassName = NSStringFromClass(thisClass);
    if (![thisClassName hasPrefix:KVOClassPrefix]) {
        thisClass  = [self makeKVOClassWithOriginalClassName:thisClassName];
        //改變類的標(biāo)示
        object_setClass(self, thisClass);
    }
    
    //判斷衍生類是否實(shí)現(xiàn)了setter方法
    if (![self hasSelector:setSelector]) {
        const char *setType = method_getTypeEncoding(setMethod);
        //自己添加set方法
        class_addMethod(object_getClass(self), setSelector, (IMP)setter, setType);
    }
    
    NSMutableArray *observers = objc_getAssociatedObject(self, &KVOServerAssociatedKey);
    if (!observers) {
        observers = [NSMutableArray new];
        objc_setAssociatedObject(self, &KVOServerAssociatedKey, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    //創(chuàng)建觀察者info類
    KVOObserverInfo *info = [[KVOObserverInfo alloc]initWithObserver:observer forKey:key withBlock:block];
    [observers addObject:info];
}

//重寫setter方法侄刽,新的setter在調(diào)用原來的setter方法后,通知每個觀察者(調(diào)用之前傳入的block)
void setter(id objc_self,SEL cmd_p,id newValue){
    //setterName 轉(zhuǎn)為 name
    NSString *setName = NSStringFromSelector(cmd_p);
    NSString *key = nameWithSetterName(setName);
    //通過kvc獲取key對應(yīng)的value
    id oldValue = [objc_self valueForKey:key];
    //將setter消息轉(zhuǎn)發(fā)給父類
    struct objc_super selfSuper = {
        .receiver = objc_self,
        .super_class = class_getSuperclass(object_getClass(objc_self))
    };
   //新版方法不帶參數(shù)朋凉,這里只要在Buid Settings中搜索msg州丹,將其修改成NO就可以了
    objc_msgSendSuper(&selfSuper,cmd_p,newValue);
    
    //調(diào)用block
    NSMutableArray *observers = objc_getAssociatedObject(objc_self, &KVOServerAssociatedKey);
    for (KVOObserverInfo *info in observers) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            if ([info.key isEqualToString:key]) {
                info.block(objc_self, key, oldValue, newValue);
            }
        });
    }
}

把觀察的數(shù)據(jù)放在一個關(guān)聯(lián)的類中,如下封裝在KVOObserverInfo中:

#import <Foundation/Foundation.h>
typedef void(^ObserverBlock)(id,NSString *,id,id);
@interface KVOObserverInfo : NSObject
// 觀察者屬性
@property (nonatomic, weak) id observer;
// key屬性
@property (nonatomic, copy) NSString *key;
// 回調(diào)block
@property (nonatomic, copy) ObserverBlock block;

- (instancetype)initWithObserver:(id)observer forKey:(NSString *)key withBlock:(ObserverBlock)block;
@end
#import "KVOObserverInfo.h"

@implementation KVOObserverInfo
- (instancetype)initWithObserver:(id)observer forKey:(NSString *)key withBlock:(ObserverBlock)block {
    self = [super init];
    if (self) {
        _observer = observer;
        _key = key;
        _block = block;
    }
    return self;
}

@end

調(diào)用:

//kvo
    Person *person = [[Person alloc]init];
    //給對象添加觀察者
    [person addObserver:self forKey:@"name" withBlock:^(id observerObject, NSString *key, id oldValue, id newValue) {
        NSLog(@"%@",oldValue);
        NSLog(@"%@",newValue);
    }];
    person.name = @"張三";
    person.name = @"李四";

打印數(shù)據(jù):

log.png

具體代碼請參考github地址:https://github.com/cusinkgetntly/RunTimeDemo.git

如果有什么問題請多多指正杂彭,謝謝墓毒!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市亲怠,隨后出現(xiàn)的幾起案子所计,更是在濱河造成了極大的恐慌,老刑警劉巖团秽,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件主胧,死亡現(xiàn)場離奇詭異叭首,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)讥裤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門放棒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人己英,你說我怎么就攤上這事间螟。” “怎么了损肛?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵厢破,是天一觀的道長。 經(jīng)常有香客問我治拿,道長摩泪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任劫谅,我火速辦了婚禮见坑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘捏检。我一直安慰自己荞驴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布贯城。 她就那樣靜靜地躺著熊楼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪能犯。 梳的紋絲不亂的頭發(fā)上鲫骗,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天,我揣著相機(jī)與錄音踩晶,去河邊找鬼执泰。 笑死,一個胖子當(dāng)著我的面吹牛合瓢,可吹牛的內(nèi)容都是我干的坦胶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼晴楔,長吁一口氣:“原來是場噩夢啊……” “哼顿苇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起税弃,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤纪岁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后则果,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體幔翰,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡漩氨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了遗增。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叫惊。...
    茶點(diǎn)故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖做修,靈堂內(nèi)的尸體忽然破棺而出霍狰,到底是詐尸還是另有隱情,我是刑警寧澤饰及,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布蔗坯,位于F島的核電站,受9級特大地震影響燎含,放射性物質(zhì)發(fā)生泄漏宾濒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一屏箍、第九天 我趴在偏房一處隱蔽的房頂上張望绘梦。 院中可真熱鬧,春花似錦赴魁、人聲如沸谚咬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至敲长,卻和暖如春郎嫁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背祈噪。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工泽铛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辑鲤。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓盔腔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親月褥。 傳聞我的和親對象是個殘疾皇子弛随,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評論 2 354

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

  • *面試心聲:其實(shí)這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,140評論 30 470
  • 如果看過我前面幾篇關(guān)于Runtime的文章,應(yīng)該知道Runtime的消息發(fā)送機(jī)制的原理是對象根據(jù)方法編號SEL去映...
    FITZ9311閱讀 433評論 0 3
  • 一宁赤、runtime簡介 RunTime簡稱運(yùn)行時舀透。OC就是運(yùn)行時機(jī)制,也就是在運(yùn)行時候的一些機(jī)制决左,其中最主要的是消...
    只敲代碼不偷桃閱讀 298評論 0 1
  • 1 動態(tài)添加屬性 若想給系統(tǒng)的類添加屬性愕够,可以采用Runtime的方法走贪,比如:給系統(tǒng)的NSObject類添加一個n...
    小碼碼閱讀 378評論 0 4
  • 1 方法調(diào)用機(jī)制 本質(zhì)是讓對象發(fā)送消息.對象方法保存到類中,每個類都有一個方法列表 (1)根據(jù)對象的isa指針找到...
    小碼碼閱讀 109評論 0 1