RunTime用法

一、什么是RunTime

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

對比C語言來說:

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

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

二、RunTime的常用用法

  • 為分類添加屬性

  • 方法交換swizzle

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

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

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

2.1 為分類添加屬性

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

#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中的某個(gè)周期方法時(shí),Runtime就可以幫我們辦到。
首先創(chuàng)建一個(gè)UIViewController的分類,封裝一個(gè)方法交換的方法:

/**
 *  交換函數(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:時(shí),在分類中:

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

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

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

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

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

//遍歷類中的所有實(shí)例變量,逐個(gè)進(jìn)行歸檔和反歸檔
- (void)encodeWithCoder:(NSCoder *)aCoder{
    
    unsigned int ivarCount = 0;
    /*
        class_copyIvarList函數(shù),它返回一個(gè)指向成員變量信息的數(shù)組,數(shù)組中每個(gè)元素是指向該成員變量信息的objc_ivar結(jié)構(gòu)體的指針。這個(gè)數(shù)組不包含在父類中聲明的變量。outCount指針返回?cái)?shù)組的大小。需要注意的是,我們必須使用free()來釋放這個(gè)數(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í)例變量鏈表,逐個(gè)進(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)用的時(shí)候,列表中的方法均自動執(zhí)行.
普通的delegate只能是一對一的回調(diào),無法做到一對多的回調(diào)。而多播委托正式對delegate的一種擴(kuò)展和延伸,多了一個(gè)注冊和取消注冊的過程,但是任何需要回調(diào)的對象都必須先注冊。
最主要的應(yīng)用是作為XMPPframework架構(gòu)的核心之一,且支持多線程!

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

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

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

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


// 消息轉(zhuǎn)發(fā)
/*
    消息轉(zhuǎn)發(fā)機(jī)制使用從下面這個(gè)方法中獲取的信息來創(chuàng)建NSInvocation對象。因此我們必須重寫這個(gè)方法,為給定的selector提供一個(gè)合適的方法簽名。
 */
// 獲取方法標(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)行時(shí)系統(tǒng)會在這一步給消息接收者最后一次機(jī)會將消息轉(zhuǎn)發(fā)給其它對象。對象會創(chuàng)建一個(gè)表示消息的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è)置觀察一個(gè)對象的時(shí)候,會動態(tài)創(chuàng)建出一個(gè)繼承自該對象的衍生類,并重寫了觀察屬性的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 指向的類是不是一個(gè) KVO 類。如果不是,新建一個(gè)繼承原來類的子類,并把 isa 指向這個(gè)新建的子類;
3、檢查對象的 KVO 類是否重寫過這個(gè) setter方法。如果沒有,添加重寫的 setter 方法;
4、添加這個(gè)觀察者。

- (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方法后,通知每個(gè)觀察者(調(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ù)放在一個(gè)關(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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

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