ios rumtime 1- 类与对象

本文是参考南峰子Objective-C Runtime系列文章
做一个自己的总结,强烈推荐查看原文

1、runtime介绍

Objective-C 是一门动态语言,它将很多静态语言在编译和连接时期做的事情放到了运行时来处理,这种动态语言的优势在于:我们写代码时更具有灵活性,如我们可以把消息转发给我们想要转发的对象,或者随意交换一个方法等。

这种特性意味着Objective-C不仅需要一个编译器,还需要一个运行时动态库来执行编译的代码。对于Objective-C来说,这个运行时系统就像一个操作系统一样:让所有工作正常运行。这个运行时系统就是Objc RuntimeObjc Runtime其实是一个Runtime库,基本上是用C语言汇编语言写的,使得C语言有了面向对象的能力。

Runtime库主要做了以下两件事:

  1. 封装:在这个库中,对象可以使用C语言中的结构体表示,方法可以使用C函数来实现,另外再加上一些额外的特性,这些结构体和函数被runtime函数封装后,我们就可以在程序运行时创建、检查、修改类、对象、方法了
  2. 找出方法的最终执行代码:当程序执行[object doSomething]时,会向接受者(object)发送一条消息(doSomething),runtime会根据消息接受者能否响应该消息而做出不同反应

Objective-C runtime目前有两个版本:Modern runtimeLegacy runtimeModern Runtime覆盖了64位的Mac OS X Apps,还有iOS AppsLegacy Runtime是早期用来给32位 Mac OS X Apps 用的,已经过时

苹果和GNU各自维护一个开源的 runtime 版本,这两个版本之间都在努力的保持一致。

高级编程语言想要成为可执行文件,需要先编译成汇编语言,再汇编为机器语言,机器语言是计算机唯一能识别的语言(转换过程待研究)

2、类

Objective-C类是有Class类型表示,实际上是指向objc_class结构体的指针,定义如下

typedef struct objc_class *Class;

objc_class定义如下

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                       OBJC2_UNAVAILABLE;  // 父类
    const char *name                        OBJC2_UNAVAILABLE;  // 类名
    long version                            OBJC2_UNAVAILABLE;  // 类的版本信息,默认为0
    long info                               OBJC2_UNAVAILABLE;  // 类信息,供运行期使用的一些位标识
    long instance_size                      OBJC2_UNAVAILABLE;  // 该类的实例变量大小
    struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 该类的成员变量链表
    struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定义的链表
    struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法缓存
    struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 协议链表
#endif
} OBJC2_UNAVAILABLE;

1.isa : 需要注意的是在Objective-C中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass(元类)

  1. super_class :指向该类的父类,如果该类已经是最顶层的根类(如NSObjectNSProxy),则super_classNULL

  2. name : 类名

  3. version :我们可以使用这个字段来提供类的版本信息。这对于对象的序列化非常有用,它可是让我们识别出不同类定义版本中实例变量布局的改变。

  4. info : 类信息

  5. instance_size : 该类的对象大小

  6. ivars : 成员变量的列表

  7. methodLists : 方法列表

  8. cathe : 用于缓存最近使用的方法。
    一个接收者对象接收到一个消息时,它会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,提高了调用的效率。

  9. protocols : 协议列表

3、对象

objc_object是表示一个类的实例的结构体

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

这个结构体只有一个属性,即指向其类的isa指针。这样,当我们向一个Objective-C对象发送消息时,运行时库会根据实例对象的isa指针找到这个实例对象所属的类。Runtime库会在类的方法列表及父类的方法列表中去寻找与消息对应的selector指向的方法,找到后即运行这个方法。

4、元类(Meta Class)

meta-class是一个类对象的类

当我们向一个对象发送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法;
而向一个类发送消息时,会在这个类的meta-class的方法列表中查找方法

meta-class之所以重要,是因为它存储着一个类的所有类方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同

meta-class也是一个类,也可以向它发送一个消息,那么它的isa又是指向什么呢?为了不让这种结构无限延伸下去,Objective-C的设计者让所有的meta-classisa指向基类的meta-class,以此作为它们的所属类。
即,任何NSObject继承体系下的meta-class都使用NSObjectmeta-class作为自己的所属类,而基类的meta-classisa指针是指向它自己。这样就形成了一个完美的闭环。

Class和meta-Class.png

对于NSObject继承体系来说,其实例方法对体系中的所有实例、类和meta-class都是有效的;而类方法对于体系内的所有类和meta-class都是有效的。

5、objc_cache

cathe用于缓存调用过的方法。这个字段是一个指向objc_cache结构体的指针,其定义如下:

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method _Nullable buckets[1]                              OBJC2_UNAVAILABLE;
};
  1. mask:一个整数,指定分配的缓存bucket的总数。在方法查找过程中,runtime使用这个字段来确定开始线性查找数组的索引位置。指向方法selector的指针与该字段做一个AND位操作(index = (mask & selector))。这可以作为一个简单的hash散列算法。
  2. occupied:一个整数,指定实际占用的缓存bucket的总数。
  3. buckets:指向Method数据结构指针的数组。这个数组可能包含不超过mask+1个元素。需要注意的是,指针可能是NULL,表示这个缓存bucket没有被占用,另外被占用的bucket可能是不连续的。这个数组可能会随着时间而增长。

6、类与对象的操作函数

类的操作方法大部分是以class_为前缀的
对象的操作方法大部分是以objc_object_为前缀

runtime.h里面搜索Working with ,一共有8个搜索结果,搜索顺序分别是:

  1. Working with Instances
  2. Working with Classes
  3. Working with Methods
  4. Working with Instance Variables
  5. Working with Properties
  6. Working with Protocols
  7. Working with Libraries
  8. Working with Selectors

这里我们主要看Working with ClassesWorking with InstancesWorking with Instance Variables里面的方法

6.1、动态创建类

//创建一个新类和元类
Class objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, 
                       size_t extraBytes) ;

// 在应用中注册由objc_allocateClassPair创建的类
void objc_registerClassPair(Class _Nonnull cls) ;

//销毁一个类极其相关类
Class objc_duplicateClass(Class _Nonnull original, const char * _Nonnull name,
                    size_t extraBytes);

  1. objc_allocateClassPair函数:如果我们要创建一个根类,则superclass指定为NilextraBytes通常指定为0,该参数是分配给类和元类对象尾部的索引ivars的字节数。

  2. objc_disposeClassPair函数用于销毁一个类,不过需要注意的是,如果程序运行中还存在类或其子类的实例,则不能调用针对类调用该方法。

创建一个新类,先用objc_allocateClassPair,调用class_addMethodclass_addIvar等函数来为新创建的类添加方法、实例变量和属性等。再调用objc_registerClassPair函数来注册类,之后这个新类就可以在程序中使用了。

实例方法和实例变量应该添加到类自身上,而类方法应该添加到类的元类上

6.2、动态创建对象

// 创建类实例
id class_createInstance ( Class cls, size_t extraBytes );
// 在指定位置创建类实例
id objc_constructInstance ( Class cls, void *bytes );
// 销毁类实例
void * objc_destructInstance ( id obj );
  1. class_createInstance :创建实例时,会在默认的内存区域为类分配内存。extraBytes额外的字节可以用来存储类定义中定义之外的其他实例变量.

  2. objc_constructInstance :在指定的位置(bytes)创建类实例。

  3. objc_destructInstance:销毁一个类的实例,但不会释放并移除任何与其相关的引用。

类和对象的相关操作函数

// 返回指定对象的一份拷贝
id object_copy ( id obj, size_t size );

// 释放指定对象占用的内存
id object_dispose ( id obj );

// 修改类实例的实例变量的值
Ivar object_setInstanceVariable ( id obj, const char *name, void *value );

// 获取对象实例变量的值
Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );

// 返回指向给定对象分配的任何额外字节的指针
void * object_getIndexedIvars ( id obj );

// 返回对象中实例变量的值
id object_getIvar ( id obj, Ivar ivar );

// 设置对象中实例变量的值
void object_setIvar ( id obj, Ivar ivar, id value );

// 返回给定对象的类名
const char * object_getClassName ( id obj );

// 返回对象的类
Class object_getClass ( id obj );

// 设置对象的类
Class object_setClass ( id obj, Class cls );

// 获取已注册的类定义的列表
int objc_getClassList ( Class *buffer, int bufferCount );

// 创建并返回一个指向所有已注册类的指针列表
Class * objc_copyClassList ( unsigned int *outCount );

// 返回指定类的类定义
Class objc_lookUpClass ( const char *name );
Class objc_getClass ( const char *name );
Class objc_getRequiredClass ( const char *name );

// 返回指定类的元类
Class objc_getMetaClass ( const char *name );

6.3、父类(super_class)和元类(meta-class)

// 获取类的父类
Class class_getSuperclass (Class _Nullable cls);

// 判断给定的Class是否是一个元类
BOOL class_isMetaClass (Class _Nullable cls);

6.4、类名

// 获取类的类名
const char * class_getName (Class _Nullable cls);

6.5、版本号

// 获取版本号
int class_getVersion (Class _Nullable cls) ;

// 设置版本号
void class_setVersion (Class _Nullable cls, int version);

6.6、实例大小

// 获取实例大小
size_t class_getInstanceSize (Class _Nullable cls);

6.7 成员变量

// 获取类中指定名称实例成员变量的信息
Ivar class_getInstanceVariable (Class _Nullable cls, const char * _Nonnull name); 

// 获取类成员变量的信息
Ivar class_getClassVariable(Class _Nullable cls, const char * _Nonnull name);

// 添加成员变量
BOOL class_addIvar (Class _Nullable cls, const char * _Nonnull name, size_t size, 
              uint8_t alignment, const char * _Nullable types) ;

// 获取整个成员变量列表
Ivar * class_copyIvarList (Class _Nullable cls, unsigned int * _Nullable outCount) ;
  1. class_getInstanceVariable: 根据name返回指定的对象成员变量信息(objc_ivar结构体)
  1. class_getClassVariable: 根据name返回指定的类成员变量信息(objc_ivar结构体),一般认为Objective-C不支持类变量

  2. class_addIvar : 参数分别是,成员变量所属类、成员变量名、对齐方式、成员变量类型

Objective-C不支持往已存在的类中添加实例成员变量,因此不管是系统库提供的提供的类,还是我们自定义的类,都无法动态添加成员变量,但是动态创建类的可以添加成员变量,只能在objc_allocateClassPairobjc_registerClassPair之间

如添加一个NSString类型的ivar1变量

class_addIvar(cls, "ivar1", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
  1. class_copyIvarList : 它返回一个指向成员变量信息的数组,数组中每个元素是指向该成员变量信息的objc_ivar结构体的指针,outCount指针返回数组的大小

注意 : 返回的列表不包含父类的成员变量和属性,必须使用free()来释放这个数组

6.8 属性

// 获取指定的属性
objc_property_t class_getProperty(Class _Nullable cls, const char * _Nonnull name);

// 获取属性列表
objc_property_t  class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount);

// 为类添加属性
BOOL class_addProperty(Class _Nullable cls, const char * _Nonnull name,
                  const objc_property_attribute_t * _Nullable attributes,
                  unsigned int attributeCount);

// 替换类的属性
void class_replaceProperty(Class _Nullable cls, const char * _Nonnull name,
                      const objc_property_attribute_t * _Nullable attributes,
                      unsigned int attributeCount)

  1. class_getProperty : 根据name返回指定的类属性(objc_property结构体)
  2. class_copyPropertyList : 它返回一个指向属性的数组,数组中每个元素是指向该属性的objc_property结构体的指针,outCount指针返回数组的大小

注意 : 返回的列表不包含父类的属性,必须使用free()来释放这个数组

  1. class_addProperty : 参数含义分别是:类名、属性名、属性特性、属性特性个数
    如添加属性名为@property (nonatomic , copy) NSString *property1;的属性
   objc_property_attribute_t type = { "T", "@\"NSString\"" };
    objc_property_attribute_t ownership = { "C", "" }; // C = copy
    objc_property_attribute_t nonatomic = { "N", "" }; //nonatomic
    objc_property_attribute_t backingivar  = { "V", "_property1" };//V 实例变量
    objc_property_attribute_t attrs[] = { type, ownership,nonatomic, backingivar };
    class_addProperty(cls, "property1", attrs, 4);
  1. class_replaceProperty : 替换类的属性

6.9、方法

//添加方法
BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types);

//获取实例方法
Method class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name);

//获取类方法
Method class_getClassMethod(Class _Nullable cls, SEL _Nonnull name);

//获取所有方法数组
Method  class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount);

//替代方法的实现
IMP class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                    const char * _Nullable types) ;

//返回方法的具体实现
IMP class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name) ;
IMP class_getMethodImplementation_stret(Class _Nullable cls, SEL _Nonnull name) ;

//对象是否响应指定的selector
BOOL class_respondsToSelector(Class _Nullable cls, SEL _Nonnull sel) ;

1.class_addMethod : 参数含义分别是:添加方法的类、添加方法的编号、方法实现、方法类型,使用这个方法添加方法会覆盖父类的方法实现,但不会取代本类中已存在的实现,如果本类中包含一个同名的实现,则函数会返回NO。如果要修改已存在实现,可以使用method_setImplementation

如添加一个eat方法

class_addMethod([self class],@selector(eat:),(IMP) eatIMP, "v@:@");
  1. class_getInstanceMethod : 根据name返回指定的对象方法(objc_method结构体)
  2. class_getClassMethod : 根据name返回指定的类方法(objc_method结构体)

注意 : 这两个函数都会去搜索父类的实现

  1. class_copyMethodList: 返回一个指向实例方法的数组,数组中每个元素都是该方法信息的objc_method结构体指针,outCount返回数组的大小。
    如果需要获取类方法,则可以使用class_copyMethodList(object_getClass(cls), &count),因为一个类的实例方法是定义在元类里面

注意 : 返回的列表不包含父类的方法,必须使用free()来释放这个数组

  1. class_replaceMethod : 如果类中不存在name指定的方法,则类似于class_addMethod函数一样会添加方法;如果类中已存在name指定的方法,则类似于method_setImplementation一样替代原方法的实现。

  2. class_getMethodImplementation : 返回一个指向方法实现函数的指针,在向类实例发送消息时会被调用,这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函数指针可能是一个指向runtime内部的函数,而不一定是方法的实际实现。例如,如果类实例无法响应selector,则返回的函数指针将是运行时消息转发机制的一部分。

  3. class_respondsToSelector : 我们通常使用NSObject类的respondsToSelector:instancesRespondToSelector:方法来达到相同目的。

6.10、协议

// 添加协议
BOOL class_addProtocol ( Class cls, Protocol *protocol );
// 返回类是否实现指定的协议
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
// 返回类实现的协议列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );

  1. class_conformsToProtocol : 可以使用NSObject类的conformsToProtocol:方法来替代。

  2. class_copyProtocolList : 返回一个指向协议的数组,数组中每个元素都是该协议信息的objc_object结构体指针,outCount返回数组的大小。

注意 : 返回的列表不包含父类的协议,必须使用free()来释放这个数组

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271