动态语言

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. IO、FileInputStream、(二十)

    1.IO流概述及其分类 * 1.概念(什么是IO?) * IO流用来处理设备之间的数据传输 * Java对数据的操作是通过流的方式 * Java用于操作流的类都在IO包中 * 流按流向分为两种:输入流 ...

  2. jdk8新特性Stream

    Stream的方法描述与实例 1,filter  过滤 Person p1 = new Person(); p1.setName("P1"); p1.setAge(10); Per ...

  3. windwo访问linux文件夹方法

    windwo访问linux文件夹:是通过linux的samba来实现的: 安装samba需要安装samba-client.samba-common.smaba3个包. 一:安装rpm 现有一个服务器l ...

  4. Struts2的各种标签库

    1 在JSP中使用taglib编译指令导入标签库 <%@ taglib prefix="s" uri="/struts-tags" %> ----- ...

  5. linux--安装phpcurl扩展

    在UBUNTU中直接用APT包管理工具安装: apt-get install curl libcurl3 libcurl3-dev php5-curl 安装好后重启Apache服务器就行了,如果还是不 ...

  6. linux 问题一 apt-get install 被 lock

    问题: sudo apt-get install vim E: Could not get lock /var/lib/dpkg/lock - open (11: Resource temporari ...

  7. (4)javascript的运算符以及运算符的优先级

                                    运算符的使用方法 在javascript的程序中要完成各种各样的运算,是离不开运算符的. 在javascript中,按运算符类型可以分为 ...

  8. robotframework自动化系列:文本类型的下拉框

    对于下拉框定位和输入,这里主要遇到有两种类型的下拉选择. 其中一个类型是select-options格式,如图 这种方式的定位可以使用select from list by value或select ...

  9. poj 1723 Soldiers【中位数】By cellur925

    题目传送门 题目大意:平面上有n个士兵,给出每个士兵的坐标,求出使这些士兵站好所需要的最少移动步数.站好要求:所有士兵y相等,x相邻.即达到 (x,y), (x+1, y), (x+2,y)……的状态 ...

  10. multiprocessing多进程模块

    1 基本理解 python不支持真多线程,无法在单进程中调用多核cpu.对于cpu密集型任务,可以使用多进程.python会调用OS原生多进程,运行在多核上,以此提高运行速度. 2 基本实现 impo ...