动态语言

OC是一门不折不扣的动态语言,所以它的很多机制都是动态运行时决定的。这点和C语言不一样,C语言是静态绑定,也就是编译后所有的一切都已经决定了。这一点和C语言的函数指针有些类似,很多时候函数指针在编译的时候并不知道会指向哪个函数,所以此时就是动态绑定。

举几个OC动态类型的例子,最为直接的就是id类型了、还有关联对象、动态绑定、消息转发、方法调配、这些技术都是动态类型很好的证明

OC对象结构

在介绍动动态性之前,我们先来看看OC对象的一些结构。

#import<objc/runtime>这是OC运行时函数库,里面定义了很多结构体。

首先看对象的结构:

typdef struct objc_object {
Class isa;
} *id;

对象结构中非常简单,只有一个isa指针,isa指针后面我们会介绍。

接下来我们看类的结构体

struct objc_class {
Class isa #if !__OBJC2__
Class super_class
const char *name
long version
long info
long instance_size
struct objc_ivar_list *ivars
struct objc_method_list **methodLists
struct objc_cache *cache
struct objc_protocol_list *protocols
#endif }
typedef struct objc_class *Class;

可以看到很多信息都在Class中定义着,里面信息如下

字段 含义
isa isa指针
super_class 父类指针
name 类名
version 类的版本信息,默认为0
info 供运行期使用的一些位标识
instance_size 实例的大小
ivars 实例变量列表
methodLists 方法列表
cache 指向最近调用的方法,用于优化调用方法的速度
protocols 协议列表

接下来我们逐个介绍一下:

isa指针和super_class

在OC中,严格意义上讲是没有类这种概念的,每一个类都是一个对象,只不过类对象是一个单例。

isa指针存在于每一个对象中,类普通实例的isa指针指向类,类的isa指针指向它的元类(类方法全部都在元类中存放)。元类的isa指针指向根元类,也就是NSObject的isa所指向的元类。

super_class只有类和元类才有,它们分别指向自己的父类和父元类,而为了让NSObject成为所有类的根类,让NSObject的元类的父类指针也指向了NSObject。

这样说可能也不是很好理解,看下面这张图应该就很快理解了。

等下我们说到消息传递的时候还会在说到isa和super_class。

ivars属性列表

struct objc_ivar_list {
int ivar_count
#ifdef __LP64__
int space
#endif
/* variable length structure */
struct objc_ivar ivar_list[1]
}

space作用还不太清楚...求指教啊。

下面是实例变量结构

struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}

可以看到有一个ivar_offset,这个是实例变量在编译时的偏移量,是由编译时决定的。

methodLists方法列表

struct objc_method_list {
struct objc_method_list *obsolete int method_count
#ifdef __LP64__
int space
#endif
/* variable length structure */
struct objc_method method_list[1]
}

该结构有方法链表和方法总数。

struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}

里面有函数名,返回值类型和函数实现,接下来看看SEL和IMP定义

typedef struct objc_selector *SEL;
typedef id (*IMP)(id, SEL, ...);

可以看到SEL是objc_selector,(*IMP)(id,SEL,...)是id,我没有找到objc_selector的结构所以这里也没法说什么...

cache缓存列表

typedef struct objc_cache *Cache                            

#define CACHE_BUCKET_NAME(B)  ((B)->method_name)
#define CACHE_BUCKET_IMP(B) ((B)->method_imp)
#define CACHE_BUCKET_VALID(B) (B)
#ifndef __LP64__
#define CACHE_HASH(sel, mask) (((uintptr_t)(sel)>>2) & (mask))
#else
#define CACHE_HASH(sel, mask) (((unsigned int)((uintptr_t)(sel)>>3)) & (mask))
#endif
struct objc_cache {
unsigned int mask /* total = mask + 1 */
unsigned int occupied
Method buckets[1]
};

缓存列表里面包含了已缓存的方法,用于快速的调用,不需要在去方法列表里面查询了。

protocol协议列表

struct objc_protocol_list {
struct objc_protocol_list *next;
long count;
Protocol *list[1];
};

协议列表包含了协议数量和协议的指针。

id

id类型实际上就是一个objc_object的typedef,是一个对象实例,而且还是一个指针,所以用id来定义对象的时候就不需要加*号了。

id类型往往需要我们使用”自省“机制来保证使用安全,所谓自省其实就是看看这个对象是不是某个类的实例,或者是不是其子类。

自省用一下两个方法:

isKindOfClass:(Class)class          判断是不是其类族对象

isMemberOfClass:(Class)class     判断是不是类本身对象

关联对象

关联对象在我之前的博客中已经有介绍了,这里就不再说了。

OC消息机制和消息转发

别的语言调用函数,OC则叫做发送消息,这是因为所有OC的方法调用实际上底层都是通过

objc_msgSend(id self , SEL cmd,...)来发送的。

该函数的作用就是传递给一个对象某个方法,后面的不定参数列表是方法所需要的参数。

这里说一下OC的消息传递机制,首先对一个对象发送消息,它会先检查自己的类中有没有该方法,如果没有就找他的父类中有没有,如果还没有则会进行消息转发。

在看例子之前先说一下,Xcode6貌似默认行为不让我们使用objc_msgSend了,所以需要先设置一下

把这一项设置为No就可以了。

这里看个例子,

        EqualObject *object1 = [EqualObject new];
EqualObject *object2 = [EqualObject new];
object1.name = @"xiaoming";
object2.name = @"xiaoming"; BOOL isEqual = objc_msgSend(object1,@selector(isEqualToEqualObject:),object2); if(isEqual)
{
NSLog(@"equal");
}

EqualObject是我们自己实现的类,它有一个判断是否相等的方法isEqualToEqualObject:,如果name相等就人为两个对象相等。

这里我们直接传递消息,不通过OC语法,运行程序可以看到equal被打印了出来。

然后我们再看看消息转发机制。

当该对象包括其父类都没有这个方法的时候会启动,消息转发机制分为两大阶段。

第一阶段先看对象所属类是否有能力动态添加方法,已处理这个位置的选择子,这叫做动态解析(dynamic method resolution)。

第二阶段设计“完整的消息转发机制”。如果运行期系统已经把第一阶段执行完了,那么接受者自己就没法再以动态新增方法的手段来处理与消息相关的方法调用。这又分为两个小步。

首先,请接受者看看有没有其他对象能处理这条消息。若有,则在运行时转给那个对象,于是消息转发过程结束。若没有“备用的接受者”,则启动完成的消息转发机制,运行起系统会把与消息有关的全部细节都封装到NSInvocation对象中,再给接受者最后一次机会,令其设法解决当前还未处理的这条消息。

动态方法解析

void showLog(id self, SEL _cmd, id value)
{
if([value isKindOfClass:[NSString class]])
{
NSLog(@"%@",(NSString *)value);
}
} +(BOOL)resolveInstanceLog:(SEL)sel
{
NSString *selString = NSStringFromSelector(sel);
if([selString isEqualToString:@"showMessage:"])
{
class_addMethod(self, sel, (IMP)showMessage, "v@:@");
return YES;
}
else
{
return [super resolveInstanceMethod:sel];
}
}

遇到无法解析的信息后,首先将调用其所属类的下列类方法:
+(BOOL)resolveInstanceMethod:(SEL)selector

该方法参数就是未知的选择子,返回BOOL类型那个,表示这个类是否能新增一个实力方法已处理这个选择子。假如是类方法,那么会调用

+(BOOL)resolveClassMethod:(SEL)selector

使用这种方法的前提是相关的实现已经写好了,只等运行时动态的插入就行,比如CoreData中NSManagedObjects对象的属性时就可以这么做,因为实现这些属性所需的存取方法在编译期就能确定。

下面我们看个例子

Runtime理解的更多相关文章

  1. iOS --runtime理解

    iOS~runtime理解 Runtime是想要做好iOS开发,或者说是真正的深刻的掌握OC这门语言所必需理解的东西.最近在学习Runtime,有自己的一些心得,整理如下,一为 查阅方便二为 或许能给 ...

  2. Objective-C——Runtime理解

    动态语言 OC是一门不折不扣的动态语言,所以它的很多机制都是动态运行时决定的.这点和C语言不一样,C语言是静态绑定,也就是编译后所有的一切都已经决定了.这一点和C语言的函数指针有些类似,很多时候函数指 ...

  3. runtime 理解笔记

    runtime 简称运行时,是系统运行的一种机制,在oc中通过c语言编写一个运行系统库.考进行一些非常底层的操作(oc无法完成的). 1.利用runtime,在程序运行过程中,动态创建一个类(比如KV ...

  4. 函数调用和给对象发消息(Runtime理解)

    在写代码的时候这个差距其实是不打看的出得,很多时候也就无所谓叫什么,很多人为了便于理解,干脆就叫函数调用.这个其实应该是oc的一个特色,消息发送.具体的类typedef struct objc_cla ...

  5. Runtime系列(二)--Runtime的使用场景

    Runtime 理解介绍的文章非常多,我只想讲讲Runtime 可以用在哪里,而我在项目里哪些地方用到了runtime.多以实际使用过程为主,来介绍runtime的使用. * 那么runtime 怎么 ...

  6. iOS书摘之编写高质量iOS与OS X代码的52个有效方法

    来自<Effective Objective-C 2.0编写高质量iOS与OS X代码的52个有效方法>一书的摘要总结 一.熟悉Objective-C 了解Objective-C语言的起源 ...

  7. Aspects 源码学习

    AOP 面向切面编程,在对于埋点.日志记录等操作来说是一个很好的解决方案.而 Aspects 是一个对于AOP编程的一个优雅的实现,也可以直接借助这个库来使用AOP思想.需要值得注意的是,Aspect ...

  8. iOS 开源库系列 Aspects核心源码分析---面向切面编程之疯狂的 Aspects

    Aspects的源码学习,我学到的有几下几点 Objective-C Runtime 理解OC的消息分发机制 KVO中的指针交换技术 Block 在内存中的数据结构 const 的修饰区别 block ...

  9. iOS runtime的理解和应用

    项目中经常会有一些的功能模块用到runtime,最近也在学习它.对于要不要阅读runtime的源码,我觉得仅仅是处理正常的开发,那真的没有必要,只要把常用的一些函数看下和原理理解下就可以了. 但是如果 ...

随机推荐

  1. hadoop源码剖析--$HADOOP_HOME/bin/hadoop脚本文件分析

    1. $HADOOP_HOME/bin/ hadoop #!/usr/bin/env bash# Licensed to the Apache Software Foundation (ASF) un ...

  2. netty codec部分剖析

    针对netty 3.2进行剖析 今天用到了netty的encoder和decoder(coder其本质还是handler),特剖析一个netty提供的coder,从而选择或者实现我自己的coder. ...

  3. JS截取与分割字符串常用技巧总结

    本文实例讲述了JS截取与分割字符串的常用方法.分享给大家供大家参考,具体如下: JS截取字符串可使用 substring()或者slice() 函数:substring() 定义:substring( ...

  4. noip2010引水入城

    https://www.zybuluo.com/ysner/note/1334997 这道题fst了 题面 戳我 解析 我一开始的想法是,按照高度给第一行排序,然后贪心地选取目前到不了的,高度最高的第 ...

  5. 【转】Chrome调试鼠标悬停后出现的元素

    原文地址:https://blog.csdn.net/sparrowflying/article/details/80996550 调试小技巧:调试样式的时候,有一类元素是鼠标悬停在特定位置才会出现的 ...

  6. usb2.0与usb3.0的区分

    USB 2.0 USB2.0技术规范是有由Compaq.Hewlett Packard.Intel.Lucent.Microsoft.NEC.Philips共同制定.发布的,规范把外设数据传输速度提高 ...

  7. 容器之vector

    #include <iostream> #include <vector> #include <string.h> #include <algorithm&g ...

  8. TRACE 学习

    TRACE   宏有点象我们以前在C语言中用的Printf函数,使程序在运行过程中输出一些调试信息,使我们能了解程序的一些状态.在Output中可以查看到结果. 但有一点不同的是:TRACE   宏只 ...

  9. 斯坦福CS231n—深度学习与计算机视觉----学习笔记 课时6

    课时6 线性分类器损失函数与最优化(上) 多类SVM损失:这是一个两分类支持向量机的泛化 SVM损失计算了所有不正确的例子,将所有不正确的类别的评分,与正确类别的评分之差加1,将得到的数值与0作比较, ...

  10. JAVA基础--异常10

    一.Object类简介 1.Object类简介 Object,是Java一切对象的最高父类:如果一个类没有显示继承一个类,他的父类就是Object: 它描述的是Java世界里所有对象最最共性的信息,它 ...