美文网首页runtime.runloop
iOS runtime 应用场景总结

iOS runtime 应用场景总结

作者: SoaringHeart | 来源:发表于2020-06-28 12:31 被阅读0次

    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
    

    相关文章

      网友评论

        本文标题:iOS runtime 应用场景总结

        本文链接:https://www.haomeiwen.com/subject/hcnifktx.html