runtime的一些小妙用

用法一: Unrecognized Selector類型crash防護(hù)(Unrecognized Selector)

先介紹下class_addMethod這個(gè)方法:
/**
     Class _Nullable cls: 你要添加方法的那個(gè)類;
     SEL _Nonnull name:name都說可以隨便取, 但有些場(chǎng)景隨便取會(huì)有問題(如下例:),一些取添加或替換方法的名稱SEL;
     IMP _Nonnull imp:新方法的 IMP;
     const char * _Nullable types:新方法的返回值及參數(shù)
     */
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) 

開發(fā)中經(jīng)常會(huì)遇到這個(gè)問題------“unrecognized selector sent to instance 0x7faa2a132c0” 而導(dǎo)致cash. 為了防止crash我們可以用class_addMethod給找不到對(duì)應(yīng)方法即將crash的消息添加一個(gè)與之對(duì)應(yīng)的方法來防止其因?yàn)檎也坏较鄳?yīng)方法而crash.

在OC中找不到對(duì)相應(yīng)的實(shí)現(xiàn)方法時(shí), 有補(bǔ)救機(jī)制 即 會(huì)先調(diào)用動(dòng)態(tài)決議方法 該方法解決不了問題 再調(diào)用重定向方法; 若都解決不了再 cash.

動(dòng)態(tài)決議方法:(這是給類利用class_addMethod添加函數(shù)的機(jī)會(huì)...)

  • (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
  • (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

重定向方法:

  • (id)forwardingTargetForSelector:(SEL)aSelector OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
  • (void)forwardInvocation:(NSInvocation *)anInvocation;
補(bǔ)救機(jī)制(攔截調(diào)用)的整個(gè)流程即Objective——C的消息轉(zhuǎn)發(fā)機(jī)制。其具體流程如下圖:
image.png

下面上代碼: 給一個(gè)類的對(duì)象調(diào)用一個(gè)未實(shí)現(xiàn)的方法 然后用runtime 在動(dòng)態(tài)決議方法中為其添加實(shí)現(xiàn), 防止crash

/* Person 類 */
#import "Person.h"
#import <objc/runtime.h>

@implementation Person

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"resolveInstanceMethod: %@", NSStringFromSelector(sel));   //  打印: eat

    Method addMethod = class_getInstanceMethod([self class], @selector(addMethod));
    // 這里 SEL _Nonnull name 必須寫 sel, 否則還是會(huì)cash...
    class_addMethod([self class], sel, method_getImplementation(addMethod), method_getTypeEncoding(addMethod));
    
    return true;
}
/*  
*  這個(gè)方法也可以解決cash問題, 意思是轉(zhuǎn)給新的對(duì)象去執(zhí)行這個(gè)方法...
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"forwardingTargetForSelector: %@",NSStringFromSelector(aSelector));
    
    if (aSelector == @selector(eat)) {
        // 創(chuàng)建新的對(duì)象
        StubProxyObject * stub = [[StubProxyObject alloc] init];
        return stub;
    }
    
    return [super forwardingTargetForSelector:@selector(addMethod)];
}
*/
- (void) addMethod {
    NSLog(@"addMethod");
}
@end
// 調(diào)用 Person未實(shí)現(xiàn)方法eat
Person * person = [[Person alloc] init];
[person performSelector:@selector(eat)];

添加后就不會(huì)cash;

用法二: 替換系統(tǒng)的方法:

代碼示例: 創(chuàng)建 UIViewController 分類, 然后替換 - (void)dismissViewControllerAnimated: (BOOL)flag completion: (void (^ __nullable)(void))completion;

#import "UIViewController+Extention.h"
#import <objc/runtime.h>

@implementation UIViewController (Extention)

+ (void)load {
    Class class = [self class];
    
    // 保證方法替換只執(zhí)行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL originSelector = @selector(dismissViewControllerAnimated:completion:);
        SEL swizzledSelector = @selector(customDismissViewController);
        
        Method oringinMethod = class_getInstanceMethod(class, originSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        
        IMP swizzledIMP = method_getImplementation(swizzledMethod);
        const char * type = method_getTypeEncoding(swizzledMethod);
        
        BOOL didAddMethod = class_addMethod(class, originSelector, swizzledIMP, type);
        
        if (didAddMethod) {
            class_replaceMethod(class, swizzledSelector, method_getImplementation(oringinMethod), method_getTypeEncoding(oringinMethod));
        } else {
            method_exchangeImplementations(oringinMethod, swizzledMethod);
        }
    });
}
- (void) customDismissViewController {
    NSLog(@"customDismissViewController");
    //  這里調(diào)用不會(huì)有 死循環(huán)....
    [self customDismissViewController];
}

還可以寫為:

+ (void)load {
    Class class = [self class];

    SEL originSelector = @selector(dismissViewControllerAnimated:completion:);
    SEL swizzledSelector = @selector(customDismissViewController);

    Method originMethod = class_getInstanceMethod(class, originSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

    // 確保兩個(gè)方法都得獲取到
    if (!originMethod || !swizzledMethod) {
        return;
    }
        // 交換跟上面情況一樣
//    method_exchangeImplementations(originMethod, swizzledMethod);
    
    //  這種只是作替換,既自定義方法里 不能再調(diào)用  [self customDismissViewController];
    class_replaceMethod(class, originSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
//    class_replaceMethod(class, swizzledSelector, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
}
- (void) customDismissViewController {
    NSLog(@"customDismissViewController");
   // 這里不能調(diào), 不然會(huì)產(chǎn)生死循環(huán)...
//    [self customDismissViewController];
}

應(yīng)用場(chǎng)景: 如何通過不去手動(dòng)修改每個(gè)UIImage的imageNamed:方法就可以實(shí)現(xiàn)為該方法中加入版本判斷語(yǔ)句? 可以為 UIImage建一個(gè)分類(UIImage+Category), 替換系統(tǒng)的 imageNamed: 方法; 注意: 替換方法后,最后要調(diào)用一下自己定義替換的方法, 讓其有加載圖片的功能...

用法三: 在不同類之間實(shí)現(xiàn)Method Swizzling

示例: Person類有一個(gè)實(shí)例方法 - (void)run:(CGFloat)speed, 目前需要Hook該方法對(duì)速度大于20才執(zhí)行 run, 利用另一個(gè)類的方法交換來實(shí)現(xiàn):

Person類

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface Person : NSObject

- (void) run: (CGFloat)speed;

@end

#import "Person.h"
#import <objc/runtime.h>

@implementation Person

- (void) run: (CGFloat)speed {
    NSLog(@"person --- %f",speed);
}

@end

StubProxyObject 類

#import "StubProxyObject.h"
#import <UIKit/UIKit.h>
#import <objc/runtime.h>

@implementation StubProxyObject

+(void)load {
    Class originClass = NSClassFromString(@"Person");
    Class swizzledClass = [self class];
    
    SEL originSelector = NSSelectorFromString(@"run:");
    SEL swizzledSelector = @selector(stub_run:);
    
    Method originMethod = class_getInstanceMethod(originClass, originSelector);
    Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector);
    
    // 向Person類中新添加 stub_run: 方法
    BOOL addMethod = class_addMethod(originClass, swizzledSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
   
    if (!addMethod) {
        return;
    }
    
    // 獲取當(dāng)前 Person 中新添加的方法 stub_run:的Method指針
    Method newSwizzledMethod = class_getInstanceMethod(originClass, swizzledSelector);
    if (!newSwizzledMethod) {
        return;
    }
    
    BOOL didAddMethod = class_addMethod(originClass, originSelector, method_getImplementation(newSwizzledMethod), method_getTypeEncoding(newSwizzledMethod));
   
    if (didAddMethod) {
        class_replaceMethod(originClass, swizzledSelector , method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
    } else {
        method_exchangeImplementations(originMethod, newSwizzledMethod);
    }
}

- (void) stub_run: (CGFloat) merter {
    NSLog(@"StubProxyObject --- stub_run");
    if (merter > 20) {
        [self stub_run:merter];
    }
}

@end

然后在其它地方 Person 對(duì)象調(diào)用 run: 方法時(shí):

Person * person = [Person new];
[person run:30];

控制臺(tái)會(huì)打印:
StubProxyObject --- stub_run
person --- 30.000000

用法四: 給分類添加屬性:

我們知道, 分類中是無(wú)法設(shè)置屬性的,如果在分類的聲明中寫@property, 能為其生成get 和 set 方法的聲明,但無(wú)法生成成員變量,就是說雖然點(diǎn)語(yǔ)法能調(diào)用出來,但程序執(zhí)行后會(huì)crash...

示例: 給NSObject添加分類(NSObject+Category)設(shè)置屬性

#import <Foundation/Foundation.h>

@interface NSObject (Extention)

@property (nonatomic, strong) NSString * name;

@end

#import "NSObject+Extention.h"
#import <objc/runtime.h>

static char * const nameKey = "nameKey";

@implementation NSObject (Extention)

- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name {
    return objc_getAssociatedObject(self, nameKey);
}
@end

用法五: 利用runtime進(jìn)行解歸檔操作

首先得知道三個(gè)方法:

  • 獲得某個(gè)類的所有成員變量(outCount 會(huì)返回成員變量的總數(shù))
Ivar *class_copyIvarList(Class cls , unsigned int *outCount)
  • 獲得成員變量的名字
const char *ivar_getName(Ivar v)
  • 獲得成員變量的類型
const char *ivar_getTypeEndcoding(Ivar v)
利用runtime 獲取所有屬性來重寫歸檔解檔方法(對(duì)于類中屬性比較多時(shí), 用runtime來解歸檔比較方便)
// 設(shè)置不需要?dú)w解檔的屬性
- (NSArray *)ignoredNames {
    return @[@"_aaa",@"_bbb",@"_ccc"];
}
// 歸檔調(diào)用方法
- (void)encodeWithCoder:(NSCoder *)aCoder {
     // 獲取所有成員變量
    unsigned int outCount = 0;
    Ivar *ivars = class_copyIvarList([self class], &outCount);
    for (int i = 0; i < outCount; i++) {
        Ivar ivar = ivars[i];
        // 獲得成員變量的名字
        const char * name = ivar_getName(ivar);
        // 將每個(gè)成員變量名轉(zhuǎn)換為NSString對(duì)象類型
        NSString *key = [NSString stringWithUTF8String:name];
        
        // 忽略不需要?dú)w檔的屬性
        if ([[self ignoredNames] containsObject:key]) {
            continue;
        }
        
        // 通過成員變量名,取出成員變量的值
        id value = [self valueForKeyPath:key];
        // 再將值歸檔
        [aCoder encodeObject:value forKey:key];
        // 這兩步就相當(dāng)于 [aCoder encodeObject:@(self.age) forKey:@"_age"];
    }
    free(ivars);
}
// 解檔方法
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super initWithCoder:aDecoder]) {
        // 獲取所有成員變量
        unsigned int outCount = 0;
        Ivar *ivars = class_copyIvarList([self class], &outCount);
        
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            // 獲得成員變量的名字
            const char * name = ivar_getName(ivar);
            // 將每個(gè)成員變量名轉(zhuǎn)換為NSString對(duì)象類型
            NSString *key = [NSString stringWithUTF8String:name];
            
            // 忽略不需要解檔的屬性
            if ([[self ignoredNames] containsObject:key]) {
                continue;
            }
            
            // 根據(jù)變量名解檔取值,無(wú)論是什么類型
            id value = [aDecoder decodeObjectForKey:key];
            // 取出的值再設(shè)置給屬性
            [self setValue:value forKey:key];
            // 這兩步就相當(dāng)于以前的 self.age = [aDecoder decodeObjectForKey:@"_age"];
        }
        free(ivars);
    }
    return self;
}

若項(xiàng)目中解歸檔的類比較多時(shí), 就可以考慮 NSObject 分類來寫上述邏輯了....

NSObject+Extension.h

#import <Foundation/Foundation.h>

@interface NSObject (Extention)

@property (nonatomic, strong) NSString * name;

- (NSArray *)ignoredNames;
- (void)encode:(NSCoder *)aCoder;
- (void)decode:(NSCoder *)aDecoder;

@end

NSObject+Extention.m

#import "NSObject+Extension.h"
#import <objc/runtime.h>

@implementation NSObject (Extension)

// 歸檔調(diào)用方法
- (void)encode:(NSCoder *)aCoder {
    // 一層層父類往上查找,對(duì)父類的屬性執(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)];
            
            // 如果有實(shí)現(xiàn)該方法再去調(diào)用
            if ([self respondsToSelector:@selector(ignoredNames)]) {
                if ([[self ignoredNames] containsObject:key]) continue;
            }
            
            id value = [self valueForKeyPath:key];
            [aCoder encodeObject:value forKey:key];
        }
        free(ivars);
        c = [c superclass];
    }
}

//  解檔方法
- (void)decode:(NSCoder *)aDecoder {
    // 一層層父類往上查找,對(duì)父類的屬性執(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)];
            
            // 如果有實(shí)現(xiàn)該方法再去調(diào)用
            if ([self respondsToSelector:@selector(ignoredNames)]) {
                if ([[self ignoredNames] containsObject:key]) continue;
            }
            
            id value = [aDecoder decodeObjectForKey:key];
            [self setValue:value forKey:key];
        }
        free(ivars);
        c = [c superclass];
    }
    
}
@end

在需要?dú)w解檔的對(duì)象中實(shí)現(xiàn)下面方法即可:

// 設(shè)置需要忽略的屬性
- (NSArray *)ignoredNames {
    return @[@"_aaa"];
}

// 在系統(tǒng)方法內(nèi)來調(diào)用我們的方法
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        [self decode:aDecoder];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [self encode:aCoder];
}

然而多個(gè)類需要解歸檔時(shí), 上面的代碼還是重復(fù)的, 所以我們直接可以定義個(gè)宏, 在需要的類里直接一句宏就搞定了,這也是MJExtention里一句宏搞定解歸檔的實(shí)現(xiàn)原理;

在 NSObject+Extension.h 里, 我們定義一個(gè)宏:

#define YQCodingImplementation \
-(void)encodeWithCoder:(NSCoder *)aCoder\
{\
[self encode:aCoder];\
}\
-(instancetype)initWithCoder:(NSCoder *)aDecoder\
{\
if (self = [super init]) {\
[self decode:aDecoder];\
}return self; \
}

然后在需要的類里:

#import "Person.h"
#import "NSObject+HZCoding.h"

@implementation Person

// 歸檔  解檔 , 一句宏就可以了...
YQCodingImplementation

@end

用法六: 利用runtime 獲取所有屬性來進(jìn)行字典轉(zhuǎn)模型

可以看參考文章, 里面寫的比較詳細(xì)了....
參考文章: http://www.itdecent.cn/p/ab966e8a82e2
http://www.cocoachina.com/ios/20161102/17920.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,030評(píng)論 0 9
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 30,192評(píng)論 8 265
  • 引導(dǎo) 對(duì)于從事 iOS 開發(fā)人員來說,所有的人都會(huì)答出「 Runtime 是運(yùn)行時(shí) 」,什么情況下用 Runtim...
    Winny_園球閱讀 4,306評(píng)論 3 75
  • 對(duì)于從事 iOS 開發(fā)人員來說,所有的人都會(huì)答出【runtime 是運(yùn)行時(shí)】什么情況下用runtime?大部分人能...
    夢(mèng)夜繁星閱讀 3,799評(píng)論 7 64
  • 親愛的妞,今天的你好累好難過的樣子,4:45下課回來就一副疲憊不堪的神情,見到媽媽就說喉嚨不舒服,媽媽看著好心疼。...
    LianaLL閱讀 266評(píng)論 0 0

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