前言

一、isa的走势

二、superClass的走势

三、isa和supterClass的经典走势图:

四、类的结构初识

五、寻找类方法

六、isKindOfClass 、isMemberOfClass

七、获取属性


我们对OC的“对象”底层了解完之后,这篇文章就对OC的“类”进行一些分析。


“类”是我们在开发项目的过程中最熟悉不过的了。但是我们真正了解“类”的底层吗?带着好奇心,先同样的抛出2个问题:

1、 类的isa指向哪里?

2、类的内部结构是什么?


一、isa的走势

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        MyTextObjc *objc = [MyTextObjc alloc];
        NSLog(@"end");
    }
    return 0;
}

首先通过lldb打印出objc的信息:

我们知道0x011d8001000083dd 是对象的isa。那么我&0x00007ffffffffff8ULL,也就得出了isa保存的类信息。

接着我们拿到0x00000001000083d8这个类地址继续x/4gx打印。

因为类在底层也是继承与objc_object的,类也是对象,所以此时 0x0000000100008400 这个其实是MyTextObjc的isa。这里我没有用 0x0000000100008400 & 0x00007ffffffffff8ULL。因为结果都是0x0000000100008400。

通过上面的打印,我们发现:0x00000001000083d8 和 0x0000000100008400 都是MyTextObjc。其中0x00000001000083d8 是类,0x0000000100008400是元类。

我们继续x/4gx 打印0x0000000100008400

我们发现打印出来的内存地址为 0x100008400,对应的isa类信息为NSObject。
我们再继续打印NSObject.class

此时会发现,MyTextObjc的元类的isa保存的是NSObject的元类,而并非是NSObject类。
最后我们继续打印NSObject元类也就是根元类的内存时,发现根元类的isa指向自己。
总结:objc->MyTextObjc->MyTextObjc(元类)->NSObject(根元类)->NSObject(根元类)。

最终可以得到isa的走势如下图:

对象 -> 类 -> 元类 -> 根元类 -> 根元类(...)

二、superClass的走势

对于supterclass,就不按照isa的走势那样进行lldb打印调试了。这里直接附上supterclass的走势图:

其中值得注意的是:
(1)所有类最终继承于NSObject。
(2)元类也是有继承链的,但是最终的根元类是继承与NSObject。
(3)NSObject的supterclass最终指向nil。

举个例子:

///.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN
@interface MyTextObjc : NSObject

@end

NS_ASSUME_NONNULL_END

//////.m
#import "MyTextObjc.h"

@implementation MyTextObjc

@end

我将MyTextObjc类里面的方法都删掉,什么方法都不留。
然后我再创建一个NSObject的分类:

///.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSObject (Test)
- (void)helloWord;
@end

NS_ASSUME_NONNULL_END

///.m
#import "NSObject+Test.h"

@implementation NSObject (Test)

- (void)helloWord {
    NSLog(@"123");
}

@end

我在NSObject分类里面写了一个实例方法。
此时我调用MyTextObjc的类方法。

#import "MyTextObjc.h"
int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        [MyTextObjc performSelector:@selector(helloWord)];
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

最终会打印123。也就验证了最终是找到了NSObject的方法。
因为方法第一次在lookUpImpOrForward慢速查找时,如果本类找不到,会递归去二分查找父类。
关于方法的查找流程,后面有相应文章分析。

三、isa和supterClass的经典走势图:

四、类的结构初识

struct objc_class : objc_object {
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;
    // Class ISA;   //8
    Class superclass;   //8
    cache_t cache;      //16       // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    .........
}

通过底层源码,可以知道objc_class的成员有 isa、superclass、cache、bits。
isa和superclass我们已经分析过走势了。cache我们后面再具体进行分析。
我们可以想而知,方法、属性、协议等等,都应该和bits有关系,所以接下来我们主要针对bits探索。
首先找到bits的内存地址,然后通过lldb来取出bits里面的数据。

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //8
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask; //4
#if __LP64__
            uint16_t                   _flags; //2
#endif
            uint16_t                   _occupied; //2
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
}
......

先来看一下cache_t的结构,这个版本cache_t将_maybeMask、_flags、_occupied放到了位域里面。所以这里cache_t的大小为16字节。
前面isa、supterclass各占8个字节。所以当我们拿到一个类的内存首地址之后,就可以通过偏移32个位置来定位到bits。
上层代码:

@interface MyTextObjc : NSObject
@property (nonatomic, copy) NSString *nickName; 
@property (nonatomic, assign) int age;
@end

我定义了2个属性,现在开始打印MyTextObjc.class

通过lldb:

第1步:将首地址0x100008440 偏移32个位置,得到(class_data_bits_t *)0x100008460

第二步读取data()方法:

 class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
}

拿到 class_rw_t*。

第三步通过源码查看class_rw_t

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;

private:
    using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, 
    class_rw_ext_t, 
    PTRAUTH_STR("class_ro_t"),
     PTRAUTH_STR("class_rw_ext_t")>;

    const ro_or_rw_ext_t get_ro_or_rwe() const {
        return ro_or_rw_ext_t{ro_or_rw_ext};
    }

    void set_ro_or_rwe(const class_ro_t *ro) {
        ro_or_rw_ext_t{ro, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, 
        memory_order_relaxed);
    }

    void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
        // the release barrier is so that the class_rw_ext_t::ro initialization
        // is visible to lockless readers
        rwe->ro = ro;
        ro_or_rw_ext_t{rwe, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, 
        memory_order_release);
    }

    class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);

public:
    void setFlags(uint32_t set)
    {
        __c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, 
        set,
         __ATOMIC_RELAXED);
    }

    void clearFlags(uint32_t clear) 
    {
        __c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, 
        ~clear,
         __ATOMIC_RELAXED);
    }

    // set and clear must not overlap
    void changeFlags(uint32_t set, uint32_t clear) 
    {
        ASSERT((set & clear) == 0);

        uint32_t oldf, newf;
        do {
            oldf = flags;
            newf = (oldf | set) & ~clear;
        } while (!OSAtomicCompareAndSwap32Barrier(oldf,
         newf, 
         (volatile int32_t *)&flags));
    }

    class_rw_ext_t *ext() const {
        return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
    }

    class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
        } else {
            return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
        }
    }

    class_rw_ext_t *deepCopy(const class_ro_t *ro) {
        return extAlloc(ro, true);
    }

    const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
        }
        return v.get<const class_ro_t *>(&ro_or_rw_ext);
    }

    void set_ro(const class_ro_t *ro) {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
        } else {
            set_ro_or_rwe(ro);
        }
    }
    //方法列表
    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)
            ->baseMethods()};
        }
    }
    //属性
    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)
            ->baseProperties};
        }
    }
    //协议
    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)
            ->baseProtocols};
        }
    }
};

通过properties()获取属性。

第4步:获取到list里面的ptr(指向property_list_t的地址指针)。

第5步:通过get方法获取到对应的属性

  Element& get(uint32_t i) const { 
        ASSERT(i < count);
        return getOrEnd(i);
    }

拿到class_rw_t 中的方法和属性类似:

通过最终打印,可以看到每一个方法都有 name、types、imp。

总结一下 sel 和 imp的关系。

SEL:方法编号 (相当于书本目录的名称)
IMP:函数指针地址(相当于书本目录的页码)
首先要知道要查找方法的名称,然后通过名称找到对应的函数指针地址,最后通过函数指针地址定位到最终的方法。

总结一下方法签名

先看age的get方法:i16@0:8
i 代表返回值
16 代表总共编码所占的字节为16
@ 代码id类型,因为每一个方法底层都会有2个默认参数 self 和 _cmd。
0 代表从0位置开始
:代表sel
8 代码从第8位开始
具体可以参考:Objective-C type encodings

关于协议,后面我会对协议的底层和使用场景具体进行分析

成员变量和属性

在上面的步骤里,我们可以得到 属性、方法、协议。但是成员变量在哪里呢?
首先我们先区分一下成员变量和属性:

成员变量不会默认生成get、set方法。

属性是通过@property在底层自动生成了带下划线的成员变量和get、set方法。

另外再区分一下实例变量:
实例变量是类实例化出来的对象。是特殊的成员变量。

接下来我们继续来寻找类中的成员变量列表
通过对源码的分析

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    union {
        const uint8_t * ivarLayout;
        Class nonMetaclass;
    };

    explicit_atomic<const char *> name;
    // With ptrauth, this is signed if it points to a small list, but
    // may be unsigned if it points to a big list.
    void *baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

会发现ivars在ro里面,首先我在MySubTextObjc里面加2个成员变量


@interface MyTextSubObjc : MyTextObjc {
    int a;
    NSString *magicgeng;
}

然后继续lldb打印:

先总结一下ro里面的结构:

类的整体宏观结构:class->bits->data()/class_rw_t->(class_ro_t/class_rw_ext_t->ro)


五、寻找类方法

在打印class_rw_t中方法列表时,我发现并没有找到类方法,只找到了实例方法。
类的方法其实是存在元类里面的,这里我们分别通过lldb和代码来验证:

通过lldb来验证

@interface MyTextObjc : NSObject 
- (void)helloWord1;
+ (void)helloWord2;
@end

我在MyTextObjc加入2个方法。
通过lldb打印:

在元类的bits->rw里面的methods中就找到了+ (void)helloWord2方法。

通过代码验证

//
//  main.m
//  ClassTest2
//
//  Created by magic-geng on 2021/4/23.
//

#import <Foundation/Foundation.h>
#import "AppDelegate.h"
#import <objc/runtime.h>
#import "MyTextObjc.h"

void getCopyMethodList(Class class) {
    unsigned int count = 0;
    Method *methods = class_copyMethodList(class, &count);
    for (unsigned int i = 0; i < count; i ++) {
        Method const method = methods[i];
        NSString *key = NSStringFromSelector(method_getName(method));
        NSLog(@"method name:%@",key);
    }
    free(methods);
}

void instanceMethodAndClassToMetaclass(Class class) {
    const char *className = class_getName(class);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(class, @selector(helloWord1));
    Method method2 = class_getInstanceMethod(metaClass, @selector(helloWord1));
    
    Method method3 = class_getInstanceMethod(class, @selector(helloWord2));
    Method method4 = class_getInstanceMethod(metaClass, @selector(helloWord2));
    
    NSLog(@"%s - %p - %p - %p - %p",__FUNCTION__,method1,method2,method3,method4);
    
}

void classMethodAndClassToMetaclass(Class class) {
    const char *className = class_getName(class);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(class, @selector(helloWord1));
    Method method2 = class_getClassMethod(metaClass, @selector(helloWord1));

    Method method3 = class_getClassMethod(class, @selector(helloWord2));

    Method method4 = class_getClassMethod(metaClass, @selector(helloWord2));
    
    NSLog(@"%s - %p - %p - %p - %p",__func__,method1,method2,method3,method4);
}

void impClassAndMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    IMP imp1 = class_getMethodImplementation(pClass, @selector(helloWord1));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(helloWord1));

    IMP imp3 = class_getMethodImplementation(pClass, @selector(helloWord2));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(helloWord2));

    NSLog(@"%s %p - %p - %p - %p",__func__,imp1,imp2,imp3,imp4);
}



int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        
        ///获取实例方法
        getCopyMethodList(MyTextObjc.class);
        
        ///获取类方法
        Class class = object_getClass(MyTextObjc.class);
        getCopyMethodList(class);
        
        ///通过class_getInstanceMethod获取方法
        instanceMethodAndClassToMetaclass(MyTextObjc.class);
        
        ///通过class_getClassMethod 获取方法
        classMethodAndClassToMetaclass(MyTextObjc.class);
       
        ///通过class_getMethodImplementation获取imp
        impClassAndMetaclass(MyTextObjc.class);
    
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

打印结果:
method name:helloWord1
method name:helloWord2
instanceMethodAndClassToMetaclass - 0x102663ce8 - 0x0 - 0x0 - 0x102663c80
classMethodAndClassToMetaclass - 0x0 - 0x0 - 0x102663c80 - 0x102663c80
impClassAndMetaclass 0x10265cc50 - 0x7fff20174440 - 0x7fff20174440 - 0x10265cc60

通过getCopyMethodList可以获取到对应的类里面的方法和元类里面的方法

Method *
class_copyMethodList(Class cls, unsigned int *outCount)
{
    unsigned int count = 0;
    Method *result = nil;
    
    if (!cls) {
        if (outCount) *outCount = 0;
        return nil;
    }
    
    mutex_locker_t lock(runtimeLock);
    const auto methods = cls->data()->methods();
    
    ASSERT(cls->isRealized());
    
    count = methods.count();
    
    if (count > 0) {
        result = (Method *)malloc((count + 1) * sizeof(Method));
        
        count = 0;
        for (auto& meth : methods) {
            result[count++] = &meth;
        }
        result[count] = nil;
    }
    
    if (outCount) *outCount = count;
    return result;
}

通过class_getInstanceMethod可以获取到对应的类里面的实例方法和元类里面的实例方法
通过class_getMethodImplementation可以获取到对应的类里面的方法函数地址和元类里面的方法函数地址
通过class_getClassMethod可以获取到对应的类里面的类方法,和元类里面的类方法。
其中有一个问题:元类可以通过class_getInstanceMethod获取helloWord2,也可以通过class_getClassMethod获取到helloWord2。
那么为什么元类也可以通过class_getClassMethod获取到helloWord2呢?
通过源码分析:

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}
 // Like isMetaClass, but also valid on un-realized classes
    bool isMetaClassMaybeUnrealized() {
        static_assert(offsetof(class_rw_t, flags) == offsetof(class_ro_t, flags), "flags alias");
        static_assert(RO_META == RW_META, "flags alias");
        if (isStubClass())
            return false;
        return data()->flags & RW_META;
    }

    // NOT identical to this->ISA when this is a metaclass (当这是一个元类时,与this-> ISA不相同)
    Class getMeta() {
        if (isMetaClassMaybeUnrealized()) return (Class)this;
        else return this->ISA();
    }

可以看到,class_getClassMethod传进去cls,最终也是走的class_getInstanceMethod,并且传进去的是一个元类。又在getMeta()函数中做了判断,如果cls已经是元类了,那么直接返回this。这里如果不断的递归去找元类的元类,然后找根元类。会无限递归。

六、isKindOfClass 、isMemberOfClass

 BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
 BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
 BOOL re3 = [(id)[MyTextObjc class] isKindOfClass:[MyTextObjc class]];       //
 BOOL re4 = [(id)[MyTextObjc class] isMemberOfClass:[MyTextObjc class]];     //

BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     //
BOOL re7 = [(id)[MyTextObjc alloc] isKindOfClass:[MyTextObjc class]];       //
BOOL re8 = [(id)[MyTextObjc alloc] isMemberOfClass:[MyTextObjc class]];     //

先通过源码分析,再来确定re1 - re8 的结果。

//判断
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

// Calls [obj isKindOfClass]  ///又是llvm做的一层重绑定
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
    if (slowpath(!obj)) return NO;
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}

首先来分析isKindOfClass。

无论isKindOfClass是实例方法还是类方法,再for循环时,tcls首先指向cls的isa(类信息)。
那么对于re1 和 re3来说,第一次遍历都不满足条件。那么对于re5和 re7来说都满足条件,所以 re5 和 re7 结果都是1。
继续看第二次遍历,tcls等于了它的父类,对于re1,tcls第一次遍历时是根元类,根元类的superclass指向NSObject,所以第二次遍历re1结果为1。对于re3来说,tcls第一次遍历时是根元类,根元类的superclass指向NSObject,并不是MyTextObjc,然后再继续便利是tcls就为nil了,所以结果为0。
总结一下isKindOfClass的方法的本意:就是拿当前的类判断其是否是要判断类的子类。

继续分析isMemberOfClass

无论isMemberOfClass是实例方法还是类方法,都是拿到当前对象或者类的isa来进行对比。对于re2来说,就是拿NSObject的根元类和NSObject对比,显然结果为0。
对于re4来说,是拿MyTextObjc的元类和MyObject对比,显然结果也为0。
对于re6和re8来说,对象的isa指向类,那么结果显示是1。
总结一下isMemberOfClass的方法的本意:主要是用来判断当前对象是否属于对象所属的类(不能是父类)。

七、获取属性

id LenderClass = objc_getClass("MyTextObjc");
        unsigned int outCount, i;
        objc_property_t *properties = class_copyPropertyList(LenderClass, 
        &outCount);
        for (i = 0; i < outCount; i++) {
            objc_property_t property = properties[i];
            fprintf(stdout, "%s %s\n", 
            property_getName(property), 
            property_getAttributes(property));
        }

根据以上代码可以获取到属性列表,我们打印一下MyTextObjc中的属性
name T@"NSString",C,N,V_name
这里T代表type类型为NSString
C代表Copy
N代表nonatomic
V代表verible(变量)_name

具体可以参考:官网说明