iOS runtime 應(yīng)用場景總結(jié)

iOS runtime 應(yīng)用場景總結(jié)

場景1. 動態(tài)分類關(guān)聯(lián)屬性
場景2. hook/Method Swizzling
場景3. 遍歷類屬性方法叭喜,映射解析以及字典與模型的轉(zhuǎn)換, 例如YYModel
場景4. 修改isa指針(研究中)
場景5. 實(shí)現(xiàn)消息轉(zhuǎn)發(fā)機(jī)制的補(bǔ)救


sr0kew2ahk.jpg

一般選擇第二步 forwardingTargetForSelector 來做崩潰防護(hù),原因如下:

resolveInstanceMethod 需要在類的本身上動態(tài)添加它本身不存在的方法壤追;
forwardInvocation可以通過NSInvocation的形式將消息轉(zhuǎn)發(fā)給多個(gè)對象,但是其開銷較大跨琳,需要創(chuàng)建新的NSInvocation對象泽论,并且forwardInvocation的函數(shù)經(jīng)常被使用者調(diào)用夺蛇,來做多層消息轉(zhuǎn)發(fā)選擇機(jī)制,不適合多次重寫副渴;
forwardingTargetForSelector可以將消息轉(zhuǎn)發(fā)給一個(gè)對象奈附,開銷較小,可以NSObject的該方法重寫煮剧,做以下幾步的處理:
1). 動態(tài)創(chuàng)建一個(gè)樁類
2). 動態(tài)為樁類添加對應(yīng)的Selector斥滤,用一個(gè)通用的返回0的函數(shù)來實(shí)現(xiàn)該SEL的IMP
3). 將消息直接轉(zhuǎn)發(fā)到這個(gè)樁類對象上。

場景6. 實(shí)現(xiàn) NSCoding 的自動歸檔和解檔(暫不支持嵌套勉盅,可用于詳情模型的本地存儲)

- (void)encodeWithCoder:(NSCoder *)aCoder {
    // 一個(gè)臨時(shí)數(shù)據(jù), 用來記錄一個(gè)類成員變量的個(gè)數(shù)
    unsigned int ivarCount = 0;
    // 獲取一個(gè)類所有的成員變量
    Ivar *ivars = class_copyIvarList(self.class, &ivarCount);
    
    // 變量成員變量列表
    for (int i = 0; i < ivarCount; i ++) {
        // 獲取單個(gè)成員變量
        Ivar ivar = ivars[i];
        // 獲取成員變量的名字并將其轉(zhuǎn)換為 OC 字符串
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        // 獲取該成員變量對應(yīng)的值
        id value = [self valueForKey:ivarName];
        // 歸檔, 就是把對象 key-value 對 encode
        [aCoder encodeObject:value forKey:ivarName];
    }
    // 釋放 ivars
    free(ivars);
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    // 因?yàn)闆]有 superClass 了
    self = [self init];
    if (self != nil) {
        unsigned int ivarCount = 0;
        Ivar *ivars = class_copyIvarList(self.class, &ivarCount);
        for (int i = 0; i < ivarCount; i ++) {
            
            Ivar ivar = ivars[i];
            NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
            // 解檔, 就是把 key-value 對 decode
            id value = [aDecoder decodeObjectForKey:ivarName];
            // 賦值
            [self setValue:value forKey:ivarName];
        }
        free(ivars);
    }
    return self;
}

場景7.分類重寫 kvc 方法佑颇,防崩潰

-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
    NSLog(@"setValue: forUndefinedKey:, 動態(tài)創(chuàng)建Key: %@",key);
    objc_setAssociatedObject(self, CFBridgingRetain(key), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(nullable id)valueForUndefinedKey:(NSString *)key{
    NSLog(@"valueForUndefinedKey:, 獲取未知鍵 %@ 的值", key);
//    return nil;
    return objc_getAssociatedObject(self, CFBridgingRetain(key));
}

-(void)setNilValueForKey:(NSString *)key{
    NSLog(@"Invoke setNilValueForKey:, 不能給非指針對象(如NSInteger)賦值 nil");
    return;//給一個(gè)非指針對象(如NSInteger)賦值 nil, 直接忽略
}

場景 8:獲取類的成員變量,屬性菇篡,方法漩符,協(xié)議

- (void)enumerateIvars:(void(^)(Ivar v, NSString *name, _Nullable id value))block{
    unsigned int count;
    Ivar *ivars = class_copyIvarList(self.class, &count);

    for(NSInteger i = 0; i < count; i++){
        Ivar ivar = ivars[i];
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        id value = [self valueForKey:ivarName];//kvc讀值
        if (block) {
            block(ivar, ivarName, value);
        }
    }
    free(ivars);
}

- (void)enumeratePropertys:(void(^)(objc_property_t property, NSString *name, _Nullable id value))block{
    unsigned int count = 0;
    objc_property_t *properties = class_copyPropertyList(self.class, &count);
    for (int i = 0; i < count; i++) {
        objc_property_t property_t = properties[i];
        const char *name = property_getName(property_t);
        NSString *propertyName = [NSString stringWithUTF8String:name];
        id value = [self valueForKey:propertyName];
        if (block) {
            block(property_t, propertyName, value);
        }
    }
    free(properties);
}

- (void)enumerateMethods:(void(^)(Method method, NSString *name))block{
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(self.class, &count);
    for (unsigned int i = 0; i < count; i++) {
        Method method = methodList[i];
        SEL mthodName = method_getName(method);
//        NSLog(@"MethodName(%d): %@", i, NSStringFromSelector(mthodName));
        if (block) {
            block(method, NSStringFromSelector(mthodName));
        }
    }
    free(methodList);
}

- (void)enumerateProtocols:(void(^)(Protocol *proto, NSString *name))block{
    unsigned int count = 0;
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList(self.class, &count);
    for (int i = 0; i < count; i++) {
        Protocol *protocal = protocolList[i];
        const char *protocolName = protocol_getName(protocal);
//        NSLog(@"protocol(%d): %@", i, [NSString stringWithUTF8String:protocolName]);
        if (block) {
            block(protocal, [NSString stringWithUTF8String:protocolName]);
        }
    }
    free(protocolList);
}

【附】:賦值和取值(沒用到過,因?yàn)?KVC 更方便)
 //獲取實(shí)例進(jìn)行賦值操作
Ivar ivar = class_getInstanceVariable([UITextField class], "_placeholderLabel");
object_setIvar(self.textField, ivar, "請輸入");
                   
//獲取當(dāng)前類對應(yīng)特征名稱的實(shí)例變量,得到該實(shí)例變量的數(shù)值
Ivar ivar = class_getInstanceVariable([UITextField class], "_placeholderLabel");
id object = object_getIvar(self.textField, ivar);

場景 9. 動態(tài)創(chuàng)建/獲取 class驱还,selector 等以及視圖子元素動態(tài)化重構(gòu)嗜暴,極大地提高代碼復(fù)用率

NSClassFromString(@"MyClass");
NSSelectorFromString(@"showShareActionSheet");

場景 10. 動態(tài)創(chuàng)建Class

objc_allocateClassPair可以動態(tài)創(chuàng)建Class,
objc_registerClassPair進(jìn)行注冊動態(tài)創(chuàng)建的Class
修改對象的Class
object_setClass可以修改對象的Class,即修改了isa指針指向的Class對象

場景 11. 動態(tài)調(diào)整 accessoryView 位置议蟆,使其上下居中(默認(rèn)不居中)

import UIKit

@objc extension UITableViewCell{

    override public class func initializeMethod() {
        super.initializeMethod();
        
        if self != UITableViewCell.self {
            return
        }

        let onceToken = "Hook_\(NSStringFromClass(classForCoder()))";
        DispatchQueue.once(token: onceToken) {
            let oriSel = NSSelectorFromString("layoutSubviews")
            let repSel = #selector(self.hook_layoutSubviews)
            _ = hookInstanceMethod(of: oriSel, with: repSel);
        }
    }
    
    private func hook_layoutSubviews() {
        hook_layoutSubviews()
        
        positionAccessoryView()
    }
}

@objc public extension UITableViewCell{

    ///調(diào)整AccessoryView位置(默認(rèn)垂直居中)
    func positionAccessoryView(_ dx: CGFloat = 0, dy: CGFloat = 0) {
        var accessory: UIView?
        if let accessoryView = self.accessoryView {
            accessory = accessoryView
        } else if self.accessoryType != .none {
            for subview in self.subviews {
                if subview != self.textLabel && subview != self.detailTextLabel
                    && subview != self.backgroundView  && subview != self.selectedBackgroundView
                    && subview != self.imageView && subview != self.contentView
                    && subview.isKind(of: UIButton.self) {
                    accessory = subview
                    break
                }
            }
        }
        
        if accessory != nil {
            accessory!.center = CGPoint(x: accessory!.center.x + dx, y: self.bounds.midY + dy)
        }
    }
}

場景 12. 防止數(shù)組越界和字典賦 nil 造成的崩潰(支持語法糖保護(hù))

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _target = [NNForwardingTarget new];;

        if (isOpenCashProtector) { 
            swizzleInstanceMethod(NSClassFromString(@"__NSArrayI"),
                                  @selector(objectAtIndex:), NSSelectorFromString(@"safe_objectAtIndex:"));
            swizzleInstanceMethod(NSClassFromString(@"__NSArrayI"),
                                  @selector(objectAtIndexedSubscript:), @selector(safe_objectAtIndexedSubscript:));
            
            swizzleInstanceMethod(NSClassFromString(@"__NSArrayM"),
                                  @selector(objectAtIndex:), NSSelectorFromString(@"safe_objectAtIndex:"));
            swizzleInstanceMethod(NSClassFromString(@"__NSArrayM"),
                                  @selector(objectAtIndexedSubscript:), NSSelectorFromString(@"safe_objectAtIndexedSubscript:"));
            swizzleInstanceMethod(NSClassFromString(@"__NSArrayM"),
                                  @selector(addObject:), NSSelectorFromString(@"safe_addObject:"));
            swizzleInstanceMethod(NSClassFromString(@"__NSArrayM"),
                                  @selector(insertObject:atIndex:), NSSelectorFromString(@"safe_insertObject:atIndex:"));
            
            
            //NSClassFromString(@"__NSDictionaryM"),objc_getClass("__NSDictionaryM")
            swizzleInstanceMethod(NSClassFromString(@"__NSDictionaryM"),
                                  @selector(setObject:forKey:), NSSelectorFromString(@"safe_setObject:forKey:"));
            swizzleInstanceMethod(NSClassFromString(@"__NSDictionaryM"),
                                  @selector(setObject:forKeyedSubscript:), @selector(safe_setObject:forKeyedSubscript:));
            swizzleInstanceMethod(NSClassFromString(@"__NSDictionaryM"),
                                  @selector(removeObjectForKey:), @selector(safe_removeObjectForKey:));
            
            
            swizzleInstanceMethod(self.class,
                                  @selector(forwardingTargetForSelector:), @selector(safe_forwardingTargetForSelector:));
        }
    });
}
@implementation NSArray (CashProtector)

- (id)safe_objectAtIndex:(NSUInteger)index{
    if (index >= self.count) {
        if (isOpenAssert) NSAssert(index < self.count, @"index越界");
        return nil;
    }
    return [self safe_objectAtIndex:index];
}

- (id)safe_objectAtIndexedSubscript:(NSUInteger)index {
    NSUInteger count = self.count;
    if (count == 0 || index >= count) {
        if (isOpenAssert) NSAssert(index < self.count, @"index越界");
        return nil;
    }
    return [self safe_objectAtIndexedSubscript:index];
}
@end


@implementation NSMutableArray (CashProtector)

- (id)safe_objectAtIndex:(NSUInteger)index{
    if (index >= self.count) {
//        DDLog(@"index越界");
        if (isOpenAssert) NSAssert(index < self.count, @"index越界");
        return nil;
    }
    return [self safe_objectAtIndex:index];
}

- (id)safe_objectAtIndexedSubscript:(NSUInteger)index {
    NSUInteger count = self.count;
    if (count == 0 || index >= count) {
        return nil;
    }
    return [self safe_objectAtIndexedSubscript:index];
}

- (void)safe_addObject:(id)anObject{
    if(!anObject){
        if (isOpenAssert) NSAssert(anObject, @"anObject不能為nil");
        return ;
    }
    [self safe_addObject:anObject];
}

- (void)safe_insertObject:(id)anObject atIndex:(NSUInteger)index{
    if(!anObject){
        if (isOpenAssert) NSAssert(anObject, @"anObject不能為nil");
        return ;
    }
    [self safe_insertObject:anObject atIndex:index];
}

@end
@implementation NSMutableDictionary (CashProtector)

- (void)safe_setObject:(id)anObject forKey:(id <NSCopying>)aKey{
    if (isOpenAssert) NSAssert(anObject && aKey, @"anObject和aKey不能為nil");
    if (anObject && aKey) {
        [self safe_setObject:anObject forKey:aKey];
    }
}


- (void)safe_setObject:(id)anObject forKeyedSubscript:(id <NSCopying>)aKey {
//    if (isOpenAssert) NSAssert(anObject && aKey, @"anObject和aKey不能為nil");
    if (anObject && aKey) {
        [self safe_setObject:anObject forKeyedSubscript:aKey];
    }
}

- (void)safe_removeObjectForKey:(id <NSCopying>)aKey {
    if (isOpenAssert) NSAssert(aKey, @"aKey不能為nil");
    if (aKey) {
        [self safe_removeObjectForKey:aKey];
    }
}

@end
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末闷沥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子咐容,更是在濱河造成了極大的恐慌舆逃,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異路狮,居然都是意外死亡虫啥,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進(jìn)店門奄妨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涂籽,“玉大人,你說我怎么就攤上這事砸抛∑来疲” “怎么了?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵直焙,是天一觀的道長景东。 經(jīng)常有香客問我,道長奔誓,這世上最難降的妖魔是什么斤吐? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮丝里,結(jié)果婚禮上曲初,老公的妹妹穿的比我還像新娘体谒。我一直安慰自己杯聚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布抒痒。 她就那樣靜靜地躺著幌绍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪故响。 梳的紋絲不亂的頭發(fā)上傀广,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天,我揣著相機(jī)與錄音彩届,去河邊找鬼伪冰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛樟蠕,可吹牛的內(nèi)容都是我干的贮聂。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼寨辩,長吁一口氣:“原來是場噩夢啊……” “哼吓懈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起靡狞,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤耻警,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體甘穿,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡腮恩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年笋庄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了囊拜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,654評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡义矛,死狀恐怖妨托,靈堂內(nèi)的尸體忽然破棺而出缸榛,到底是詐尸還是另有隱情,我是刑警寧澤兰伤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布内颗,位于F島的核電站敦腔,受9級特大地震影響找前,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜槽惫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一合冀、第九天 我趴在偏房一處隱蔽的房頂上張望君躺。 院中可真熱鬧晰洒,春花似錦治宣、人聲如沸坏怪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽百拓。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工纹冤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洒宝,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓萌京,卻偏偏與公主長得像雁歌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子知残,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評論 2 349