前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >译文: 低调奢华有内涵的「Runtime」

译文: 低调奢华有内涵的「Runtime」

作者头像
iOS Development
发布2019-02-14 17:48:46
9410
发布2019-02-14 17:48:46
举报

这是一篇译文,原文是The down low on Objective-C Runtime ,原文文风俏皮,所以我也没有直译,尽量意译。 当然,我是翻译了这篇文章,但是对Runtime的理解,还是很基础——主要是还没有太多实践,真实开发中几乎也用不到,一如文章所说:「如果可以,避免使用Objective-C的Runtime……」。所以,有问题,我暂时也解答不了。 而至于为什么现在看这个几乎用不着的Runtime?主要是受刺激了。

正文:

你期待看到的是最近更新的Xcode 8 和Swift3.0?你又错了:这次要聊的是我们的老相好——Objective-C!(译者:这篇文章发表于2016年10月4日,那时候刚更新Xcode8)

为什么还要聊OC?Swift3.0不是要干死Objective-C了吗?

此言差矣。Swift虽是天天上头条,但是并不意味着已经完全把曾经和我们朝夕相处的老相好干翻了。为什么非得要互怼,就不能一起愉快滴玩耍吗?一起在「操场」(一语相关)上基情四射。(译者:操场——playground,是Swift的一个工具,用于学习、验证Swift)。

Swift的一个核心功能就是可以和OC进行混编。这两种语言可能根本上不一样,但实际上可以很好地互补。

虽然Swift是用来取代Objective-C的,但苹果依然继续维护Ovjective-C。主要改进了:可以更好地和Swift编译,并添加了很多新特性,如nullability、generics。没有改变OC应用的行为,OC对于开发者来说仍然是一种可读性好的开发语言。

还有,不要忘记,很多激动人心的代码库都是用Objective-C写的,Cocoa本身就是用Objective-C写的,还有很多第三方库,和November Five(译者:一家公司)的内部库。

换言之,虽然Swift很明显会扮演越来越重要的角色,但是Objective-C还是会在未来几年保持影响力。这门古老的语言,还是有一些有用的窍门不为人熟知。而我的最爱,就是下面要讲到的Objective-C Runtime——对于大部分开发者而言,还是有些神秘。

故事要从这里讲起

不久前,当浏览「iOS-developers Slack commnunity」时(译者注:Slack上一个聚集了iOS开发者的地方),我看到有人在Swift频道问一个问题。另外一个人提到了Objective-C的Runtime可能可以解决问题,然后过半的开发者认同答案。

大部分苹果开发者大概听过Runtime——他们知道Runtime的存在——但绝大部分人没有去用过。这个主题不常出现,无论是社区论坛还是苹果自己的文档资料。事实上,苹果还特别声明:

「当你用Objective-C编程的时候,并不需要用到Runtime库」

这就很容易理解为什么有人会这样说:在不了解Objective-C Runtime下去使用它,将会是危险的(会导致程序异常或者崩溃)。因为Runtime允许你访问很多Cocoa或者第三方库的底层特性。

Objective-C的Runtime究竟是什么?

Objective-C的Runtime,是一个用C和「汇编」写的开源库,它为C添加了面向对象的特性,从而创建了Objrctive-C这门语言。

下面引用一些Objective-C Runtime的定义——因为我相信自己是讲不清楚的:

「Objective-C可以从『编译时』、『链接时』再到『运行时』,hold住尽可能多的决策。只要有可能,它都是动态地干活儿的。这就意味着,这门语言不仅需要一个编译器,还需要一个runtime系统,用来执行编译的代码。这个runtime系统就好比如是Objective-C的操作系统,(runtime系统)让这门语言能工作起来。」

上面这个陈述,表明Objective-C是动态干活儿的,也就是说Objective-C是一门动态语言,与之相反,就是Swift、C++、Java等等这类语言。是什么因素决定了一门语言是静态的还是动态?最主要的,就是看方法的调用(什么时候、由谁决定、执行哪段代码,什么时候方法会被执行),还有类型绑定(什么时候决定一个变量会有什么类型)。

静态语言,使用的是静态的方法调度,还有前期类型绑定,意味着编译器在「编译时」就已经定下来了。也就是说,当一个程序正在运行时,你可以100%确保开发者的意图是会被执行的。

而像Objective-C这类动态语言,就有点不一样了。所有的决定都是在Objecitve-C的Runtime库创造的。正因为有了这个库,我们可以自己操纵方法的调度和类型的绑定。

也就是,Objective-C的Runtime,允许大伙儿在runtime(运行时)创建、修改、移除以下内容:

  • 类/Class
  • 方法/Method
  • 实现/Implementation
  • 属性/Properties
  • 实例变量/Instance varivables

另外,你不单可以对自己的代码进行上述修改;Runtime还可以让你操作闭源的代码库,甚至是苹果自己的框架。

杀伤力几何?

现在你可能会想:「wow,听起来好屌」。你是对的,确实屌屌的。但是,如果你曾经是个码农,你更可能想到的是:「wow,听起来好危险」。然后,你又对了。

Objective-C的Runtime就像一把双刃剑,使用它,风险高,回报也高。它赋予你很大的权力,但只要你犯了哪怕一丁点儿错误,都有可能让程序挂掉。Runtime让你有权修改本来不需要修改的代码,还可以访问本来是私有的代码。

听起来很恐怖,不过不是说不要用Runtime了。某位大神曾经讲过:「能力越大,责任越大」。而我们在November Five(译者:一家公司名)也一直尝试使用各种强悍的工具,让事情变得更美好。这里有一些我们过去使用Runtime的真实例子。

用于检视(闭源框架)类的方法、属性;进行学习(Looking under the hood & learning from it)

因为Objective-C的Runtime允许你检视、重写(覆盖)、修改私有或者闭源框架中的方法,这样就可以揭开别人的神秘的面纱,看到某人的代码是如何工作的,所以Runtime是一个很有价值的学习工具。比如,假设你想创建一个类似UITableView,但又有点不一样的组件,这时候你可以用Runtime看一下UITableView是如何构建的。下面就是一个很方便的方法:

代码语言:javascript
复制
+(NSArray*)objcruntime_getMethodNames
{
    Class class = [self class];
    NSMutableArray* names = [NSMutableArray new];

    uint count;
    Method* methods = class_copyMethodList(class, &count);

    for (NSUInteger i = 0; i < count; ++i)
    {
        Method property = methods[i];

        SEL methodSelector = method_getName(property);
        NSString* methodName = NSStringFromSelector(methodSelector);
        if (methodName)
            [names addObject:methodName];
    }
    free(methods);

    return [names copy];
}

上面的代码,允许你在控制台快速地检索到所有方法名。如果在UITableView中使用,就会看到如下结果:

代码语言:javascript
复制
(lldb) po [UITableView objcruntime_getMethodNames]
<__NSArrayI 0x148316000>(
indexPathIsFirstRowInSection:,
indexPathIsLastRowInSection:,
indexPathIsFirstSection:,
indexPathIsLastSection:,
sectionIsLastSection:,
_cnui_applyContactStyle,
_cnui_applyContactStyleStark,
_cnui_adjustContentInset:,
ab_delayedScrollRespectingCaretOfActiveTextViewToCell:atIndexPath:atScrollPosition:animated:,
ab_internalScrollToRowAtIndexPathRespectingCaretOfActiveTextView:atScrollPosition:animated:,
ab_scrollToRowAtIndexPathRespectingCaretOfActiveTextView:atScrollPosition:animated:,
_mapkit_dequeueReusableCellWithIdentifier:,
initWithFrame:,
setFrame:,
layoutSubviews,
.cxx_destruct,
_contentSize,
setBackgroundColor:,
traitCollection,
initWithCoder:,
_populateArchivedSubviews:,

可以看到,这里打印出来的方法,比平常在.h文件看到的多。你可以自己试一下。我们还创建一个NSObject的category,专门用来干这个的,可以在这里找到(译者:链接挂掉了)。

使用关联对象(Working with associated objects)

有时候你会有这样的需求:要在一个类的category添加一个属性,不幸的是,在Objective-C是不能这样干的(译者:通过category可以添加属性,但是不能添加实例变量——因此属性并不能用)。幸运的是,你有associated objects,它允许你在「运行时」将任意值和某个对象关联起来。假设你要创建一个UIImageView的category,用于下载图片。这种情况下,你就可以用associated objects添加一个用于保存图片NSURL的属性,类似如下:

代码语言:javascript
复制
NSURL* imageURL = [NSURL URLWithString:@"https://www.trythisforexample.com/images/example_logo.png"];
objc_setAssociatedObject(self.imageView, (__bridge CFStringRef)@"imageURL", imageURL, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

稍后要拿回URL的话,可以这样做: NSURL* imageURL = objc_getAssociatedObject(self.imageView, (__bridge CFStringRef)@"imageURL");

调试闭源的源代码(Debugging closed source code)

有时候你会遇到程序崩溃,但引起崩溃的代码并不是你写的那部分。如果是开源的代码库,解决办法很简单:你报告这个问题,最好自己解决,然后创建一个pull request(译者:类似在GitHub上贡献开源库的过程)。但如果是闭源的框架,就不好说了。当然你可以报告这个问题,并且保佑很多人也遇到同样的问题,然后祈祷作者可以快速地修复,但确实很难保证问题会得到解决——很可能你也没有时间跟他耗。

之前我们就遇过一遭,我们的应用Appmiral崩溃了,Spotify这个SDK(一个闭源库)导致了这个问题,具体原因是有一个未被识别的selector发送给了一个对象实例,这个对象在这个SDK并没有暴露出来。我们报告了这个错误,并且收到了在下一个版本中会修复这个问题的回复——但悲剧的是,对方没有明确什么时候会发下一版。在节假日期间,我们通常每周会提交多个节日版本,很明显耐心等待人家修复这个问题并不是一种很好的选择。感谢Objective-C的Runtime,我们可以在「运行时」为这个对象添加缺失的方法(方法的实现为空),这样就可以防止这个崩溃了。虽然不是一种理想的解决方案,但在等真正导致问题的修复发布前,Runtime确实帮忙防止了成千上万这种崩溃(译者:通过class_addMethod()函数,可以在+ (BOOL)resolveInstanceMethod:(SEL)sel方法中动态地添加方法实现)。

JSONModel

很多流行的第三方库都是利用Objective-C的Runtime实现的,JSONModel就是我们常用到的一个。有人可能不知道,JSONModel允许你轻松地从JSON创建数据模型。实现原理是:Objective-C的Runtime,会在「运行时」读取对象的属性,并填充从JSON获取的值。

要知道它具体是怎么实现的,只需要看一下JSONModel.m文件的__inspectProperties方法就可以了。下面是一个简单的截取:

代码语言:javascript
复制
// inspect inherited properties up to the JSONModel class
while (class != [JSONModel class]) {
    //JMLog(@"inspecting: %@", NSStringFromClass(class));

    unsigned int propertyCount;
    objc_property_t *properties = class_copyPropertyList(class, &propertyCount);

    //loop over the class properties
    for (unsigned int i = 0; i < propertyCount; i++) {

        JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];

        //get property name
        objc_property_t property = properties[i];
        const char *propertyName = property_getName(property);
        p.name = @(propertyName);

        //JMLog(@"property: %@", p.name);

        //get property attributes
        const char *attrs = property_getAttributes(property);
        NSString* propertyAttributes = @(attrs);
        NSArray* attributeItems = [propertyAttributes componentsSeparatedByString:@","];

总结(Wrapping up)

通过上面这些例子,可以总结一下Objective-C的Runtime了!你记住了什么了吗?

  • 如果可以,避免使用Objective-C的Runtime,只有在手头上的问题不能用其他方法解决时,才使用它(小心使用)。
  • 当你使用Runtime时,要清醒知道自己在做什么。
  • 说真的,使用Runtime的时候,确保知道自己在做什么!风险很高的,而且很多事情可能会出错。
  • 不要用来修改苹果框架的私有方法,你的App上架时会被拒的。
  • 如果你交换(swizzle)了苹果框架的方法,始终要调用原来的方法实现。要知道系统更新会对你的应用产生严重影响。

你还想研究更多关于Objective-C Runtime的内容吗?如果你是一个Cocoa开发者,最好的学习资源当然是苹果自己的API文档。或者点击这些优秀的博文,关于associated objectsmethod swzzling,还有这个Objective-C Runtime的基本应用(译者:最后这个链接挂掉了,不用点了)。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.08.08 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 故事要从这里讲起
  • Objective-C的Runtime究竟是什么?
  • 杀伤力几何?
    • 用于检视(闭源框架)类的方法、属性;进行学习(Looking under the hood & learning from it)
      • 使用关联对象(Working with associated objects)
        • 调试闭源的源代码(Debugging closed source code)
          • JSONModel
          • 总结(Wrapping up)
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档