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

  1. 1 [array insertObject:foo atIndex:5];
  2. 2 objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);

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

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

  1. 1 - (id)doSomethingWithInt:(int)aInt{}
  2. 2 id doSomethingWithInt(id self, SEL _cmd, int aInt){}

objc中存在一些用于修改和自省的方法,这些方法差不多都是以特定的前缀开头,如

  1. class_addIvar, class_addMethod,class_addPropertyclass_addProtocol

允许重建classes,

  1. class_copyIvarList,class_copyMethodList, class_copyProtocolListclass_copyPropertyList

能拿到一个class的所有内容,而

  1. class_getClassMethod, class_getClassVariable, class_getInstanceMethod,class_getInstanceVariable, class_getMethodImplementationclass_getProperty

返回单个内容。也有些用于自省的方法,如

  1. class_conformsToProtocol, class_respondsToSelector,class_getSuperclass

最后,你可以使用class_createInstance来创建一个object。

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

  1. 1 Class stringclass = NSClassFromString(@"NSString");
  2. 2 NSString *myString = [stringclass stringWithString:@"Hello World"];

为什么要这么做呢,直接使用class不是更方便,通常情况下,但有些场景有会很能有用,首先可以得知某个class是否存在,因为不存在的话NSClassFromString会返回nil,用于检查。另一个常见的场景是根据不同的输入返回不同的class跟method。在解析数据的时候,下面是一个例子

  1. - (void)parseObject:(id)object {
  2. for (id data in object) {
  3. if ([[data type] isEqualToString:@"String"]) {
  4. [self parseString:[data value]];
  5. } else if ([[data type] isEqualToString:@"Number"]) {
  6. [self parseNumber:[data value]];
  7. } else if ([[data type] isEqualToString:@"Array"]) {
  8. [self parseArray:[data value]];
  9. }
  10. }
  11. }
  12. - (void)parseObjectDynamic:(id)object {
  13. for (id data in object) {
  14. [self performSelector:NSSelectorFromString([NSString stringWithFormat:@"parse%@:", [data type]]) withObject:[data value]];
  15. }
  16. }
  17. - (void)parseString:(NSString *)aString {}
  18. - (void)parseNumber:(NSString *)aNumber {}
  19. - (void)parseArray:(NSString *)aArray {}

可以看到用了后者的话可以把代码行数降下来,将来如果有新的类型,只需要增加实现方法即可,而不用去添加新的else if.

在objc中,方法由两部分组成,selector相当于一个方法的id,IMP是方法的实现,这样分开的一个便利之处就是selector和IMP之间的对应关系可以被改变。这就是Method Swizzling的存在处,交换两个方法的实现,下面是代码实现:

  1. 1 void MethodSwizzle(Class aClass, SEL orig_sel, SEL alt_sel){
  2. 2 Method orig_method = nil, alt_method = nil;
  3. 3 // First, look for the methods
  4. 4 orig_method = class_getInstanceMethod(aClass, orig_sel);
  5. 5 alt_method = class_getInstanceMethod(aClass, alt_sel);
  6. 6 // If both are found, swizzle them
  7. 7 if ((orig_method != nil) && (alt_method != nil)){
  8. 8 char *temp1;
  9. 9 IMP temp2;
  10. 10 temp1 = orig_method->method_types;
  11. 11 orig_method->method_types = alt_method->method_types;
  12. 12 alt_method->method_types = temp1;
  13. 13 temp2 = orig_method->method_imp;
  14. 14 orig_method->method_imp = alt_method->method_imp;
  15. 15 alt_method->method_imp = temp2;
  16. 16 }
    17 }

当然,上面的第八行开始到十五行之间的代码可以用如下进行替换。

  1. method_exchangeImplementations(orig_method,alt_method)

上面我们谈到了方法交换,但是当你发送了一个object无法处理的消息时会发生什么呢?这里首先会是动态方法处理

  1. 1 resolveInstanceMethod && resolveClassMethod

在这两个重写的地方运用class_addMethod,同时记得返回YES,下面是一个例子

  1. 1 + (BOOL)resolveInstanceMethod:(SEL)aSelector {
  2. 2 if (aSelector == @selector(myDynamicMethod)) {
  3. 3 class_addMethod(self, aSelector, (IMP)myDynamicIMP, "v@:");
  4. 4 return YES;
  5. 5 }
  6. 6 return [super resolveInstanceMethod:aSelector];
  7. 7 }

如果resolve method返回了NO,那么运行时就进入下一个步骤--消息转发。首先会调用-forwardingTargetForSelector:,如果只是把消息发送到另一个object,那么就用这个方法,但是如果你想修改消息,那么就要使用-forwardInvocation:,将消息打包成NSInvocation,调用invokeWithTarget:

整个文章下来,可以看到objc表面看起来跟c#,java等语言在方法调用上没什么区别,但最关键的是objc的运行时消息处理,我们可以在消息处理上添加更多的自由,其优势在于在不扩展语言本身的情况下做很多事,比如KVO,提供了优雅的API来与已有的代码进行无疑结合。

下面就结合运行时来谈谈KVO的内部真正实现。当你第一次观察某个object时,runtime会创建一个新的继承原先class的subclass。在这个新的class中,它重写了所有被观察的key,然后将object的isa指针指向新创建的class(这个指针告诉Objective-C运行时某个object到底是哪种类型的object)。所以object神奇地变成了新的子类的实例。

objc语言的运行时处理的更多相关文章

  1. Swift和OC,是编译型语言、解释性语言、运行时语言

    首先需要明确的一点是,什么是编译型语言和解释性语言 编译型语言,就是在其执行过程中需要先将其经过编译成机器码来给计算机识别的,其执行效率就会比较高这个是显而易见的,常见比如:C.C++ 而解释型语言, ...

  2. C语言程序运行时的一些细节

    本章可以看作是 <Unix 环境高级编程>Ch7 的笔记. C 程序运行的开始和结束 一个可以运行的 C 语言总要有一个 main 函数,main 函数现在的完整定义是 int main( ...

  3. Swift运行时简介

    因为Swift的操作在高层并且也得与Objc联合起来干活,用Swift写的程序一般会被Objc和Swift运行时处理.因为Swift的本性--换句话说,它是一门静态语言--Swift运行时在一些关键地 ...

  4. 读书笔记-浅析Java运行时数据区

    作为一个 Java 为主语言的程序员,我偶尔也需要 用 C/C++ 写程序,在使用时让我很烦恼的一件事情就是需要对 new 出来的对象进行 delete/free 操作,我老是担心忘了这件事情,从而导 ...

  5. MFC原理第三讲.RTTI运行时类型识别

    MFC原理第三讲.RTTI运行时类型识别 一丶什么是RTTI RTTI. 运行时的时候类型的识别. 运行时类型信息程序.能够使用基类(父类)指针 或者引用 来检查这些指针或者引用所指的对象. 实际派生 ...

  6. 由objC运行时所想到的。。。

    objC语言不仅仅有着面向对象的特点(封装,继承和多态),也拥有类似脚本语言的灵活(运行时),这让objC有着很多奇特的功能-可在运行时添加给类或对象添加方法,甚至可以添加类方法,甚至可以动态创建类. ...

  7. ObjC运行时部分概念解析(二)

    上篇文章简单的说明了两个关键字究竟是什么,这里主要讲讲ObjC中各种基本内存模型 Method typedef struct objc_method *Method; struct objc_meth ...

  8. ObjC运行时部分概念解析(一)

    转型iOS已经许久了,Runtime(运行时)还没有好好了解过.之前没有阅读过源码,紧紧凭借自己的臆测.现在阅读下源码,做一些笔记.方便再次翻阅 SEL SEL是一个关键字,如果没有涉及runtime ...

  9. [Asp.net 5] Localization-Asp.net运行时多语言

    本节介绍的是Microsoft.AspNet.Localization工程.该工程是运行在Asp.net 5环境中的运行时多语言设置. ASP.net 5中间件技术 在新的Asp.net 5中,可以将 ...

随机推荐

  1. 复选框回显、全选、非全选、cookie处理数据、json数组对象转换处理学习笔记参考的页面

    <%@include file="/common/head.jsp"%> <%@ page contentType="text/html; charse ...

  2. 4.flask数据库

    1.安装MySQL 直接去下载即可,如果是windows建可以下载msi,一路next即可.我已经安装过了,这里就不再演示了. 最后使用Navicat连接测试一下,我这里是没有问题的 2.SQLAlc ...

  3. Spring--Quartz 任务调度的配置详解

    Quartz是一个强大的企业级任务调度框架,Spring中继承并简化了Quartz,下面就看看在Spring中怎样配置Quartz:首先我们来写一个被调度的类: package com.kay.qua ...

  4. Yii2中如何访问controller的二级目录下的控制器

    Yii2默认的控制器下只有一层目录放在controller目录下,当开发的需要的时候需要在该目录下面新增一层目录也就是在controller下增加二级目录,比如controller/api/Membe ...

  5. IIS-CS0016未能写入文件错误解决方法

    使用IIS创建Workbench站点,运行在本地 Workbench主页显示错误,这个提示的错误信息其实是有误导人的,真的打开至c:\Windows\Microsoft.NET\Framework64 ...

  6. NYOJ 6.喷水装置(一)-贪心

    喷水装置(一) 时间限制:3000 ms  |  内存限制:65535 KB 难度:3   描述 现有一块草坪,长为20米,宽为2米,要在横中心线上放置半径为Ri的喷水装置,每个喷水装置的效果都会让以 ...

  7. RASscan

    内网端口极速扫描器 软件下载地址:https://github.com/RASSec/RASscan 命令: Python Rasscan.py 网络第一个ip 网络最后一个ip

  8. [JSOI2008]Star War

    星球之间互相直接或间接地连接帝国开始使用死星有计划地摧毁反抗军占领的星球给出星球间隧道的连通情况,已经帝国打击的顺序要求以尽量快的速度求出每一次打击之后反抗军占据的星球的联通快的个数(若两个星球,直接 ...

  9. DP 贪心【p2134】百日旅行

    Background 重要的不是去哪里,而是和你在一起.--小红 对小明和小红来说,2014年7月29日是一个美好的日子.这一天是他们相识100天的纪念日. (小明:小红,感谢你2场大考时默默的支持, ...

  10. Column 1 of table 'xxx' cannot be converted from type 'varchar(33)' to type 'varchar(11)'

    mysql主从同步失败.错误日志如下. Column 1 of table 'xxx' cannot be converted from type 'varchar(33)' to type 'var ...