Objective-C Runtime 介绍

最近项目里面有需要屏蔽系统弹出的alertController,上网找了一下方法,发现OC有Runtime的方法可以实现,后来就研究了一下Runtime,在此做一下总结。

既然不知道Runtime是究竟个什么东东,那照惯例先从苹果的开发文档下手。

Overview

The Objective-C runtime is a runtime library that provides support for the dynamic properties of the Objective-C language, and as such is linked to by all Objective-C apps. Objective-C runtime library support functions are implemented in the shared library found at /usr/lib/libobjc.A.dylib.

You typically don’t need to use the Objective-C runtime library directly when programming in Objective-C. This API is useful primarily for developing bridge layers between Objective-C and other languages, or for low-level debugging.

The macOS implementation of the Objective-C runtime library is unique to the Mac. For other platforms, the GNU Compiler Collection provides a different implementation with a similar API. This document covers only the macOS implementation.

The low-level Objective-C runtime API is significantly updated in OS X version 10.5. Many functions and all existing data structures are replaced with new functions. The old functions and structures are deprecated in 32-bit and absent in 64-bit mode. The API constrains several values to 32-bit ints even in 64-bit mode—class count, protocol count, methods per class, ivars per class, arguments per method, sizeof(all arguments) per method, and class version number. In addition, the new Objective-C ABI (not described here) further constrains sizeof(anInstance) to 32 bits, and three other values to 24 bits—methods per class, ivars per class, and sizeof(a single ivar). Finally, the obsolete NXHashTable and NXMapTable are limited to 4 billion items.

貌似上面一大段都在说macOS开发,其实不然,“For other platforms” 就说明其他平台(ios)也能使用,
平台。文档说了这么多,就看第一句就可以了:Objective-C runtime是一个提供OC语言动态特性的运行库,因此被所有Objective-C应用程序连接。

Runtime机制

顾名思义,Runtime就是运行时的意思,指一个程序在运行(或者在被执行)的状态。OC的Runtime库时一套比较底层的C语言库,我们平时编写OC代码,最终运行起来都是转成了Runtime的C语言代码。

Runtime的常用API

有个大概的了解,我们再来瞅瞅API是咋样的。(当然,Runtime的API很多,这里只罗列部分)

  • class_getName、class_getSuperclass
    class_getName是获取Class的名字(这里的Class是对象里指向一个结构体的指针,可以点击这里去了解类)传递参数Class结构体,返回类的名字;
    class_getSuperclass是获取父类的Class。传递参数Class结构体,返回它的父类的Class。
  • objc_getMetaClass
    获取MetaClass(这里的MetaClass是上面提到的Class里指向一个结构体的指针,点击这里去了解MetaClass)。传递参数Class的名字,返回Class的MetaClass
  • class_getProperty
    获取类的property属性,传递参数Class结构体和Class的名字,返回objc_property_t结构体。

    注:如果在XCode里面查看objc_property_t会发现它是一个指向结构体objc_property的指针,当你想进一步看清楚objc_property又是什么的时候,会发现不能点进去看,你会发现这时候有这么一行注释:
    /// An opaque type that represents an Objective-C declared property.
    typedef struct objc_property *objc_property_t;

    这是一个模糊不清的类型,它代表着oc声明的属性。苹果这是屏蔽不让我们看到里面的数据结构,那我们只能用这个指向结构体的指针来持有这个整体,来进行传参和接收返回值。类似的注释说明还有objc_method *Method、objc_ivar *Ivar、objc_category *Category,SEL,IMP等。

  • class_getMethodImplementation
    获取方法的实现,传递参数Class结构体和SEL结构体,返回IMP结构体。

  • class_respondsToSelector
    判断是否能否相应某个方法。传递参数Class结构体和SEL结构体,返回BOOL变量。
  • class_replaceMethod
    替换某个方法。参数有Class结构体、SEL结构体、IMP结构体和char*,返回IMP结构体。也就是指定类、和类的哪个方法、替换后的方法实现、方法参数类型来替换,并返回替换之前的方法实现。参数设置可以参考这篇文章。
  • method_exchangeImplementations
    交换方法的实现,分别传递两个IMP结构体作参数,实现方法实现的交换。

这些API可以实现程序运行时做一些动态操作,例如获取类的方法、属性、交换方法的实现等,会大大提高代码实现的灵活性。

代码示例

说了这么久,还是来点代码最实际~
回到开篇说到的屏蔽系统弹出的AlertController,是因为调用一个动态更新app桌面图标的API(UIApplication的setAlternateIconName方法)引起的系统提示框,业务需求想把它干掉。为此上网找了一下,发现用runtime搞就挺不错的。
于是,兴高采烈地把网上代码用上:

- (void)runtimeReplaceAlert:(UIViewController *)vc
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method previous_present = class_getInstanceMethod(vc.class, @selector(presentViewController:animated:completion:));
        Method latter_present = class_getInstanceMethod(self.class, @selector(latter_presentViewController:animated:completion:));
        // 交换方法实现
        method_exchangeImplementations(previous_present, latter_present);
    });
}

// 替换后的方法
- (void)latter_presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {

    if ([viewControllerToPresent isKindOfClass:[UIAlertController class]]) {
        NSLog(@"title : %@",((UIAlertController *)viewControllerToPresent).title);
        NSLog(@"message : %@",((UIAlertController *)viewControllerToPresent).message);

        // 换图标时的提示框的title和message都是nil,所以就可以判断title和message是否为nil
        UIAlertController *alertController = (UIAlertController *)viewControllerToPresent;
        if (alertController.title == nil && alertController.message == nil) {
            return;
        } else {// 其他情况正常处理
            [self latter_presentViewController:viewControllerToPresent animated:flag completion:completion];
            return;
        }
    }

    [self latter_presentViewController:viewControllerToPresent animated:flag completion:completion];
}

为了不想让调用setAlternateIconName的ViewController太臃肿,于是我就用单独写了一个Manager的类来管理更换图标,所以就传了个vc对象进来。
起初,确实发现更换图标没再出现系统的提示框了,感觉确实好使,然后就放一边了。后来,app不知怎的present一个vc就会出现闪退,报了个错说找不到latter_presentViewController:animated:completion:,于是就调试了一下,走到了第29行[self latter_presentViewController:viewControllerToPresent animated:flag completion:completion];就崩掉了。

再看看调试变量列表,如下显示:
这里写图片描述
self是一个RootViewController,找不到latter_presentViewController:viewControllerToPresent animated:flag completion:completion也是正常了,毕竟,我只是交换了两个方法的实现,方法名并没有换掉。
既然这样,我加一个latter_presentViewController:viewControllerToPresent animated:flag completion:completion进入不就行了吗?就把代码改成这样了:

- (void)runtimeReplaceAlert:(UIViewController *)vc
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method previous_present = class_getInstanceMethod(vc.class, @selector(presentViewController:animated:completion:));
        Method latter_present = class_getInstanceMethod(self.class, @selector(latter_presentViewController:animated:completion:));
        // 交换方法实现
        method_exchangeImplementations(previous_present, latter_present);
        // 再把系统的原方法加回去,方法命名仍为latter_presentViewController:animated:completion:方便调用
        BOOL isSuccess = class_addMethod(vc.class, @selector(latter_presentViewController:animated:completion:), method_getImplementation(presentSwizzlingM), method_getTypeEncoding(presentSwizzlingM));
        if (!isSuccess) {
            method_exchangeImplementations(previous_present, latter_present);
        }
    });
}

// 替换后的方法
- (void)latter_presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {

    if ([viewControllerToPresent isKindOfClass:[UIAlertController class]]) {
        NSLog(@"title : %@",((UIAlertController *)viewControllerToPresent).title);
        NSLog(@"message : %@",((UIAlertController *)viewControllerToPresent).message);

        // 换图标时的提示框的title和message都是nil,所以就可以判断title和message是否为nil
        UIAlertController *alertController = (UIAlertController *)viewControllerToPresent;
        if (alertController.title == nil && alertController.message == nil) {
            return;
        } else {// 其他情况正常处理
            [self latter_presentViewController:viewControllerToPresent animated:flag completion:completion];
            return;
        }
    }
    if ([self respondsToSelector:@selector(methodSwizzling_presentViewController:animated:completion:)]) {
        [self latter_presentViewController:viewControllerToPresent animated:flag completion:completion];
    }
}

比起之前的代码,多了添加方法的操作class_addMethod、添加不成功的情况下就换回去、执行latter_presentViewController:animated:completion:之前判断能否响应这个方法。这下就能正常present一个vc了,还做了容错处理。

注:使用class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)这个RunTime API的时候,name参数可以通过@selector()、imp参数可以通过method_getImplementation()、types参数可以通过method_getTypeEncoding()快速获取。另外,也符合上面提到的SEL、IMP等为了持有整体进行传参或接返回值。
有关types参数的具体说明请点击这里

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值