OC底层-类和对象

类和对象

OC中的类

OC中.类基于C/C++的结构体.

image-20210511155149831.png
image-20210511155751921.png

通过查看NSObject的类定义,可以看到内部有一个Class isa的成员变量. 从Apple开放的objc源码来看,可以发现,Class类型是一个结构体指针

继续往下找.objc_class的内部实现,可以发现objc_class是继承于objc_object的结构体.而objc_object内部.只存放了isa变量.

image-20210511155853395.png

image-20210511160003551.png

isa

这里需要知道.64位系统前.isa是个普通指针.存放着类对象和元类对象地址值

64位系统中,isa是一个union(共用体).这里涉及到共用内存以及位域的概念.
对于共用体,个人理解不深...就不乱分析误导别人了.相关的内容,依据我个人的理解.我在对应成员后都加上注释了.直接获取isa的地址.是拿不到正确地址的.需要与 ISA_MASK做位运算&才能得到真正的isa地址.

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
# if __arm64__
    struct {
        uintptr_t nonpointer        : 1;        /*nonpointer 值为0代表普通指针.
                                                                                    存储Class, Meta-Class对象的内存地址
                                                                                    1代表优化过的union*/
        uintptr_t has_assoc         : 1;    // 是否有关联对象,影响释放效率
        uintptr_t has_cxx_dtor      : 1;    // 是否有析构函数,影响释放想绿
        uintptr_t shiftcls          : 33;   /* 存放地址值.存放着Class,Meta-Class的地址
             &ISA_MASK就是取的这个成员 根据与运算的规则. 类对象地址最后三位都是0*/
        uintptr_t magic             : 6;    // 分辨对象是否完场初始化
        uintptr_t weakly_referenced : 1;    // 是否有弱引用
        uintptr_t deallocating      : 1;    // 是否正在释放
        uintptr_t has_sidetable_rc  : 1;    // 引用计数是否过大,会指向引用表
        uintptr_t extra_rc          : 19;   // 引用计数
    };
 void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

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};
        }
    }
};
struct class_ro_t {
    void *baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
 }
  • class_ro_t (里面存放的都是一维数组.只读.包含类初始内容)
    • method_list_t * baseMethodList;
    • protocol_list_t * baseProtocols;
    • const ivar_list_t * ivars;
    • property_list_t *baseProperties;
  • method_array_t methods;
  • property_array_t properties;
  • protocol_array_t protocols;

class_rw_t中.存放了类的 成员变量, 协议, 属性以及方法. 其中.class_ro_t中存放的是类的初始内容.这些内容不可被更改.而 methods, properties, protocols都是二维数组,可读可写.包含类的初始,以及分类的内容.

之所以设置成二维数组,是为了实现动态添加相应的功能.

本类的方法一开始都放在class_ro_t中,当运行程序时,分类中的数据与class_ro_t 的数据会合并放到class_rw_t中.

关于分类的详细.由于篇幅过长.后续会在其他的文章进行分析.

关于method_t.应该会在消息发送机制(objc_msgsend)里面进行补充

总结

OC中的类.其实是一个结构体指针.指向的结构体类型为 objc_class.其内部存放了isa. 以及类成员变量, 协议, 属性以及方法.

OC中的对象

实例对象(Instace)

实例对象,顾名思义就是类的实例对象.当一个类的实例对象在堆中alloc时.内存中存放的是实例对象的isa以及成员变量.至于方法.是通过isa找到对应类对象.再寻找对应的方法进行调用

类对象(Class)

OC的类对象,其实我们上面已经讲过了.类对象存放了isa.成员变量列表.属性列表.方法列表.协议列表.但是值得注意的一点是,类对象中存放的方法为实例方法

元类对象(Metaclass)

元类对象.是类对象的元类对象.元类对象的结构与类对象基本一致.但是存放的是类的类方法

为什么说类对象存放的是实例方法.元类对象存放的是类方法.我们通过把OC代码编译成c++可以得到答案.

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx.m -o xxx.cpp

从编译得到的C++代码中可以看到. OC调用方法,其实是调用的 objc_msgSend().

image-20210512144339503.png

image-20210512145030282.png

看看objc_msgSend的部分代码,不难看出.调用方法时.会先判断传入的是否为nil,如果不是.
找到传入对象的isa.然后找到对应的类中寻找方法.

isa指向

image-20210512142915984.png

对象的isa ===> 类

类的isa ===> 元类(metaClass)

元类(metaClass)的isa ===>父类 ===> RootMetaClass(根元类)

RootMetaClass(根元类)的isa ===> RootMetaClass(根元类)

类对象的superClass ===> 父类 ===> rootClass(根类) ===> nil

元类的superClass ==> 父元类 ===> metaRootClass (根元类) ===> rootClass(根类) ===> nil

方法调用顺序(不涉及消息转发)

  • 调用实例对象方法时

    • 通过isa找到对象的类,然后查找类中的方法列表,

    • 如果找不到,通过superClass找到父类

    • 最终到根类.如果都找不到.程序崩溃(unrecognize selector)

  • 调用类方法时

    • 通过isa找到元类,查找类方法列表

    • 层层向上.到根元类.

    • 最终到根类.根元类isa指向根类.都找不到.崩溃

思考题:上述代码会不会崩溃

@interface Test : NSObject
+ (void)test;
@end

@implementation Test
@end

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

@implementation NSObject (Test)
- (void)test {
    NSLog(@"%s",__func__);
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [Test test];
    }
    return 0;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容