iOS类的本质与底层探索

前言


首先,我们看下几个类型:NSObjectClassobjc_objectobjc_classid的联系与区别。

  • NSObject:OC中的基类,绝大多数类都继承NSObjectNSProxy也是基类哦~)
  • Class:NSObject的类型,在objc源码的NSObject.mm文件中可以看到
    + (Class)class {
        return self;
    }
    
  • objc_object:NSObject类在C++的底层实现的结构体名称,在生成的cpp文件中可以看到,它和NSObject是同一个东西,只是在不同语言环境的名字不同。objc_object结构体里面,只有一个成员变量isa指针。
    /// Represents an instance of a class.
    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    
    /// A pointer to an instance of a class.
    typedef struct objc_object *id;
  • objc_class:Class在C++底层实现的结构体名称,在生成的cpp文件中可以看到,它和Class是同一个东西,只是在不同语言环境的名字不同。
    typedef struct objc_class *Class;
  • id:OC环境中可以指向任何类型,并且不用带*号
    typedef struct objc_object *id;

isa与superClass探究


我们先看下类在内存中地址,是不是每创建一对象,都会创建一个新的与之对应的类。 

 Class cls1 = Person.class;
        Class cls2 = [Person alloc].class;
        Class cls3 = object_getClass([Person alloc]);
        Class cls4 = [Person alloc].class;
        NSLog(@"cls1 : %p \n cls1 : %p \n cls1 : %p \n cls1 : %p \n", cls1, cls2, cls3, cls4);

执行打印结果如下:

 也就是同样的类,在内存中只会有1份。

我们打印实例变量p的内存,我们知道前8位是isa指针

isa地址与ISA_MASK 0x00007ffffffffff8ULL 做与运算,可以拿到类对象的地址。 

 我们继续拿类的isa地址与ISA_MASK 0x00007ffffffffff8ULL  做与运算,看可以得到什么。

扫描二维码关注公众号,回复: 14221341 查看本文章

我们看到,最后打印的结果也是Person,但是两个Person的地址不一样,一个0x00000001000082a0,一个0x0000000100008278,也就是说这两个不是一个类型。第二个Person就是我们常说的元类。

那我们继续看下元类的isa指向哪里。

我们看到Perosn元类的isa打印出来是NSObject,但是地址和NSObject类对象地址不一样,我们在打印NSObject的元类地址,发现地址一致了,也就是说Person的元类的isa指向NSObject元类。Person继承自NSObject,那是不是说明直接指向父类的元类呢,我们再创建一个Student继承Person,再试一下。

 我们看到$8$10相同,也就是说,无论对象继承谁,自定义对象的元类的isa都指向根元类。

继续往下,我们看下NSObject元类的isa指向哪里。

上图可得出根元类的isa指向它自己。

继承关系
isa指向链我们看完了,再看下继承关系,元类也有继承吗,我们用以下代码测试以下: 

  NSObject *obj = [NSObject alloc];
        Class objCls = object_getClass(obj);
        Class objMetaCls = object_getClass(objCls);
        Class objRootMetaCls = object_getClass(objMetaCls);
        NSLog(@"\n\nNSObject实例对象: %p \n NSObject类对象: %p \n NSObject元类对象: %p \n NSObject根元类对象: %p \n ", obj, objCls, objMetaCls, objRootMetaCls);
            
        Class pMetaCls = object_getClass([Person class]);
        Class pMetaSuperCls = class_getSuperclass(pMetaCls);
        NSLog(@"\n\nPerson元类对象: %p \n Person元类对象的父类: %p \n ", pMetaCls, pMetaSuperCls);
            
        Class sMetaCls = object_getClass([Student class]);
        Class sMetaSuperCls = class_getSuperclass(sMetaCls);
        NSLog(@"\n\nStudent元类对象: %p \n Student元类对象的父类: %p \n ", sMetaCls, sMetaSuperCls);
            
        Class rootMetaSuperCls = class_getSuperclass(objRootMetaCls);
        NSLog(@"根元类对象的父类: %p", rootMetaSuperCls);

运行打印结果:

根据结果,我们看到Student元类的父类就是Person元类,Person元类的父类就是NSObject的元类,NSObject元类的父类就是NSObject类对象。
isa的流程图与继承流程图如下:

类的内存数据


OC的类在C++中是objc_class类型,我们在objc源码objc-runtime-new中找objc_class的结构。
我们找到1个结构

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

struct objc_object {
private:
    isa_t isa;
}

类结构里面有isa指针,superclasscachebitsisa我们已经分析过了,superclass就是父类,cache我们以后再说,我们接下来研究下bits
在此之前我们验证下superclass:

Person的父类是NSObject,没有问题。
要研究bits,首先我们需要知道bits在内存存储的位置,isa占8字节,superclass占8字节,那么cache占多少字节呢,我们点进去看下cache_t的结构:

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

再看下explicit_atomic实现

template <typename T>
struct explicit_atomic : public std::atomic<T> {
    explicit explicit_atomic(T initial) noexcept : std::atomic<T>(std::move(initial)) {}
    operator T() const = delete;
    
    T load(std::memory_order order) const noexcept {
        return std::atomic<T>::load(order);
    }
    void store(T desired, std::memory_order order) noexcept {
        std::atomic<T>::store(desired, order);
    }
    
    // Convert a normal pointer to an atomic pointer. This is a
    // somewhat dodgy thing to do, but if the atomic type is lock
    // free and the same size as the non-atomic type, we know the
    // representations are the same, and the compiler generates good
    // code.
    static explicit_atomic<T> *from_pointer(T *ptr) {
        static_assert(sizeof(explicit_atomic<T> *) == sizeof(T *),
                      "Size of atomic must match size of original");
        explicit_atomic<T> *atomic = (explicit_atomic<T> *)ptr;
        ASSERT(atomic->is_lock_free());
        return atomic;
    }
};

T泛型,这个变量的大小取决于泛型T的大小。uintptr_t指针占8字节,第二个变量共用体,mask_ttypedef uint32_t mask_t;,占4字节,uint16_t占2字节,结构体占8字节,_originalPreoptCache是个*也就是指针类型也是8字节,所以,cache_t一共占16字节。bits从32字节开始。
再看下怎么获取bits里面数据,在源码中可以找到data方法。

class_rw_t *data() const {
        return bits.data();
    }

类的属性


如我们想看类里面的属性怎么办呢?答:从class_rw_t结构里面找成员变量或者方法。

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

成员变量没找到,方法倒是看上去有一个,那我们接着调试。

class property_array_t : 
    public list_array_tt<property_t, property_list_t, RawPtr>
{
    typedef list_array_tt<property_t, property_list_t, RawPtr> Super;

 public:
    property_array_t() : Super() { }
    property_array_t(property_list_t *l) : Super(l) { }
};
template <typename Element, typename List, template<typename> class Ptr>
class list_array_tt {
    struct array_t {
        uint32_t count;
        Ptr<List> lists[0];

        static size_t byteSize(uint32_t count) {
            return sizeof(array_t) + count*sizeof(lists[0]);
        }
        size_t byteSize() {
            return byteSize(count);
        }
    };
}
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;

    uint32_t entsize() const {
        return entsizeAndFlags & ~FlagMask;
    }
    uint32_t flags() const {
        return entsizeAndFlags & FlagMask;
    }

    Element& getOrEnd(uint32_t i) const { 
        ASSERT(i <= count);
        return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
    }
    Element& get(uint32_t i) const { 
        ASSERT(i < count);
        return getOrEnd(i);
    }

    size_t byteSize() const {
        return byteSize(entsize(), count);
    }
    
    static size_t byteSize(uint32_t entsize, uint32_t count) {
        return sizeof(entsize_list_tt) + count*entsize;
    }

    struct iterator;
}

分析下property_array_t结构:是个数组,数组里面每个元素都是property_list_t类型,property_list_t也是个数组,因为entsize_list_tt里面有迭代器iteratorproperty_list_t数组里面的元素是property_t。大致结构:property_array_t = [property_list_t[property_t, property_t], property_list_t[property_t]]

 再看下property_t的结构:

struct property_t {
    const char *name;
    const char *attributes;
};

类的方法


属性看完了,再看下方法,在Person里面添加两个方法,一个实例方法,一个类方法。

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject
@property(nonatomic,strong)NSString *name;
@property(nonatomic,assign)int age;
@property(nonatomic,assign)float height;


-(void)saylloinstance;
+(void)saylloclass;
@end

NS_ASSUME_NONNULL_END

找方法同样我们看到class_rw_t里面有个methods方法,不出意外应该能拿到方法。

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

method_array_t的结构和property_array_t一样,都是二维数组,只不过最里面的对象换成了method_t

我们看到同样的操作,怎么最后打印不出来数据,是不是我们操作不对?这就要说下property_t里面有两个成员,打印的时候直接把两个成员打印出来,我们再看下method_t的结构里面没有这种成员,但是里面有一个结构体:

struct big {
    SEL name;
    const char *types;
    MethodListIMP imp;
};

 这里面成员能看到方法所有内容,是不是我们打印这个big就行呢?测试一下:

非常完美方法都打印出来了,但是自己观察,一共就3个方法,saylloclass没有打印出来,为什么呢?对象方法存在类结构中,那类方法会不会放在元类里面呢?类方法是存储在元类。
这也就是为什么iOS要设计元类的原因,因为OC中有实例方法和类方法,也就是所谓的减号方法和加号方法,但是在OC底层,是没有这个含义的,也就是所有方法都转化成同名函数,如果没有元类,我们写两个方法+(void)saylle和-(void)saylle,这两个方法都存储在类里面,那我们执行方法的时候,根据函数名,就无法确定到底是执行实例方法还是类方法了,所以设计元类把两种方法分离开。

类的成员变量


class_ro_t演变
在WWDC视频中,我们可以详细看到class_ro_t演变优化过程,也知道成员变量存在class_ro_t中。在Person中添加一个成员变量hobby

//
//  Person.h
//  SXObjcDebug
//
//  Created by wangyun on 2022/4/17.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject
{
    NSString *hobby;    
}
@property(nonatomic,strong)NSString *name;
@property(nonatomic,assign)int age;
@property(nonatomic,assign)float height;


-(void)saylloinstance;
+(void)saylloclass;
@end

NS_ASSUME_NONNULL_END

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

调试验证:

 

总结


类的整体结构

ro,rw,rwe

class_ro_t是在编译的时候生成的。当类在编译的时候,类的属性,实例方法,协议这些内容就存 在class_ro_t这个结构体里面了,这是一块纯净的内存空间,不允许被修改。

class_rw_t是在运行的时候生成的,类一经使用就会变成class_rw_t,它会先将class_ro_t的内 容"拿"过去,然后再将当前类的分类的这些属性、方法等拷⻉到class_rw_t里面。它是可读写的。

class_rw_ext_t可以减少内存的消耗。苹果在wwdc2020里面说过,只有大约10%左右的类需要动 态修改。所以只有10%左右的类里面需要生成class_rw_ext_t这个结构体。这样的话,可以节约很 大一部分内存。

class_rw_ext_t生成的条件:
第一:用过runtime的Api进行动态修改的时候。 第二:有分类的时候,且分类和本类都为非懒加载类的时候。实现了+load方法即为非懒加载类。

猜你喜欢

转载自blog.csdn.net/wywinstonwy/article/details/124317669