Objective-C runtime杂谈

.老规矩:首先甩出官方文档:
https://developer.apple.com/library/prerelease/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008048-CH1-SW1
1:runtime简介
Objective-C是一种动态语言,它将编译和链接时做的是,推迟到运行时进行,只要有可能,他就会动态的执行任务,这意味着语言需要的不仅仅是一个编译器,但也一个运行时系统来执行编译后的代码。这个系统其实就类似于电脑的操作系统一样,

其实运行时也分版本的,在不同的平台上,运行着不同的版本:“modern”和 “legacy”。
1:modern runtime:我们现在用的 Objective-C 2.0 采用的是现行(Modern)版的Runtime系统,只能运行在 iOS 和 OS X 10.5 之后的64位程序中。
2:legacy runtime:OS X较老的32位程序仍采用 Objective-C 1中的(早期)Legacy 版本的 Runtime 系统。

这两个版本最大的区别在于当你更改一个类的实例变量的布局时,在早期版本中你需要重新编译它的子类,而现行版就不需要.所以我们只需要关注modern版本的runtime即可


2运行时交互
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtInteracting.html#//apple_ref/doc/uid/TP40008048-CH103-SW1
OC程序与运行时通过三个不同的层次进行交互:

1:通过Objective-C源代码
2:通过Foundation框架中NSObject类定义的方法
3:直接调用运行时开放的API

源代码
在大多数情况下,你使用它只是通过编写和编译Objective-C的源代码,运行时系统会自动的在后台工作.当你编译代码包含Objective-C的类和方法,编译器创建数据结构和函数调用,实现语言的动态特性。

通过Foundation框架中NSObject类定义的方法
Cocoa程序中,绝大部分的类都是NSObject的子类,所以大多数的类都继承了NSObject定义的方法,(NSProxy是个例外,他是个抽象超类).然而在少数情况下,NSObject类只定义了一个模板应该怎么做,他不提供所有必要的代码本身,

例如:

  • -description 方法,该方法返回类内容的字符串表示,该方法主要用来调试程序。NSObject 类并不知道子类的内容,所以它只是返回类的名字和对象的地址,NSObject 的子类可以重新实现。

还有一些 NSObject 的方法可以从 Runtime 系统中获取信息,允许对象进行自我检查。例如:

  1. class方法返回对象的类;
  2. isKindOfClass: 和 isMemberOfClass: 方法检查对象是否存在于指定的类的继承体系中(是否是其子类或者父类或者当前类的成员变量);
  3. respondsToSelector: 检查对象能否响应指定的消息;
  4. conformsToProtocol:检查对象是否实现了指定协议类的方法;
  5. methodForSelector: 返回指定方法实现的地址。

直接调用runtime的公共API
运行时系统是一个动态共享库和一个公共接口组成的头文件位于/usr/include/objc的一组函数和数据结构.这些函数允许您使用纯C语言复制你用OC写的代码让编译器做的事(即C实现OC的功能)


3:Messaging
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtHowMessagingWorks.html#//apple_ref/doc/uid/TP40008048-CH104-SW1

3.1objc_msgSend函数
在OC中,消息直到运行时才绑定到方法的实现

[receiver message]

编译器会将上述方法的调用转换成

objc_msgSend(receiver, selector)

这个函数需要接收者和消息中提到的方法的名称.作为他的两个主要的参数,

objc_msgSend(receiver, selector, arg1, arg2, ...)

任何其他参数传入的消息也交给objc_msgSend

消息传递函数做了动态绑定所需要的一切事情。

1.它首先发现这个选择器指向的实现(method implementation),因为相同的方法可以被单独的类不同的实现(即:很多不同的类,可以有一个方法名相同的方法);这个精确的实现取决于方法接受者的类。

2.然后将消息接收者对象(指向消息接收者对象的指针)以及方法中指定的参数传递给方法实现 IMP。

3.最后,他将这个方法实现的返回值作为自己的返回值

注意:这个编译器生成的调用消息传递的函数,你绝不应该在你写的代码中直接调用

消息传递的关键在于编译器为每个类和对象构建的结构体。每个类的结构体包括两个基本要素:
1:一个指向父类的指针
2:一个类的分派表,

当一个新的对象被创建的时候,内存为它分配存储空间,它的实例变量也被初始化了,这个对象的第一个实例变量是一个指向它的类的指针,这个指针叫isa指针

注意:虽然isa严格意义上来说不是语言的一部分,但是却是运行时系统所必须的,你很少需要自己创建根对象,对象实例化自NSObject和NSProxy,并且自动有一个isa的成员变量

这些对象和类的结构的元素如下图:

消息转发的结构图

当一个消息发送给一个对象的时候,这个消息的函数跟随着对象的isa指针指向的类的结构体,去寻找方法的实现在类的分派表中,如果找不到选择器,那么就去它的父类中去寻找方法选择器,以此类推,直到NSObject类.一旦定位了选择器,这个函数调用这个方法进入分派表中,并且传递返回对象数据结构.方法的实现在运行时才被选中,或者面向对象编程的术语里面,方法是动态绑定到消息的.
为了加速消息的传递,每个类的结构体中,都有一个Cache成员变量,当方法被调用一次之后,会被加入到Cache,下次再调用的时候,直接从Cache中查找,不用再去遍历MethodList了,这样就加快了方法的执行速度.

3.2:隐藏参数的使用
当objc_msgSend找到了方法的实现的时候,它调用方法的实现并传递消息中所有的参数,也包括了两个隐藏的参数
1:接受对象
2:方法的选择器
eg:

objc_msgSend(receiver, selector)

我们经常在方法中使用self关键字来引用实例本身,但从没有想过为什么self就能取到调用当前方法的对象吧。其实self的内容是在方法运行时被偷偷的动态传入的。

当objc_msgSend找到方法对应的实现时,它将直接调用该方法实现,并将消息中所有的参数都传递给方法实现,同时,它还将传递两个隐藏的参数:

接收消息的对象(也就是self指向的内容)
方法选择器(_cmd指向的内容)
之所以说它们是隐藏的是因为在源代码方法的定义中并没有声明这两个参数。它们是在代码被编译时被插入实现中的。尽管这些参数没有被明确声明,在源代码中我们仍然可以引用它们。在下面的例子中,self引用了接收者对象,而_cmd引用了方法本身的选择器:

- strange
{
    id  target = getTheReceiver();
    SEL method = getTheMethod();

    if ( target == self || method == _cmd )
        return nil;
    return [target performSelector:method];
    }

在这两个参数中,self 更有用。实际上,它是在方法实现中访问消息接收者对象的实例变量的途径。

而当方法中的super关键字接收到消息时,编译器会创建一个objc_super结构体:

struct objc_super { id receiver; Class class; };

这个结构体指明了消息应该被传递给特定超类的定义。但receiver仍然是self本身,这点需要注意,因为当我们想通过[super class]获取超类时,编译器只是将指向self的id指针和class的SEL传递给了objc_msgSendSuper函数,因为只有在NSObject类才能找到class方法,然后class方法调用object_getClass(),接着调用objc_msgSend(objc_super->receiver, @selector(class)),传入的第一个参数是指向self的id指针,与调用[self class]相同,所以我们得到的永远都是self的类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值