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 调皮
  • 907f3d31621c:开发过程使用这种方法减少奔溃会不会导致不太好发现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