我的阅读iOS小知识ios技术点

Objective-C Runtime:深入理解类与对象

2018-03-22  本文已影响799人  Jack_lin
在那樱花盛开的季节

概述

常说Objective-C是一门动态语言,那么问题来了,这个动态表现在那些方面呢?

其实最主要的表现就是Objective-C将很多静态语言在编译和链接时做的事情放到了运行时去做,它在运行时实现了对类、方法、成员变量、属性等信息的管理机制。

同时,运行时机制为我们开发过程提供很多便利之处,比如:

Objective-C的Runtime目前有两个版本:Leagcy RuntimeModen RuntimeLeagcy Runtime是最早期给32位Mac OX Apps使用的,而Moden Runtime是给64位Mac OX AppsiOS Apps使用的。

Runtime基本是C和汇编编写的,有一系列函数和数据结构组成的,具有公共接口的动态共享库,可见苹果为了动态系统的高效而作出的努力,你可以在这里下载到苹果维护的开源代码。

同时,GNU也有一个开源的Runtime版本,他们在努力保持一致。其头文件都存放在/usr/include/objc目录下。在Objective-C Runtime Reference中,有对Runtime函数使用细节的文档。

类与对象

类的数据结构(Class)

类的数据结构可以在objc/runtime.h源码中找到,如下所示:

struct objc_class {
   //isa指针指向Class
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

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

} OBJC2_UNAVAILABLE;

在Objective-C中是由Class表示的,Class是一个指向struct objc_class的指针。

typedef struct objc_class *Class;

在这个类的数据结构中,有几个字段需要解释一下:

objc_cache与cache

上文object_class中结构体中的cache字段,是用来缓存使用过的方法。这个字段是一个指向objc_cache的指针,具体数据结构如下所示:

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};

字段的具体描述如下:

关于上文object_class中结构体中的cache字段,对它的解释如下:

举个栗子:

NSDictionary *dic= [[NSDictionary alloc] init];//执行过程

其缓存调用方法的流程:

元类(Meta Class)

上面讲到,有时候类也是一个对象,这种类对象是某一种类的实例,这种类就是元类(Meta Class)。

好比类与对应的实例描述一样,元类则是类作为对象的描述。元类中方法列表对应的是类方法(Class Method)列表,这正是类作为一个对象所需要的。

当调用该方法[NSArray alloc]时,Runtime就会在对应的元类方法列表查找其类对应的方法,并匹配调用。

Meta Class是类对象的类。

官方的解释如下所示:

Since a class is an object, it must be an instance of some other class: a metaclass. The metaclass is the description of the class object, just like the class is the description of ordinary instances. Class methods are described by the metaclass on behalf of the class object, just like instance methods are described by the class on behalf of the instance objects.

至此,又有了新的疑问:元类又是谁的实例呢?它的isa又指向谁呢?答案如下图所示:

Class vs. Meta Class

由上图可以看出,元类的isa都指向根元类(Root Meta Class),即元类都是根元类的实例。

而根元类(Root Meta Class)的isa则指向自己,这样就不会无休止的关联下去了。

图中同样展示类和元类的继承关系,非常清晰易懂。

类的实例数据结构

在 Objective-C 中类的实例的数据结构是定义在struct objc_object 中(objc/objc.h):

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

可以看出,这个结构体只有一个字段,即指向该实例所属类的isa指针。

这个指针跟上面介绍的类的isa不一样:类的isa指向对应的元类(Meta Class),实例的isa则是指向对应的类(Class),而这个Class里包含上述所讲的数据:父类、类名、方法列表等等。

当我们向一个类的实例发送消息时,Runtime会根据实例对象的isa找到这个实例对象所属的类,然后再在这个类的方法列表和其父类的方法列表中查找与消息相对应的selector指向的方法,进而执行目标方法。

当创建某一个类的实例时,分配的内存中会包含一个objc_object数据结构,然后是类的实例变量的相关数据。

NSObject类的allocallocWithZone:方法是使用函数class_createInstance来创建objc_object数据结构。

我们常见的id是一个struct objc_object类型的指针。id类型的对象可以转换为任何一种类型的对象,它的作用有点类似 C 语言中的 void * 指针类型。

/// A pointer to an instance of a class.
typedef struct objc_object *id;

相关函数

Objective-C的Runtime我们提供了很多运行时状态跟类与对象相关的函数。类的操作方法大部分是以class_ 为前缀的,而对象的操作方法大部分是以objc_object_为前缀,具体以分类的形式进行讨论。

类相关函数

类的相关函数大部分是与objc_class结构体各个字段相关的方法。

类名
// 获取类的类名
const char * class_getName ( Class cls );
父类(super_class)和元类(meta_class)
// 获取类的父类
Class class_getSuperclass ( Class cls );
// 判断给定的Class是否是一个元类
BOOL class_isMetaClass ( Class cls );
实例变量大小(instance_size)
// 获取实例大小
size_t class_getInstanceSize ( Class cls );
成员变量(ivars)及属性

objc_class中,所有的成员变量、属性的信息是放在链表ivars中的。ivars是一个数组,数组中每个元素是指向Ivar(变量信息)的指针。

1、成员变量操作函数:

// 获取类中指定名称实例成员变量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );
// 获取类成员变量的信息
Ivar class_getClassVariable ( Class cls, const char *name );
// 添加成员变量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
// 获取整个成员变量列表
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 );

3.MAC OS X系统支持使用垃圾回收器,Runtime提供了几个函数来确定一个对象的内存区域是否可以被垃圾回收器扫描,以处理strong/weak引用。这几个函数定义如下:

const uint8_t * class_getIvarLayout ( Class cls );
void class_setIvarLayout ( Class cls, const uint8_t *layout );
const uint8_t * class_getWeakIvarLayout ( Class cls );
void class_setWeakIvarLayout ( Class cls, const uint8_t *layout );

通常情况下,我们不需要去主动调用这些方法;在调用objc_registerClassPair时,会生成合理的布局。

方法(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 );
void myMethodIMP(id self, SEL _cmd)
{
    // implementation ....
}

与成员变量不同的是,我们可以为类动态添加方法,不管这个类是否已存在。

参数types是一个描述传递给方法的参数类型的字符数组,这就涉及到类型编码。

协议(objc_protocol_list)
// 添加协议
BOOL class_addProtocol ( Class cls, Protocol *protocol );
// 返回类是否实现指定的协议
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
// 返回类实现的协议列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
版本(version)
// 获取版本号
int class_getVersion ( Class cls );
// 设置版本号
void class_setVersion ( Class cls, int version );

其它

runtime还提供了两个不直接使用的函数来供CoreFoundationtool-free bridging使用,即:

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

使用上述函数时,需要特别的注意一下细节信息和使用规范,具体可以查阅 Objective-C Runtime Reference

动态创建类与对象

Runtime提供在运行时创建类与对象的方法。

动态创建类
// 创建一个新类和元类
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );
// 销毁一个类及其相关联的类
void objc_disposeClassPair ( Class cls );
// 在应用中注册由objc_allocateClassPair创建的类
void objc_registerClassPair ( Class cls );
动态创建对象
// 创建类实例
id class_createInstance ( Class cls, size_t extraBytes );
// 在指定位置创建类实例
id objc_constructInstance ( Class cls, void *bytes );
// 销毁类实例
void * objc_destructInstance ( id obj );
- (void)testInstanceMethod{
    id theObject = class_createInstance([NSString class], sizeof(unsigned));
    id str1 = [theObject init];
    NSLog(@"%@", [str1 class]);
    
    id str2 = [[NSString alloc] initWithString:@"test"];
    NSLog(@"%@", [str2 class]);
}

输出结果:

2018-03-21 22:55:18.503665+0800 RuntimeUsage[2774:32008] NSString
2018-03-21 22:55:21.624606+0800 RuntimeUsage[2774:32008] __NSCFConstantString
实例操作函数

实例操作函数主要是针对我们创建的实例对象的一系列操作函数。

我们可以使用这组函数来从实例对象中获取我们想要的一些信息,如实例对象中变量的值。这组函数可以分为三小类:

1、针对整个对象进行操作的函数,这类函数包含:

// 返回指定对象的一份拷贝
id object_copy ( id obj, size_t size );
// 释放指定对象占用的内存
id object_dispose ( id obj );

有这样一种场景,假设我们有类A和类B,且类B是类A的子类。

类B通过添加一些额外的属性来扩展类A。现在我们创建了一个A类的实例对象,并希望在运行时将这个对象转换为B类的实例对象,这样可以添加数据到B类的属性中。

这种情况下,我们没有办法直接转换,因为B类的实例会比A类的实例更大,没有足够的空间来放置对象。此时,我们就要以使用以上几个函数来处理这种情况,如下代码所示:

NSObject *a = [[NSObject alloc] init];
id newB = object_copy(a, class_getInstanceSize(MyClass.class));
object_setClass(newB, MyClass.class);
object_dispose(a);

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

 修改类实例的实例变量的值
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快。

3、针对对象的类进行操作的函数,这类函数包含:

// 返回给定对象的类名
const char * object_getClassName ( id obj );
// 返回对象的类
Class object_getClass ( id obj );
// 设置对象的类
Class object_setClass ( id obj, Class cls );

获取类定义

Objective-C动态运行库会自动注册我们代码中定义的所有的类。

我们也可以在运行时创建类定义并使用objc_addClass函数来注册它们。Runtime提供了一系列函数来获取类定义相关的信息,这些函数主要包括:

// 获取已注册的类定义的列表
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 );

下面代码演示了该函数的用法:

- (void)testGetNumberClass{
    int numClasses;
    Class *classes = NULL;
    
    numClasses = objc_getClassList(NULL, 0);
    if (numClasses > 0) {
        classes = (Class *) malloc(sizeof(Class) * numClasses);
        numClasses = objc_getClassList(classes, numClasses);
        NSLog(@"count of classes:%d", numClasses);
        
        for (int i = 0; i < numClasses; i ++) {
            Class cls = classes[I];
           NSLog(@"class name: %s", class_getName(cls));
        }
    }
    free(classes);
}

输出的结果:

2018-03-21 23:58:57 RuntimeUsage[3378:65534] count of classes:11809
2018-03-21 23:59 RuntimeUsage[3378:65534] class name: _CNZombie_
2018-03-21 23:59 RuntimeUsage[3378:65534] class name: JSExport
2018-03-21 23:59 RuntimeUsage[3378:65534] class name: NSLeafProxy
2018-03-21 23:59 RuntimeUsage[3378:65534] class name: NSProxy
2018-03-21 23:59 RuntimeUsage[3378:65534] class name: _UITargetedProxy
2018-03-21 23:59 RuntimeUsage[3378:65534] class name: _UIViewServiceReplyControlTrampoline
2018-03-21 23:59:04  RuntimeUsage[3378:65534] class name: _UIViewServiceReplyAwaitingTrampoline
····  还有很多

运行时操作操作类与对象的示例代码

首先,创建继承关系为Animal->Dog->NSObject的几个类,然后使用Runtime的方法打印其中的关系,运行结果如下所示:

- (void)verifyClassTypeRelation{
    
    //Use `object_getClass` get Class's `isa`
    
    Dog *aDog = [[Dog alloc] init];
    Class dogCls = object_getClass(aDog);
    NSLog(@"isa->%@ , super_class->%@", dogCls, class_getSuperclass(dogCls));
    // print:isa->Dog, super_class->Animal
    Class dogMetaCls = objc_getMetaClass([NSStringFromClass(dogCls) UTF8String]);
    if (class_isMetaClass(dogMetaCls)) {
        NSLog(@"YES, metaCls->%@ , metaCls's super_Class->%@, metaCls's isa->%@", dogMetaCls, class_getSuperclass(dogMetaCls), object_getClass(dogMetaCls));
        //print: YES, metaCls->Dog , metaCls's super_Class->Animal, metaCls's isa->NSObject
    }else{
        NSLog(@"NO");
    }
    
    Animal *animal = [[Animal alloc] init];
    Class animalCls = object_getClass(animal);
    NSLog(@"isa->%@ , super_class->%@", animalCls, class_getSuperclass(animalCls));
    //print: isa->Animal , super_class->NSObject
    Class animalMetaCls = objc_getMetaClass([NSStringFromClass(animalCls) UTF8String]);
    if (class_isMetaClass(animalMetaCls)) {
        NSLog(@"YES, metaCls->%@ , metaCls's super_Class->%@, metaCls's isa->%@", animalMetaCls, class_getSuperclass(animalMetaCls), object_getClass(animalMetaCls));
        //print:YES, metaCls->Animal , metaCls's super_Class->NSObject, metaCls's isa->NSObject
    }else{
        NSLog(@"NO");
    }
    
    Class viewMetaCls = objc_getMetaClass([NSStringFromClass([UIView class]) UTF8String]);
    if (class_isMetaClass(viewMetaCls)) {
        NSLog(@"YES, metaCls->%@ , metaCls's super_Class->%@, metaCls's isa->%@", viewMetaCls, class_getSuperclass(viewMetaCls), object_getClass(viewMetaCls));
        //print:YES, metaCls->UIView , metaCls's super_Class->UIResponder, metaCls's isa->NSObject
    }
    
    Class rootMetaCls = objc_getMetaClass([NSStringFromClass([NSObject class]) UTF8String]);
    if (class_isMetaClass(rootMetaCls)) {
        NSLog(@"YES, metaCls->%@ , metaCls's super_Class->%@, metaCls's isa->%@", rootMetaCls, class_getSuperclass(rootMetaCls), object_getClass(rootMetaCls));
        //print:YES, metaCls->NSObject , metaCls's super_Class->NSObject, metaCls's isa->NSObject
    }
    
}

打印信息如下所示:

isa->Dog, super_class->Animal
YES, metaCls->Dog , metaCls's super_Class->Animal, metaCls's isa->NSObject
isa->Animal , super_class->NSObject
YES, metaCls->Animal , metaCls's super_Class->NSObject, metaCls's isa->NSObject
YES, metaCls->UIView , metaCls's super_Class->UIResponder, metaCls's isa->NSObject
YES, metaCls->NSObject , metaCls's super_Class->NSObject, metaCls's isa->NSObject

需要特别注意一下,Object_getClass可以获取当前对象的isa。以Dog类打印信息为例,解释一下具体实现的原理:

isa->Dog, super_class->Animal
YES, metaCls->Dog , metaCls's super_Class->Animal, metaCls's isa->NSObject

同理可得,AnimalUIView打印信息解释同上。NSobject,它元类的isa指针还是指向自己的类——NSobject。打印的信息与上述的关系图保持一致。

动态操作类与实例的代码

动态创建类的源码

/***********************************************************************
* _objc_allocateFutureClass
* Allocate an unresolved future class for the given class name.
* Returns any existing allocation if one was already made.
* Assumes the named class doesn't exist yet.
* Locking: acquires runtimeLock
**********************************************************************/
Class _objc_allocateFutureClass(const char *name)
{
    rwlock_writer_t lock(runtimeLock);

    Class cls;
    NXMapTable *map = futureNamedClasses();

    if ((cls = (Class)NXMapGet(map, name))) {
        // Already have a future class for this name.
        return cls;
    }

    cls = _calloc_class(sizeof(objc_class));
    addFutureNamedClass(name, cls);

    return cls;
}
- (void)dynamicAddMethod{
    
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
    
    //1、Create and register class, add method to class
    Class cls = objc_allocateClassPair([Animal class], "Lion", 0);// 当为`Cat`时,返回的创建类cat地址为0x0,将`Cat`作为关键字
    //method: 返回`int32_t`,type使用`i`;参数:`id self`,type使用`@`;`SEL _cmd`,type使用`:`;
    //`NSDictionary *dic`,type使用`@`.综上,type使用'i@:@'
    ///具体类型可参照 https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
    BOOL isAddSuccess = class_addMethod(cls, @selector(howlMethod), (IMP)testRuntimeMethodIMP, "i@:@");
    NSLog(@"%@", isAddSuccess ? @"添加方法成功" : @"添加方法失败");
    //You can only register a class once.
    objc_registerClassPair(cls);
    
    //2、Create instance of class
    id whiteCat = [[cls alloc] init];
     NSLog(@"%@, %@", object_getClass(whiteCat), class_getSuperclass(object_getClass(whiteCat)));
    // Print: Lion, Animal
    Class whiteCatCls = object_getClass(whiteCat);
    Class metaCls = objc_getMetaClass([NSStringFromClass(whiteCatCls) UTF8String]);
    if (class_isMetaClass(metaCls)) {
        NSLog(@"YES, %@, %@, %@", metaCls, class_getSuperclass(metaCls), object_getClass(metaCls));
        // Print: YES, Lion, Animal, NSObject
    }else{
        NSLog(@"NO");
    }
    
    //3、Method of class
    unsigned int methodCount = 0;
    Method *methods = class_copyMethodList(cls, &methodCount);
    for (int32_t i = 0; i < methodCount; i ++) {
        Method aMethod = methods[I];
        NSLog(@"%@, %s", NSStringFromSelector(method_getName(aMethod)), method_getTypeEncoding(aMethod));
        //print:howlMethod, I@:@
    }
    free(methods);
    
    //4、call method
    int32_t result = (int)[whiteCat performSelector:@selector(howlMethod) withObject:@{@"name":@"lion", @"sex": @"male"}];
    NSLog(@"%d", result);//print:9
    
    //5、destory instance and class
    whiteCat = nil;
    
    // Do not call this function if instances of the cls class or any subclass exist.
    objc_disposeClassPair(cls);
    
#pragma clang diagnostic pop
}

打印的信息如下所示:

添加方法成功
 Lion, Animal
YES, Lion, Animal, NSObject
howlMethod, I@:@
testRuntimeMethodIMP: {
    name = lion;
    sex = male;
}
9

在执行objc_allocateClassPair中,类的名称设置为Cat时,创建出的Class的地址始终指向0x0,创建类失败。

猜测其中的原因可能是Cat与内部的关键字冲突了,导致类创建失败,改为cat或者其他的都可以创建成功;

针对上述代码,有几点需要特殊说明一下:

isKindOf 和 isMemberOf

举个栗子:

@interface TestMetaClass: NSObject
@end

@implementation TestMetaClass
@end

int main(int argc, char * argv[]) {
    @autoreleasepool {
        BOOL result1 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
        BOOL result2 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
        BOOL result3 = [(id)[TestMetaClass class] isMemberOfClass:[TestMetaClass class]];
        BOOL result4 = [(id)[TestMetaClass class] isKindOfClass:[TestMetaClass class]];
        NSLog(@"%d %d %d %d", result1, result2, result3, result4);
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

//log
2018-02-09 16:45:54.048040+0800 RuntimeUsage[9220:5754652] 0 1 0 0

关于isMemberOfClassisKindOfClassObject.mm中的实现,具体如下:

- (BOOL)isMemberOf:aClass
{
     return isa == (Class)aClass;
}

- (BOOL)isKindOf:aClass
{
     Class cls;
     for (cls = isa; cls; cls = cls->superclass)
          if (cls == (Class)aClass)
               return YES;
     return NO;
}

以上再次验证了,NSObject Meta Classisa指针指向自身,其super class指向NSObject

小结

本文着重讲解了在Runtime时类与对象相关方法和数据结构,通过这些讲解可以让大家对Objective-C底层类与对象实现有大致的了解,并且可以为大家平常编程过程提供一些思路上的启发。

测试使用的栗子(Demo)都在篮子里

参考

扫一扫下面的二维码,欢迎关注我的个人微信公众号攻城狮的动态(ID:iOSDevSkills),可在微信公众号进行留言,更多精彩技术文章,期待您的加入!一起讨论,一起成长!一起攻城狮!

公众号
上一篇下一篇

猜你喜欢

热点阅读