Runtime 常用場景

前言:
本文主要介紹一些常用Runtime API的常用場景厘擂,用以解決初學(xué)者對于Runtime運用上的一些困惑,以便合理的將Runtime運用到項目中
*--- 歡迎指正和補充 ---*

1. 常用場景歸納

    1. 利用關(guān)聯(lián)對象(AssociatedObject)給分類添加屬性
    1. 遍歷類的所有成員變量或?qū)傩裕@取私有成員變量信息圃庭,以修改私有屬性(如:修改textfield的占位文字顏色)
    1. 遍歷類的屬性(字典轉(zhuǎn)模型吏颖、自動歸檔解檔、重寫- description等)
    1. 交換方法實現(xiàn)
    • 攔截系統(tǒng)方法季希,對其進行修改和補充褪那,拓展一些自己的業(yè)務(wù)邏輯(如監(jiān)聽一些事件等)
    • 當(dāng)三方pod框架不滿足使用場景情況下,可以通過交換方法實現(xiàn)來達到不修改原框架的情況下式塌,實現(xiàn)對業(yè)務(wù)場景的支持
    1. 其它不常用使用:
    • App喚醒時控制器的萬能跳轉(zhuǎn)(動態(tài)創(chuàng)建一個控制器然后博敬,傳入預(yù)定好的參數(shù)進行跳轉(zhuǎn))
    • 熱點修復(fù)(先動態(tài)添加一個方法,然后替換有問題方法的實現(xiàn))
    • 利用方法調(diào)用過程中的消息轉(zhuǎn)發(fā)機制峰尝,來優(yōu)化方法找不到的異常問題

2. 應(yīng)用案例

?? 利用關(guān)聯(lián)對象給TLPerson分類添加nameweight屬性

#import <objc/runtime.h>

@interface TLPerson (ExampleCode)

@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) int weight;
@end

@implementation TLPerson (ExampleCode)

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

- (NSString *)name
{
    // 隱式參數(shù)
    // _cmd == @selector(name)
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setWeight:(int)weight
{
    objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (int)weight
{
    // _cmd == @selector(weight)
    return [objc_getAssociatedObject(self, _cmd) intValue];
}

@end

?? 修改UITextView實例對象的私有屬性

  • 通過class_copyIvarList遍歷UITextField的所有成員變量,
// 成員變量的數(shù)量
    unsigned int count;
    Ivar *ivars = class_copyIvarList([UITextField class], &count);
    for (int i = 0; i < count; i++) {
        // 取出i位置的成員變量
        Ivar ivar = ivars[i];
        NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
    }
    free(ivars);
  • 可發(fā)現(xiàn)它有_placeholderLabel_clearButton兩個私有成員
  • 然后可以通過KVC來修改其屬性
 // 修改光標(biāo)顏色(placeholder)
[textField setValue:UIColorFromRGBA(0xcccdcd, 1.f) forKeyPath:@"_placeholderLabel.textColor"]; 

// 修改清除按鈕的圖片
UIButton *clearButton = [textField valueForKey:@"_clearButton"];
[clearButton setImage:[UIImage imageNamed:@"login_icon_clear"] forState:UIControlStateNormal];

?? 字典轉(zhuǎn)模型偏窝、自動歸檔解檔(來自MJExtension節(jié)選片段,僅作參考)

    1. 通過class_copyPropertyList獲取屬性列表
+ (NSMutableArray *)properties
{
    NSMutableArray *cachedProperties = [self propertyDictForKey:&MJCachedPropertiesKey][NSStringFromClass(self)];
    
    if (cachedProperties == nil) {
        MJExtensionSemaphoreCreate
        MJExtensionSemaphoreWait
        
        if (cachedProperties == nil) {
            cachedProperties = [NSMutableArray array];
            
            [self mj_enumerateClasses:^(__unsafe_unretained Class c, BOOL *stop) {
                // 1.獲得所有的成員變量
                unsigned int outCount = 0;
                objc_property_t *properties = class_copyPropertyList(c, &outCount);
                
                // 2.遍歷每一個成員變量
                for (unsigned int i = 0; i<outCount; i++) {
                    MJProperty *property = [MJProperty cachedPropertyWithProperty:properties[i]];
                    // 過濾掉Foundation框架類里面的屬性
                    if ([MJFoundation isClassFromFoundation:property.srcClass]) continue;
                    property.srcClass = c;
                    [property setOriginKey:[self propertyKey:property.name] forClass:self];
                    [property setObjectClassInArray:[self propertyObjectClassInArray:property.name] forClass:self];
                    [cachedProperties addObject:property];
                }
                
                // 3.釋放內(nèi)存
                free(properties);
            }];
            
            [self propertyDictForKey:&MJCachedPropertiesKey][NSStringFromClass(self)] = cachedProperties;
        }
        
        MJExtensionSemaphoreSignal
    }
    
    return cachedProperties;
}
    1. 遍歷properties將字典轉(zhuǎn)為模型
- (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
    // 獲得JSON對象
    keyValues = [keyValues mj_JSONObject];
    
    MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], self, [self class], @"keyValues參數(shù)不是一個字典");
    
    Class clazz = [self class];
    NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames];
    NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];
    
    //通過封裝的方法回調(diào)一個通過運行時編寫的,用于返回屬性列表的方法武学。
    [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
        @try {
            // 0.檢測是否被忽略
            if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
            if ([ignoredPropertyNames containsObject:property.name]) return;
            
            // 1.取出屬性值
            id value;
            NSArray *propertyKeyses = [property propertyKeysForClass:clazz];
            for (NSArray *propertyKeys in propertyKeyses) {
                value = keyValues;
                for (MJPropertyKey *propertyKey in propertyKeys) {
                    value = [propertyKey valueInObject:value];
                }
                if (value) break;
            }
            
            // 值的過濾
            id newValue = [clazz mj_getNewValueFromObject:self oldValue:value property:property];
            if (newValue != value) { // 有過濾后的新值
                [property setValue:newValue forObject:self];
                return;
            }
            
            // 如果沒有值祭往,就直接返回
            if (!value || value == [NSNull null]) return;
            
            // 2.復(fù)雜處理
            MJPropertyType *type = property.type;
            Class propertyClass = type.typeClass;
            Class objectClass = [property objectClassInArrayForClass:[self class]];
            
            // 不可變 -> 可變處理
            if (propertyClass == [NSMutableArray class] && [value isKindOfClass:[NSArray class]]) {
                value = [NSMutableArray arrayWithArray:value];
            } else if (propertyClass == [NSMutableDictionary class] && [value isKindOfClass:[NSDictionary class]]) {
                value = [NSMutableDictionary dictionaryWithDictionary:value];
            } else if (propertyClass == [NSMutableString class] && [value isKindOfClass:[NSString class]]) {
                value = [NSMutableString stringWithString:value];
            } else if (propertyClass == [NSMutableData class] && [value isKindOfClass:[NSData class]]) {
                value = [NSMutableData dataWithData:value];
            }
            
            if (!type.isFromFoundation && propertyClass) { // 模型屬性
                value = [propertyClass mj_objectWithKeyValues:value context:context];
            } else if (objectClass) {
                if (objectClass == [NSURL class] && [value isKindOfClass:[NSArray class]]) {
                    // string array -> url array
                    NSMutableArray *urlArray = [NSMutableArray array];
                    for (NSString *string in value) {
                        if (![string isKindOfClass:[NSString class]]) continue;
                        [urlArray addObject:string.mj_url];
                    }
                    value = urlArray;
                } else { // 字典數(shù)組-->模型數(shù)組
                    value = [objectClass mj_objectArrayWithKeyValuesArray:value context:context];
                }
            } else {
                if (propertyClass == [NSString class]) {
                    if ([value isKindOfClass:[NSNumber class]]) {
                        // NSNumber -> NSString
                        value = [value description];
                    } else if ([value isKindOfClass:[NSURL class]]) {
                        // NSURL -> NSString
                        value = [value absoluteString];
                    }
                } else if ([value isKindOfClass:[NSString class]]) {
                    if (propertyClass == [NSURL class]) {
                        // NSString -> NSURL
                        // 字符串轉(zhuǎn)碼
                        value = [value mj_url];
                    } else if (type.isNumberType) {
                        NSString *oldValue = value;
                        
                        // NSString -> NSNumber
                        if (type.typeClass == [NSDecimalNumber class]) {
                            value = [NSDecimalNumber decimalNumberWithString:oldValue];
                        } else {
                            value = [numberFormatter_ numberFromString:oldValue];
                        }
                        
                        // 如果是BOOL
                        if (type.isBoolType) {
                            // 字符串轉(zhuǎn)BOOL(字符串沒有charValue方法)
                            // 系統(tǒng)會調(diào)用字符串的charValue轉(zhuǎn)為BOOL類型
                            NSString *lower = [oldValue lowercaseString];
                            if ([lower isEqualToString:@"yes"] || [lower isEqualToString:@"true"]) {
                                value = @YES;
                            } else if ([lower isEqualToString:@"no"] || [lower isEqualToString:@"false"]) {
                                value = @NO;
                            }
                        }
                    }
                } else if ([value isKindOfClass:[NSNumber class]] && propertyClass == [NSDecimalNumber class]){
                    // 過濾 NSDecimalNumber類型
                    if (![value isKindOfClass:[NSDecimalNumber class]]) {
                        value = [NSDecimalNumber decimalNumberWithDecimal:[((NSNumber *)value) decimalValue]];
                    }
                }
                
                // value和property類型不匹配
                if (propertyClass && ![value isKindOfClass:propertyClass]) {
                    value = nil;
                }
            }
            
            // 3.賦值
            [property setValue:value forObject:self];
        } @catch (NSException *exception) {
            MJExtensionBuildError([self class], exception.reason);
            MJExtensionLog(@"%@", exception);
        }
    }];
    
    // 轉(zhuǎn)換完畢
    if ([self respondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject)]) {
        [self mj_keyValuesDidFinishConvertingToObject];
    }
    if ([self respondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject:)]) {
        [self mj_keyValuesDidFinishConvertingToObject:keyValues];
    }
    return self;
}
    1. 歸檔與解檔
- (void)mj_encode:(NSCoder *)encoder
{
    Class clazz = [self class];
    
    NSArray *allowedCodingPropertyNames = [clazz mj_totalAllowedCodingPropertyNames];
    NSArray *ignoredCodingPropertyNames = [clazz mj_totalIgnoredCodingPropertyNames];
    // 遍歷歸檔
    [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
        // 檢測是否被忽略
        if (allowedCodingPropertyNames.count && ![allowedCodingPropertyNames containsObject:property.name]) return;
        if ([ignoredCodingPropertyNames containsObject:property.name]) return;
        
        id value = [property valueForObject:self];
        if (value == nil) return;
        [encoder encodeObject:value forKey:property.name];
    }];
}

- (void)mj_decode:(NSCoder *)decoder
{
    Class clazz = [self class];
    
    NSArray *allowedCodingPropertyNames = [clazz mj_totalAllowedCodingPropertyNames];
    NSArray *ignoredCodingPropertyNames = [clazz mj_totalIgnoredCodingPropertyNames];
     // 遍歷解檔
    [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
        // 檢測是否被忽略
        if (allowedCodingPropertyNames.count && ![allowedCodingPropertyNames containsObject:property.name]) return;
        if ([ignoredCodingPropertyNames containsObject:property.name]) return;
        
        id value = [decoder decodeObjectForKey:property.name];
        if (value == nil) { // 兼容以前的MJExtension版本
            value = [decoder decodeObjectForKey:[@"_" stringByAppendingString:property.name]];
        }
        if (value == nil) return;
        [property setValue:value forObject:self];
    }];
}

?? 同交換方法實現(xiàn)
eg1. 給UIViewController分類中使用系統(tǒng)原生API,如:- dealloc方法
eg2. 解決給數(shù)組添加nil時奔潰問題;

// eg1. 給UIViewController的的分類中使用`- dealloc`方法
@implementation UIViewController (TLTransition)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class cls = [UIViewController class];
         //
        // 注意直接使用:@selector(dealloc) 會報錯:ARC forbids use of 'dealloc' in a @selector
        // Method method = class_getInstanceMethod(cls, @selector(dealloc));
        // 使用字符串獲取NSSelectorFromString(@"dealloc")
        Method method1 = class_getInstanceMethod(cls, NSSelectorFromString(@"dealloc"));
        Method method2 = class_getInstanceMethod(cls, @selector(tl_dealloc));
        method_exchangeImplementations(method1, method2);
    });
}

- (void)tl_dealloc{
   // do something
    tl_Log(@"%@ %s", [self class], __func__);
    [TLTransitionDelegate removeAnimatorForKey:self];
    
    // 回到原來的dealloc方法 
    [self tl_dealloc];
}

@end
// eg2. 解決給數(shù)組添加nil時奔潰問題;
@implementation NSMutableArray (Extension)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 類簇:NSString火窒、NSArray硼补、NSDictionary,真實類型是其他類型(如:NSMutableArray 真實類型是 __NSArrayM)
        Class cls = NSClassFromString(@"__NSArrayM");
        Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
        Method method2 = class_getInstanceMethod(cls, @selector(swizzling__insertObject:atIndex:));
        method_exchangeImplementations(method1, method2);
    });
}

- (void)swizzling__insertObject:(id)anObject atIndex:(NSUInteger)index
{
    if (anObject == nil) return;
    
    [self swizzling__insertObject:anObject atIndex:index];
}
@end
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末熏矿,一起剝皮案震驚了整個濱河市已骇,隨后出現(xiàn)的幾起案子离钝,更是在濱河造成了極大的恐慌,老刑警劉巖褪储,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卵渴,死亡現(xiàn)場離奇詭異,居然都是意外死亡鲤竹,警方通過查閱死者的電腦和手機奖恰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宛裕,“玉大人瑟啃,你說我怎么就攤上這事】” “怎么了蛹屿?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長岩榆。 經(jīng)常有香客問我错负,道長,這世上最難降的妖魔是什么勇边? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任犹撒,我火速辦了婚禮,結(jié)果婚禮上粒褒,老公的妹妹穿的比我還像新娘识颊。我一直安慰自己,他們只是感情好奕坟,可當(dāng)我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布祥款。 她就那樣靜靜地躺著,像睡著了一般月杉。 火紅的嫁衣襯著肌膚如雪刃跛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天苛萎,我揣著相機與錄音桨昙,去河邊找鬼。 笑死腌歉,一個胖子當(dāng)著我的面吹牛蛙酪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播究履,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼滤否,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了最仑?” 一聲冷哼從身側(cè)響起藐俺,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤炊甲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后欲芹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體卿啡,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年菱父,在試婚紗的時候發(fā)現(xiàn)自己被綠了颈娜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡浙宜,死狀恐怖官辽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情粟瞬,我是刑警寧澤同仆,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站裙品,受9級特大地震影響俗批,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜市怎,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一岁忘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧区匠,春花似錦干像、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至揩懒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間挽封,已是汗流浹背已球。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辅愿,地道東北人智亮。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像点待,于是被迫代替她去往敵國和親阔蛉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,092評論 2 355

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