iOS_收藏

Runtime Class 和元类

2017-01-22  本文已影响58人  EnjoyWT

Objective-C Runtime 运行时之一:类与对象 :http://southpeak.github.io/blog/2014/10/25/objective-c-runtime-yun-xing-shi-zhi-lei-yu-dui-xiang/
1.http://chun.tips/blog/2014/11/05/bao-gen-wen-di-objective%5Bnil%5Dc-runtime(1)%5Bnil%5D-self-and-super/
2.http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/

"v@:"属于Objective-C类型编码的内容

(一)Class 类

runtime中对类的定义:

typedef struct objc_class *Class;

这里可以看到Class 其实是个指针,这个指针指向->struct objc_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;
    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;

可以看到运行时一个类还关联了它的超类指针,类名,成员变量,方法,缓存,还有附属的协议.

PS:OBJC2_UNAVAILABLE之类的宏定义是苹果在 Objc 中对系统运行版本进行约束的黑魔法,为的是兼容非Objective-C 2.0的遗留逻辑,但我们仍能从中获得一些有价值的信息,有兴趣的可以查看源代码。

Objective-C 2.0 的头文件虽然没暴露出objc_class结构体更详细的设计,我们依然可以从Objective-C 1.0 的定义中小窥端倪:

objc_class结构体中:ivarsobjc_ivar_list指针;methodLists是指向objc_method_list指针的指针。也就是说可以动态修改*methodLists的值来添加成员方法,这也是Category实现的原理,同样解释了Category不能添加属性的原因。关于二级指针,可以参考[这篇文章]3。而最新版的 Runtime 源码对这一块的描述已经有很大变化,可以参考下美团技术团队的[深入理解Objective-C:Category][4]。
[4]:http://tech.meituan.com/DiveIntoCategory.html
其中objc_ivar_listobjc_method_list分别是成员变量列表和方法列表:

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

struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}

如果你C语言不是特别好,可以直接理解为objc_ivar_list结构体存储着objc_ivar数组列表,而objc_ivar结构体存储了类的单个成员变量的信息;同理objc_method_list结构体存储着objc_method数组列表,而objc_method结构体存储了类的某个方法的信息。

最后要提到的还有一个objc_cache,顾名思义它是缓存,它在objc_class的作用很重要,在后面会讲到。

不知道你是否注意到了 objc_class中也有一个isa对象,这是因为一个 ObjC 类本身同时也是一个对象,为了处理类和对象的关系,runtime 库创建了一种叫做元类 (Meta Class) 的东西,类对象所属类型就叫做元类,它用来表述类对象本身所具备的元数据。类方法就定义于此处,因为这些方法可以理解成类对象的实例方法。每个类仅有一个类对象,而每个类对象仅有一个与之相关的元类。当你发出一个类似[NSObject alloc]的消息时,你事实上是把这个消息发给了一个类对象 (Class Object) ,这个类对象必须是一个元类的实例,而这个元类同时也是一个根元类 (root meta class) 的实例。所有的元类最终都指向根元类为其超类。所有的元类的方法列表都有能够响应消息的类方法。所以当[NSObject alloc]这条消息发给类对象的时候,objc_msgSend()会去它的元类里面去查找能够响应消息的方法,如果找到了,然后对这个类对象执行方法调用。

1.1元类是什么(或者元类里面存放放的是什么)

从上面的一段话可以结论这里总结强调一下:

元类里面存放的是:这个类的类方法(加号方法)

1.png

上图实线是super_class指针,虚线是isa指针。 有趣的是根元类的超类是NSObject,而isa指向了自己,而NSObject的超类为nil,也就是它没有超类.

元类小结

meta-class是类对象的类,每个类都有自己单独的meta-class。所有的类对象并不会属于同一个meta-class。
meta-class要保证类对象具有继承体系中基类的所有实例和类方法,以及继承体系中的所有中间类方法。对于所有NSObject继承体系下的类,NSObject的实例方法和协议方法对他们和他们meta-class的对象都要有效。
所有的meta-class使用基类的meta-class作为自己的基类,对于顶层基类的meta-class也是一样,只是它指向自己而已

1.2怎样获得一个类的元类呢?

先看一下isa的定义:他就是个指针(英文全名暂时找不到)

对象的isa指向类,类的isa 指向它的元类, 元类的isa 指向父类的元类, 如图1.png显示

通过上面的内容可以得出获得一个类的元类的方法:通过一个对象a,找到这个A类,通过这个A类找到这个A类的元类.

假设现在有实例对象b;
获得b元类的方法如下:

Class currentClass = [b class];  //得到的currentClass是b的类,
currentClass = object_getClass(currentClass);//此时currentClass是b类的元类
currentClass = object_getClass(currentClass);//currentClass 是元类的父元类
... 知道NSObject的元类,因为所有类都继承自NSObject

注:上面使用object_getClass来获取isa指针指向的类,因为isa指针是一个受保护成员,你不能直接访问其他对象的isa指针。上面没有使用class方法是因为在一个类对象上调用这个方法是无法获得meta-class的,它只是返回这个类而已。(所以[NSString class]只是返回NSString类,而不是NSString的meta-class 但是这里有另外一个方法 ```
objc_getMetaClass(class_getName(cls)) //暂时不知道两者区别,以后再修改



######1.3怎样判断一个类是不是元类
简单到一句话:

BOOL isMetaClass = class_isMetaClass(currentClass)//判断currentClass是否为元类

######1.4怎样通过元类获取类方法

Method *methods = class_copyMethodList(cls, &outCount);
//获得该类中所有的方法,属性的set,get方法也包涵在内,还有一个 .cxx_destruct的方法-也就是delloc方法

####(一 :Class相关函数)类与对象操作函数
runtime提供了大量的函数来操作类与对象。类的操作方法大部分是以class为前缀的,而对象的操作方法大部分是以objc或object_为前缀。下面我们将根据这些方法的用途来分类讨论这些方法的使用。
######类相关操作函数
我们可以回过头去看看```objc_class```的定义,runtime提供的操作类的方法主要就是针对这个结构体中的各个字段的。下面我们分别介绍这一些的函数

// 获取类的类名
const char * class_getName ( Class cls );
//如果传入的cls为Nil,则返回一个nil

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

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

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

- class_getSuperclass函数,当cls为Nil或者cls为根类时,返回Nil。不过通常我们可以使用NSObject类的superclass方法来达到同样的目的。
- class_isMetaClass函数,如果是cls是元类,则返回YES;如果否或者传入的cls为Nil,则返回NO。
**实例变量大小(instance_size)**

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

成员变量(ivars)及属性
在objc_class中,所有的成员变量、属性的信息是放在链表ivars中的。ivars是一个数组,数组中每个元素是指向Ivar(变量信息)的指针。runtime提供了丰富的函数来操作这一字段。大体上可以分为以下几类
1.成员变量操作函数

//获取类中指定名称实例成员变量的信息,它返回一个指向包含name指定的成员变量信息的objc_ivar结构体的指针(Ivar)
Ivar class_getInstanceVariable ( Class cls, const char *name );

// 获取类成员变量的信息 目前没有找到关于Objective-C中类变量的信息,一般认为Objective-C不支持类变量。注意,返回的列表不包含父类的成员变量和属性。
Ivar class_getClassVariable ( Class cls, const char *name );

// 添加成员变量,OC现在不支持添加成员变量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );

// 获取整个成员变量列表.它返回一个指向成员变量信息的数组,数组中每个元素是指向该成员变量信息的objc_ivar结构体的指针。
//这个数组不包含在父类中声明的变量。outCount指针返回数组的大小。需要注意的是,我们必须使用free()来释放这个数组。
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );

2.属性操作函数,主要包含以下函数:

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

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

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

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

**方法(methodLists)**

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

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

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

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

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

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

IMP class_getMethodImplementation_stret ( Class cls, SEL name );

// 类实例是否响应指定的selector

BOOL class_respondsToSelector ( Class cls, SEL sel );

class_addMethod的实现会覆盖父类的方法实现,但不会取代本类中已存在的实现,如果本类中包含一个同名的实现,则函数会返回NO。如果要修改已存在实现,可以使用method_setImplementation。一个Objective-C方法是一个简单的C函数,它至少包含两个参数—self和_cmd。所以,我们的实现函数(IMP参数指向的函数)至少需要两个参数,如下所示:

void myMethodIMP(id self, SEL _cmd)

{
// implementation ....

}

与成员变量不同的是,我们可以为类动态添加方法,不管这个类是否已存在。
>● class_getInstanceMethod、class_getClassMethod函数,与class_copyMethodList不同的是,这两个函数都会去搜索父类的实现。

>● class_copyMethodList函数,返回包含所有实例方法的数组,如果需要获取类方法,则可以使用class_copyMethodList(object_getClass(cls), &count)(一个类的实例方法是定义在元类里面)。该列表不包含父类实现的方法。outCount参数返回方法的个数。在获取到列表后,我们需要使用free()方法来释放它。

>● class_replaceMethod函数,该函数的行为可以分为两种:如果类中不存在name指定的方法,则类似于class_addMethod函数一样会添加方法;如果类中已存在name指定的方法,则类似于method_setImplementation一样替代原方法的实现。

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

>● class_respondsToSelector函数,我们通常使用NSObject类的respondsToSelector:或instancesRespondToSelector:方法来达到相同目的。

**协议(objc_protocol_list)**

// 添加协议
BOOL class_addProtocol ( Class cls, Protocol *protocol );

// 返回类是否实现指定的协议.
//可以使用NSObject类的conformsToProtocol:方法来替代
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );

// 返回类实现的协议列表
//函数返回的是一个数组,在使用后我们需要使用free()手动释放
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );

runtime还提供了两个函数来供CoreFoundation的tool-free bridging使用,即:

Class objc_getFutureClass ( const char *name );

void objc_setFutureClass ( Class cls, const char *name );

**动态创建类和对象**
runtime的强大之处在于它能在运行时创建类和对象。
动态创建类

// 创建一个新类和元类

Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );

// 销毁一个类及其相关联的类

void objc_disposeClassPair ( Class cls );

// 在应用中注册由objc_allocateClassPair创建的类

void objc_registerClassPair ( Class cls );

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

为了创建一个新类,我们需要调用objc_allocateClassPair。然后使用诸如class_addMethod,class_addIvar等函数来为新创建的类添加方法、实例变量和属性等。完成这些后,我们需要调用objc_registerClassPair函数来注册类,之后这个新类就可以在程序中使用了。

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

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

**动态创建对象**

// 创建类实例

id class_createInstance ( Class cls, size_t extraBytes );

// 在指定位置创建类实例

id objc_constructInstance ( Class cls, void *bytes );

// 销毁类实例

void * objc_destructInstance ( id obj );


- class_createInstance函数:创建实例时,会在默认的内存区域为类分配内存。extraBytes参数表示分配的额外字节数。这些额外的字节可用于存储在类定义中所定义的实例变量之外的实例变量。该函数在ARC环境下无法使用

调用class_createInstance的效果与+alloc方法类似。不过在使用class_createInstance时,我们需要确切的知道我们要用它来做什么。在下面的例子中,我们用NSString来测试一下该函数的实际效果:

id theObject = class_createInstance(NSString.class, sizeof(unsigned));

id str1 = [theObject init];

NSLog(@"%@", [str1 class]);

id str2 = [[NSString alloc] initWithString:@"test"];
NSLog(@"%@", [str2 class]);


输出结果是:

2014-10-23 12:46:50.781 RuntimeTest[4039:89088] NSString

2014-10-23 12:46:50.781 RuntimeTest[4039:89088] __NSCFConstantString

可以看到,使用class_createInstance函数获取的是NSString实例,而不是类簇中的默认占位符类__NSCFConstantString。

- objc_constructInstance函数:在指定的位置(bytes)创建类实例。
- objc_destructInstance函数:销毁一个类的实例,但不会释放并移除任何与其相关的引用
**实例操作函数**
针对整个对象进行操作的函数,这类函数包含

// 返回指定对象的一份拷贝

id object_copy ( id obj, size_t size );

// 释放指定对象占用的内存

id object_dispose ( id obj );

有这样一种场景,假设我们有类A和类B,且类B是类A的子类。类B通过添加一些额外的属性来扩展类A。现在我们创建了一个A类的实例对象,并希望在运行时将这个对象转换为B类的实例对象,这样可以添加数据到B类的属性中。这种情况下,我们没有办法直接转换,因为B类的实例会比A类的实例更大,没有足够的空间来放置对象。此时,我们就要以使用以上几个函数来处理这种情况,如下代码所示:

SObject *a = [[NSObject alloc] init];

id newB = object_copy(a, class_getInstanceSize(MyClass.class));

object_setClass(newB, MyClass.class);

object_dispose(a);

针对对象实例变量进行操作的函数,这类函数包含:

// 修改类实例的实例变量的值

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 );

如果实例变量的Ivar已经知道,那么调用object_getIvar会比object_getInstanceVariable函数快,相同情况下,object_setIvar也比object_setInstanceVariable快。
上一篇下一篇

猜你喜欢

热点阅读