iOS runtime 应用场景总结

iOS runtime 应用场景总结

场景1. 动态分类关联属性
场景2. hook/Method Swizzling
场景3. 遍历类属性方法,映射解析以及字典与模型的转换, 例如YYModel
场景4. 修改isa指针(研究中)
场景5. 实现消息转发机制的补救


sr0kew2ahk.jpg

一般选择第二步 forwardingTargetForSelector 来做崩溃防护,原因如下:

resolveInstanceMethod 需要在类的本身上动态添加它本身不存在的方法;
forwardInvocation可以通过NSInvocation的形式将消息转发给多个对象,但是其开销较大,需要创建新的NSInvocation对象,并且forwardInvocation的函数经常被使用者调用,来做多层消息转发选择机制,不适合多次重写;
forwardingTargetForSelector可以将消息转发给一个对象,开销较小,可以NSObject的该方法重写,做以下几步的处理:
1). 动态创建一个桩类
2). 动态为桩类添加对应的Selector,用一个通用的返回0的函数来实现该SEL的IMP
3). 将消息直接转发到这个桩类对象上。

场景6. 实现 NSCoding 的自动归档和解档(暂不支持嵌套,可用于详情模型的本地存储)

- (void)encodeWithCoder:(NSCoder *)aCoder {
    // 一个临时数据, 用来记录一个类成员变量的个数
    unsigned int ivarCount = 0;
    // 获取一个类所有的成员变量
    Ivar *ivars = class_copyIvarList(self.class, &ivarCount);
    
    // 变量成员变量列表
    for (int i = 0; i < ivarCount; i ++) {
        // 获取单个成员变量
        Ivar ivar = ivars[i];
        // 获取成员变量的名字并将其转换为 OC 字符串
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        // 获取该成员变量对应的值
        id value = [self valueForKey:ivarName];
        // 归档, 就是把对象 key-value 对 encode
        [aCoder encodeObject:value forKey:ivarName];
    }
    // 释放 ivars
    free(ivars);
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    // 因为没有 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:, 动态创建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;//给一个非指针对象(如NSInteger)赋值 nil, 直接忽略
}

场景 8:获取类的成员变量,属性,方法,协议

- (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);
}

【附】:赋值和取值(没用到过,因为 KVC 更方便)
 //获取实例进行赋值操作
Ivar ivar = class_getInstanceVariable([UITextField class], "_placeholderLabel");
object_setIvar(self.textField, ivar, "请输入");
                   
//获取当前类对应特征名称的实例变量,得到该实例变量的数值
Ivar ivar = class_getInstanceVariable([UITextField class], "_placeholderLabel");
id object = object_getIvar(self.textField, ivar);

场景 9. 动态创建/获取 class,selector 等以及视图子元素动态化重构,极大地提高代码复用率

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

场景 10. 动态创建Class

objc_allocateClassPair可以动态创建Class,
objc_registerClassPair进行注册动态创建的Class
修改对象的Class
object_setClass可以修改对象的Class,即修改了isa指针指向的Class对象

场景 11. 动态调整 accessoryView 位置,使其上下居中(默认不居中)

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{

    ///调整AccessoryView位置(默认垂直居中)
    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. 防止数组越界和字典赋 nil 造成的崩溃(支持语法糖保护)

+ (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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352