Runtime的运用和减少应用崩溃

作者: iOS_小松哥 | 来源:发表于2017-06-03 14:18 被阅读1691次

    Objective-C 是一个动态语言,它需要一个运行时系统来动态的创建类和对象、进行消息传递和转发。关于Runtime的知识大家可以参看Apple开源的Runtime代码Rumtime编程指南

    本文总结一些其常用的方法。

    一、新建测试Demo

    我们先创建一个测试Demo如下图,其中TestClass是一个测试类,TestClass+Category是它的一个分类,NSObject+Runtime封装了一些Runtime的方法。大家可以在这里下载Demo

    Demo

    下面是几个类的主要部分:

    TestClass.h TestClass.m TestClass+Category.h TestClass+Category.m

    二、Runtime的封装

    接下来我们就来看看NSObject+Runtime中的内容,其对Runtime常用的方法进行了简单的封装:

    Paste_Image.png

    别着急,我们一个一个看。

    1、获取成员变量

    下面这个方法就是获取类的成员变量列表,其中包括属性生成的成员变量。我们可以用ivar_getTypeEncoding()来获取成员变量的类型,用ivar_getName()来获取成员变量的名称:

    + (NSArray *)fetchIvarList
    {
        unsigned int count = 0;
        Ivar *ivarList = class_copyIvarList(self, &count);
        
        NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
        for (unsigned int i = 0; i < count; i++ )
        {
            NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithCapacity:2];
            const char *ivarName = ivar_getName(ivarList[i]);
            const char *ivarType = ivar_getTypeEncoding(ivarList[i]);
            dic[@"type"] = [NSString stringWithUTF8String: ivarType];
            dic[@"ivarName"] = [NSString stringWithUTF8String: ivarName];
            
            [mutableList addObject:dic];
        }
        free(ivarList);
        return [NSArray arrayWithArray:mutableList];
    }
    

    使用[TestClass fetchIvarList]方法获取TestClass类的成员变量结果:

    TestClass的成员变量列表
    2、获取属性列表

    下面这个方法获取的是属性列表,包括私有和公有属性,也包括分类中的属性:

    + (NSArray *)fetchPropertyList
    {
        unsigned int count = 0;
        objc_property_t *propertyList = class_copyPropertyList(self, &count);
        
        NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
        for (unsigned int i = 0; i < count; i++)
        {
            const char *propertyName = property_getName(propertyList[i]);
            [mutableList addObject:[NSString stringWithUTF8String:propertyName]];
        }
        free(propertyList);
        return [NSArray arrayWithArray:mutableList];
    }
    
    

    使用[TestClass fetchPropertyList]获取TestClass的属性列表结果:

    TestClass的属性列表
    3、获取实例方法

    下面这个方法就是获取类的实例方法列表,包括getter, setter, 分类中的方法等:

    + (NSArray *)fetchInstanceMethodList
    {
        unsigned int count = 0;
        Method *methodList = class_copyMethodList(self, &count);
        
        NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
        for (unsigned int i = 0; i < count; i++)
        {
            Method method = methodList[i];
            SEL methodName = method_getName(method);
            [mutableList addObject:NSStringFromSelector(methodName)];
        }
        free(methodList);
        return [NSArray arrayWithArray:mutableList];
    }
    

    使用[TestClass fetchInstanceMethodList]获取TestClass的实例方法列表的结果:

    TestClass实例方法列表
    4、获取类方法列表

    下方这个方法就是获取类的类方法列表:

    + (NSArray *)fetchClassMethodList
    {
        unsigned int count = 0;
        Method *methodList = class_copyMethodList(object_getClass(self), &count);
        
        NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
        for (unsigned int i = 0; i < count; i++)
        {
            Method method = methodList[i];
            SEL methodName = method_getName(method);
            [mutableList addObject:NSStringFromSelector(methodName)];
        }
        free(methodList);
        return [NSArray arrayWithArray:mutableList];
    }
    

    使用[TestClass fetchClassMethodList]获取TestClass的类方法列表的结果:

    TestClass类方法列表
    5、获取协议列表

    下面是获取类所遵循协议列表的方法:

    + (NSArray *)fetchProtocolList
    {
        unsigned int count = 0;
        __unsafe_unretained Protocol **protocolList = class_copyProtocolList(self, &count);
        
        NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
        for (unsigned int i = 0; i < count; i++ )
        {
            Protocol *protocol = protocolList[i];
            const char *protocolName = protocol_getName(protocol);
            [mutableList addObject:[NSString stringWithUTF8String:protocolName]];
        }
        
        return [NSArray arrayWithArray:mutableList];
    }
    

    使用[TestClass fetchProtocolList]获取TestClass类所遵循的协议列表的结果:

    TestClass的协议列表
    6、给类添加一个方法

    下面的方法就是给类添加方法。第一个参数是方法的SEL,第二个参数则是提供方法实现的SEL。这个可以用在找不到某个方法时就添加一个,不然有可能会崩溃。详见Demo。

    + (void)addMethod:(SEL)methodSel methodImp:(SEL)methodImp;
    {
        Method method = class_getInstanceMethod(self, methodImp);
        IMP methodIMP = method_getImplementation(method);
        const char *types = method_getTypeEncoding(method);
        class_addMethod(self, methodSel, methodIMP, types);
    }
    
    7、交换实例方法

    下面的方法就是将类的两个实例方法进行交换。如果将originMethod与currentMethod的方法实现进行交换的话,调用originMethod时就会执行currentMethod的内容。详见Demo。

    + (void)swapMethod:(SEL)originMethod currentMethod:(SEL)currentMethod;
    {
        Method firstMethod = class_getInstanceMethod(self, originMethod);
        Method secondMethod = class_getInstanceMethod(self, currentMethod);
        method_exchangeImplementations(firstMethod, secondMethod);
    }
    
    8、交换类方法

    下面的方法就是将类的两个类方法进行交换,与交换实例方法类似,详见Demo。

    + (void)swapClassMethod:(SEL)originMethod currentMethod:(SEL)currentMethod;
    {
        Method firstMethod = class_getClassMethod(self, originMethod);
        Method secondMethod = class_getClassMethod(self, currentMethod);
        method_exchangeImplementations(firstMethod, secondMethod);
    }
    
    

    三、利用Runtime减少应用崩溃

    利用交换方法可以减少程序中的崩溃,例如数组越界等等。demo里面的Safe文件夹就是一些防止崩溃的分类,直接放进工程就可以了。

    Safe

    补充:如果有键盘推到后台崩溃的话,需要到build phase里给NSArray+Safe.m文件加上-fno-objc-arc标识

    我们看一个例子,其他的大家可以去demo里面看。

    我们首先把__NSArrayIobjectAtIndex方法换成我们的ls_objectAtIndex,然后方法里面判断但是否越界,是的话直接返回nil:

    [NSClassFromString(@"__NSArrayI") swapMethod:@selector(objectAtIndex:) currentMethod:@selector(ls_objectAtIndex:)];
    
    - (id)ls_objectAtIndex:(NSUInteger)index
    {
        if (index >= [self count])
        {
            return nil;
        }
        return [self ls_objectAtIndex:index];
    }
    
    

    然后当我们想下面这样写的时候就不会崩溃了:

    NSArray *array = @[@"aa",@"ddd"];
    array[5];
    

    好了,先说到这里吧。大家可以下载我的demo详细看一下。

    欢迎关注 和我的专题:iOS技术交流,查看更多好文章。

    相关文章

      网友评论

      • _luckysk:老板 来两个老婆饼 不要饼
        iOS_小松哥:@luckySK 踢踢踢
      • 小凡凡520:swift 的版本有吗
      • kococlass:是缘是情是童真还是意外
        iOS_小松哥:@kococlass 调皮
      • 雯子cyan:开发过程使用这种方法减少奔溃会不会导致不太好发现bug?
        iOS_小松哥:@雯子cyan 你可以设置debug模式下不生效
      • 星月之星歌:load方法里的方法交换最好加上dispatch once
        starfox寒流:@星月之星歌 你说的没错。 不过你的问题是特定场景下的:
        场景1,你添加了NSObject的category,并override了load方法。
        场景2,在其他地方调用了[super load].

        场景1 可以加dispatch one,而场景2要尽量避免。
        星月之星歌:@starfox寒流 APP启动过程中,main函数之前,镜像加载完成后,会逐一调用包含类和分类中的所有load方法,并且是以函数指针的形式调用,不走消息转发机制。这时候,load方法是会只被调用一次的。但在这之后,load方法的调用仍然会遵循消息转发机制,也就是说,如果在子类中某处调用了[super load],父类中的load方法仍然有可能被调用到。比较典型的例子是,iOS 11后的storeKit中某些类,会调用父类的load方法,可能导致NSObject的分类中的load方法会被调用不止一次。所以,为了代码的强壮性,建议在load中使用dispatch once。
        starfox寒流:不知道何时出现这么个观点的,我在公司的项目中看到以前的代码中也采用了你说的这种写法。
        但是从load的概念来看,它只会被调用一次。
        “对于加入运行时的类class和分类category来说,当包含类和分类的程序库载入系统时,必定会调用+ (void)load方法,且只调用一次。
        对于iOS系统,是在应用程序启动的时候。对于Mac OS X来说更自由,因为可以使用动态加载dynamic loading这种特性,等应用程序启动完成之后再去加载程序库。若分类和类中都定义了load方法,则先调用类的,再调用分类的。”

        可以去看看Effective objc 2.0
      • 成博_:小松哥 参数里面传 object_getClass(self) 和 object_getClass([self class]) 还有直接传self 这三种 具体差别在哪啊
        郭小弟:@成博_ 括号里面传的是实例对象,获取的是类对象,括号里面传的是类对象,获取到的是元类对象
      • 沉船无数:是缘是情是童真还是意外。松哥收徒吗?我会烤肠、烤鸡翅。
        iOS_小松哥:@沉船无数 爱我别走
      • lionsom_lin:又学到了一些干活,厉害了:sunglasses:
        iOS_小松哥:是缘是情是童真还是意外
      • 从小玩到大的青梅竹马:是缘是情是童真还是意外
        iOS_小松哥:@从小玩到大的青梅竹马 调皮
      • 无星灬:我的饼卖不出去了松哥
        iOS_小松哥:@无星灬 来和我一起卖吧
      • 吊儿郎当的认真:替换数组的取值方法 在调用键盘时会cash
        吊儿郎当的认真:@iOS_小松哥 感觉还是用分类添加自定义取值方法好些 毕竟是替换了系统自带的方法 不知道还会有其他什么问题:flushed:
        iOS_小松哥:需要到build phase里给NSArray+Safe.m文件加上-fno-objc-arc标识
      • 张家小虎:松哥真棒
        iOS_小松哥:@张家小虎 是缘是情是童真还是意外
      • 跳跳虾:群主威武
        iOS_小松哥:@跳跳虾 盼望你没有为我又再度暗中淌泪
      • 失忆的程序员:腿呢?让我抱一会儿
        iOS_小松哥:@Await_Xpf 是缘是情是童真还是意外

      本文标题:Runtime的运用和减少应用崩溃

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