博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
(译)Objective-C的动态特性
阅读量:4132 次
发布时间:2019-05-25

本文共 5110 字,大约阅读时间需要 17 分钟。

这是一篇译文,原文在此,上一篇文章就是受这篇文章启发,这次干脆都翻译过来。

过去的几年中涌现了大量的Objective-C开发者。有些是从动态语言转过来的,比如Ruby或Python,有些是从强类型语言转过来的,如Java或C#,当然也有直接以Objective-C作为入门语言的。也就是说有很大一部分开发者都没有使用Objective-C太长时间。当你接触一门新语言时,更多地会关注基础知识,如语法和特性等。但通常有一些更高级的,更鲜为人知又有强大功能的特性等待你去开拓。

这篇文章主要是来领略下Objective-C的运行时(runtime),同时解释是什么让Objective-C如此动态,然后感受下这些动态化的技术细节。希望这回让你对Objective-C和Cocoa是如何运行的有更好的了解。

The Runtime

Objective-C是一门简单的语言,95%是C。只是在语言层面上加了些关键字和语法。真正让Objective-C如此强大的是它的运行时。它很小但却很强大。它的核心是消息分发。

Messages

如果你是从动态语言如Ruby或Python转过来的,可能知道什么是消息,可以直接跳过进入下一节。那些从其他语言转过来的,继续看。

执行一个方法,有些语言,编译器会执行一些额外的优化和错误检查,因为调用关系很直接也很明显。但对于消息分发来说,就不那么明显了。在发消息前不必知道某个对象是否能够处理消息。你把消息发给它,它可能会处理,也可能转给其他的Object来处理。一个消息不必对应一个方法,一个对象可能实现一个方法来处理多条消息。

在Objective-C中,消息是通过objc_msgSend()这个runtime方法及相近的方法来实现的。这个方法需要一个target,selector,还有一些参数。理论上来说,编译器只是把消息分发变成objc_msgSend来执行。比如下面这两行代码是等价的。

[array insertObject:foo atIndex:5];

objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);
Objects, Classes, MetaClasses

大多数面向对象的语言里有 classes 和 objects 的概念。Objects通过Classes生成。但是在Objective-C中,classes本身也是objects(译者注:这点跟python很像),也可以处理消息,这也是为什么会有类方法和实例方法。具体来说,Objective-C中的Object是一个结构体(struct),第一个成员是isa,指向自己的class。这是在objc/objc.h中定义的。

typedef struct objc_object {

Class isa;
} *id;
object的class保存了方法列表,还有指向父类的指针。但classes也是objects,也会有isa变量,那么它又指向哪儿呢?这里就引出了第三个类型: metaclasses。一个 metaclass被指向class,class被指向object。它保存了所有实现的方法列表,以及父类的metaclass。如果想更清楚地了解objects,classes以及metaclasses是如何一起工作地,可以阅读这篇文章。

Methods, Selectors and IMPs

我们知道了运行时会发消息给对象。我们也知道一个对象的class保存了方法列表。那么这些消息是如何映射到方法的,这些方法又是如何被执行的呢?

第一个问题的答案很简单。class的方法列表其实是一个字典,key为selectors,IMPs为value。一个IMP是指向方法在内存中的实现。很重要的一点是,selector和IMP之间的关系是在运行时才决定的,而不是编译时。这样我们就能玩出些花样。

IMP通常是指向方法的指针,第一个参数是self,类型为id,第二个参数是cmd,类型为SEL,余下的是方法的参数。这也是self和cmd被定义的地方。下面演示了Method和IMP

  • (id)doSomethingWithInt:(int)aInt{}

id doSomethingWithInt(id self, SEL _cmd, int aInt){}

其他运行时的方法

现在我们知道了objects,classes,selectors,IMPs以及消息分发,那么运行时到底能做什么呢?主要有两个作用:

创建、修改、自省classes和objects

消息分发
之前已经提过消息分发,不过这只是一小部分功能。所有的运行时方法都有特定的前缀。下面是一些有意思的方法:

class

class开头的方法是用来修改和自省classes。方法如class_addIvar, class_addMethod, class_addProperty和class_addProtocol允许重建classes。class_copyIvarList, class_copyMethodList, class_copyProtocolList和class_copyPropertyList能拿到一个class的所有内容。而class_getClassMethod, class_getClassVariable, class_getInstanceMethod, class_getInstanceVariable, class_getMethodImplementation和class_getProperty返回单个内容。

也有一些通用的自省方法,如class_conformsToProtocol, class_respondsToSelector, class_getSuperclass。最后,你可以使用class_createInstance来创建一个object。

ivar

这些方法能让你得到名字,内存地址和Objective-C type encoding。

method

这些方法主要用来自省,比如method_getName, method_getImplementation, method_getReturnType等等。也有一些修改的方法,包括method_setImplementation和method_exchangeImplementations,这些我们后面会讲到。

objc

一旦拿到了object,你就可以对它做一些自省和修改。你可以get/set ivar, 使用object_copy和object_dispose来copy和free object的内存。最NB的不仅是拿到一个class,而是可以使用object_setClass来改变一个object的class。待会就能看到使用场景。

property

属性保存了很大一部分信息。除了拿到名字,你还可以使用property_getAttributes来发现property的更多信息,如返回值、是否为atomic、getter/setter名字、是否为dynamic、背后使用的ivar名字、是否为弱引用。

protocol

Protocols有点像classes,但是精简版的,运行时的方法是一样的。你可以获取method, property, protocol列表, 检查是否实现了其他的protocol。

sel

最后我们有一些方法可以处理 selectors,比如获取名字,注册一个selector等等。

现在我们对Objective-C的运行时有了大概的了解,来看看它们能做哪些有趣的事情。

Classes And Selectors From Strings

比较基础的一个动态特性是通过String来生成Classes和Selectors。Cocoa提供了NSClassFromString和NSSelectorFromString方法,使用起来很简单:

Class stringclass = NSClassFromString(@”NSString”);

于是我们就得到了一个string class。接下来:

NSString *myString = [stringclass stringWithString:@”Hello World”];

为什么要这么做呢?直接使用Class不是更方便?通常情况下是,但有些场景下这个方法会很有用。首先,可以得知是否存在某个class,NSClassFromString 会返回nil,如果运行时不存在该class的话。比如可以检查NSClassFromString(@”NSRegularExpression”)是否为nil来判断是否为iOS4.0+。

另一个使用场景是根据不同的输入返回不同的class或method。比如你在解析一些数据,每个数据项都有要解析的字符串以及自身的类型(String,Number,Array)。你可以在一个方法里搞定这些,也可以使用多个方法。其中一个方法是获取type,然后使用if来调用匹配的方法。另一种是根据type来生成一个selector,然后调用之。以下是两种实现方式:

  • (void)parseObject:(id)object {
    for (id data in object) {
    if ([[data type] isEqualToString:@”String”]) {
    [self parseString:[data value]];
    } else if ([[data type] isEqualToString:@”Number”]) {
    [self parseNumber:[data value]];
    } else if ([[data type] isEqualToString:@”Array”]) {
    [self parseArray:[data value]];
    }
    }
    }
  • (void)parseObjectDynamic:(id)object {
    for (id data in object) {
    [self performSelector:NSSelectorFromString([NSString stringWithFormat:@”parse%@:”, [data type]]) withObject:[data value]];
    }
    }
  • (void)parseString:(NSString *)aString {}
  • (void)parseNumber:(NSString *)aNumber {}
  • (void)parseArray:(NSString *)aArray {}
    可一看到,你可以把7行带if的代码变成1行。将来如果有新的类型,只需增加实现方法即可,而不用再去添加新的 else if。

Method Swizzling

之前我们讲过,方法由两个部分组成。Selector相当于一个方法的id;IMP是方法的实现。这样分开的一个便利之处是selector和IMP之间的对应关系可以被改变。比如一个 IMP 可以有多个 selectors 指向它。

而 Method Swizzling 可以交换两个方法的实现。或许你会问“什么情况下会需要这个呢?”。我们先来看下Objective-C中,两种扩展class的途径。首先是 subclassing。你可以重写某个方法,调用父类的实现,这也意味着你必须使用这个subclass的实例,但如果继承了某个Cocoa class,而Cocoa又返回了原先的class(比如 NSArray)。这种情况下,你会想添加一个方法到NSArray,也就是使用Category。99%的情况下这是OK的,但如果你重写了某个方法,就没有机会再调用原先的实现了。

Method Swizzling 可以搞定这个问题。你可以重写某个方法而不用继承,同时还可以调用原先的实现。通常的做法是在category中添加一个方法(当然也可以是一个全新的class)。可以通过method_exchangeImplementations这个运行时方法来交换实现。来看一个demo,这个demo演示了如何重写addObject:方法来纪录每一个新添加的对象。

import

转载地址:http://hifvi.baihongyu.com/

你可能感兴趣的文章
SQL Server触发器
查看>>
Xcode 常用插件(持久更新)
查看>>
iOS 中的 promise 模式
查看>>
android学习笔记-序
查看>>
Linux6.X图形界面如何打开终端以及如何将终端加入右键
查看>>
关于平衡积分卡
查看>>
Android sqlite数据库存取图片信息
查看>>
茹立云:推荐未来的价值要比搜索引擎大
查看>>
黑马程序员—面向接口编程的好处
查看>>
自己实现一个shell
查看>>
Django-数据库增查
查看>>
yum 安装报错:*epel: mirrors.aliyun.comError: xzcompressionnot available
查看>>
scala yield和文件的操作
查看>>
2016 - 1- 24 随便写写~~
查看>>
2016 - 1- 23 大文件下载
查看>>
使用JavaScript验证用户输入的是否为正整数
查看>>
property相关补充
查看>>
403 Forbidden client denied by server configuration[apache2, linux]
查看>>
shell脚本监控服务器状态
查看>>
在centos 6.9安装wordpress,浏览器不能访问问题
查看>>