类的结构分析(中)

本节讲什么

本节从源码及lldb的形式来分析类中的属性、成员变量、方法、类方法。

class_data_bits_t

从源码中可以知道类在底层是以objc_class形式存在

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;
    Class superclass;
    cache_t cache;             // 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的指向,我们接下来看一下bits中存在着什么?

创建一个Person类如下:

@interface Person : NSObject{
    NSString *subject;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;

- (void)sayNB;
+ (void)say666;

@end


@implementation Person

- (instancetype)init{
    if (self = [super init]) {
        self.name = @"Cooc";
    }
    return self;
}

- (void)sayNB{
    
}
+ (void)say666{
    
}

@end

复制代码

截屏2021-06-19 下午4.44.56.png
找到首地址
0x100008380然后我们需要找到到class_data_bits_t bits;的偏移量,就可以取到bits
我们来看cache_t的结构

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;//8
    };
    ......
    }

复制代码

由共用体结构可知,cache_t占16字节

// Class ISA;   8
    Class superclass;   8
    cache_t cache;      16        // formerly cache pointer and vtable
    class_data_bits_t bits;   
复制代码

所以首地址移动32就可以到bits的位置。

截屏2021-06-19 下午4.57.48.png

struct class_data_bits_t {
    friend objc_class;
    ......
     class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    ......
}
复制代码

通过data()方法取出bits

properties

我们来看一下class_rw_t中有什么

struct class_rw_t {
......
 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};
        }
    }
......
}

复制代码

我们打印一下properties里面的内容

截屏2021-06-19 下午5.11.01.png
可以看到有两个属性name,和hobby

ivars

但是成员变量subject在哪里呢,属性=成员变量+setter+getter
我们看到class_rw_t有一个ro,我们来读取一下
截屏2021-06-19 下午5.24.52.png
class_ro_t中有一个ivars,打印如下,有三个成员变量

截屏2021-06-19 下午5.25.42.png
可以看到class_ro_t中还有一个baseProperties,我们也来打印一下

截屏2021-06-19 下午5.26.53.png

methods_t

我们来打印一下方法:

截屏2021-06-20 上午9.56.34.png

有6个方法,我们分别打印出来

截屏2021-06-20 上午9.58.41.png

但是这个里面并没有我们的+ (void)say666这个➕方法。

对象方法和类方法在底层都是方法,我们知道对象方法是存在类中,那么类方法呢,类方法对于元类来说也是对象方法,存在于元类当中,在底层只有对象方法。(这就是为什么要存在一个元类)
我们首先拿到metaclass的地址,然后打印里面的methods如下:

截屏2021-06-20 上午10.13.20.png

得出如下关系图:

截屏2021-06-20 上午10.31.34.png

typedef struct objc_class *Class;

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

struct objc_class : objc_object {
.....
}
复制代码

万物皆对象。

从runtime角度来分析

runtime已经提供给我们很多有用的api,我们可以根据api来验证上面的图片。

@interface Person : NSObject
{
NSString *subject;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;

- (void)sayHello;
+ (void)sayHappy;

@end

@implementation Person

- (instancetype)init{
    if (self = [super init]) {
        self.name = @"Cooc";
    }
    return self;
}

- (void)sayHello{
    
}
+ (void)sayHappy{
    
}

@end
复制代码

获取对象方法


void lgObjc_copyMethodList(Class pClass){
    unsigned int count = 0;
    Method *methods = class_copyMethodList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Method const method = methods[i];
        //获取方法名
        NSString *key = NSStringFromSelector(method_getName(method));
        
        LGLog(@"Method, name: %@", key);
    }
    free(methods);
}
 Person *person = [Person alloc];
 Class pClass     = object_getClass(person);
 lgObjc_copyMethodList(pClass);
 const char *className = class_getName(pClass);
 Class metaClass = objc_getMetaClass(className);
 NSLog(@"*************");
 lgObjc_copyMethodList(metaClass);
 
 /***** 打印结果
Method, name: sayHello
Method, name: hobby
Method, name: setHobby:
Method, name: init
Method, name: name
Method, name: .cxx_destruct
Method, name: setName:
2021-06-20 10:46:08.432824+0800 002-类方法归属分析[10011:757512] *************
Method, name: sayHappy
***/
            
复制代码
void lgClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHello));
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));

//    - (void)sayHello;
//    + (void)sayHappy;
    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

//  0x1000081c8-0x0-0x0-0x100008160
复制代码

我们来看一下class_getClassMethod的源码:

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

    return class_getInstanceMethod(cls->getMeta(), sel);
}
复制代码

所以底层实现中是不区分- +方法的,都是方法,只有对象方法,类方法对于元类来说也是对象方法。

当我们打印方法的实现的时候我们发现

void lgIMP_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));// 0
    // sel -> imp 方法的查找流程 imp_farw
    IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy)); // 0
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));

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

}

//0x100003b50-0x7fff204805c0-0x7fff204805c0-0x100003b60
复制代码

imp2、imp3我们没有实现,但是也是有值, 我们来打印一下
截屏2021-06-20 上午10.53.55.png
这是为什么呢,我们来看一下class_getMethodImplementation的源码

IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;

    if (!cls  ||  !sel) return nil;

    lockdebug_assert_no_locks_locked_except({ &loadMethodLock });

    imp = lookUpImpOrNilTryCache(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);

    // Translate forwarding function to C-callable external version
    if (!imp) { //如果没有imp,则会返回_objc_msgForward
        return _objc_msgForward;
    }

    return imp;
}
复制代码

可以看到如果么有imp,则会返回_objc_msgForward,_objc_msgFordward接下来的流程又是什么样子的呢。我们能通过这个流程以后做些什么呢,怎么给业务赋能呢?,先留作思考。

补充

1.关于 v16@0:8
developer.apple.com/library/arc…

2.lldb使用命令解释

截屏2021-06-20 上午11.58.51.png

3.objc_setProperty

@interface Person : NSObject
{
    // STRING   int  double  float char bool
    NSString *hobby; // 字符串
    int a;
    NSObject *objc;  // 结构体
}

@property (nonatomic, copy) NSString *nickName;
@property (atomic, copy) NSString *acnickName;
@property (nonatomic) NSString *nnickName;
@property (atomic) NSString *anickName;
复制代码

clang查看

extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_LGPerson_setNickName_(LGPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _nickName), (id)nickName, 0, 1); }
复制代码

有一个这个方法:objc_setProperty,看objc的源码,没有看到调用,猜测应该是在llvm中进行了绑定,但是别的为什么没有这个方法呢,下载llvm,看到源码

截屏2021-06-20 下午12.01.39.png
是在有copy修饰的情况下,才会有这个objc_setProperty方法的调用,objc_setProperty在llvm中做了什么呢?

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享