iOS底层探索之对象原理(三)

前言

iOS底层探索之对象原理(二)我们了解到 isa是一个联合体位域,ISA_BITFIELD存储了类的一些信息,本文我们将继续探索isa是如何关联对象与类的呢?以及isa的走位分析

alloc流程

iOS底层探索之对象原理(一)我们探索了《alloc底层原理》知道给一个对象发送alloc消息,在底层源码会来到_objc_rootAlloc,然后进入callAlloc。但其实这里漏掉了一个过程,就是objc_alloc

dyld在加载Mach-O二进制文件的时候,会进行符号绑定的操作,也就是说sel_alloc绑定到了 objc_alloc上面去了。 这一步其实没有真正开源。

不过我们在 libobjc 源码中全局搜索 objc_alloc 即可发现如下代码:

static void 
fixupMessageRef(message_ref_t *msg)
{    
    msg->sel = sel_registerName((const char *)msg->sel);

    if (msg->imp == &objc_msgSend_fixup) { 
        if (msg->sel == SEL_alloc) {
            msg->imp = (IMP)&objc_alloc;
        } 
        /* 省略下面的代码 */
    } 
}

fixupMessageRef 的调用则是在 _read_images 也就是 dyld 读取我们的镜像文件的时候,什么意思呢,这里我们再看 _read_images 的源码:

#if SUPPORT_FIXUP
    // Fix up old objc_msgSend_fixup call sites
    for (EACH_HEADER) {
        message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
        if (count == 0) continue;

        if (PrintVtables) {
            _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
                         "call sites in %s", count, hi->fname());
        }
        for (i = 0; i < count; i++) {
            fixupMessageRef(refs+i);
        }
    }

    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif

从代码不难看出,我们在读取镜像文件的时候,判断如果需要 fixup ,如果需要的话,我们就调用 fixupMessageRef ,然后在 fixupMessageRef 内部,我们判断当前消息的 SEL 是否是 SEL_alloc,如果是的话就替换其 IMPobjc_alloc 。这一流程只会走一次,也就是说 objc_alloc 只会走一次。

用简单的流程图表示如下:
alloc符号绑定流程.png

isa的走位分析

我们都知道对象可以创建多个,但是类是否可以创建多个呢?
答案很简单,一个。那么如果来验证呢?

首先我们来张 isa 和 superclass 的走位图:
isa走位图.png

接着我们开始如下验证:

//MARK: - 分析类对象内存存在个数
void lgTestClassNum(){
    Class class1 = [LGPerson class];
    Class class2 = [LGPerson alloc].class;
    Class class3 = object_getClass([LGPerson alloc]);
    Class class4 = [LGPerson alloc].class;
    NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);
}

// 打印输出如下:
0x100002108-
0x100002108-
0x100002108-
0x100002108

所以我们就知道了类在内存中只会存在一份。

(lldb) x/4gx LGTeacher.class
0x100001420: 0x001d8001000013f9 0x0000000100b38140
0x100001430: 0x00000001003db270 0x0000000000000000
(lldb) po 0x001d8001000013f9
17082823967917874

(lldb) p 0x001d8001000013f9
(long) $2 = 8303516107936761
(lldb) po 0x100001420
LGTeacher

我们通过上面的打印,就发现 类的内存结构里面的第一个结构打印出来还是 LGTeacher,那么是不是就意味着 对象->类->类 这样的死循环呢?这里的第二个类其实是 元类。是由系统帮我们创建的。这个元类也无法被我们实例化。

也就是下面的这种关系: 对象 ——> 类对象 ——> 元类

那么好奇心来了,元类的 isa 又是什么呢,在Xcode测试有以下结果:

isa走位.jpeg

isa走位 & 继承关系结论

  • isa:对象 ——> 类对象 ——> 元类 ——> 根元类 ——> 根元类自己
  • superclass:NSObject 父类是nil 、根元类的父类是 NSObject

对象的本质

在我们认知里面,OC 对象的本质就是一个结构体,这个结论在 libobjc 源码的 objc-private.h 源文件中可以得到证实。

struct objc_object {
private:
    isa_t isa;

public:
    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();

    /* 省略其他的内容 */
}

而对于对象所属的类来说,我们也可以在 objc-runtime-new.h 源文件中找到

struct objc_class : objc_object {
    // 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,我们可以通过 LLDB 打印内存地址来验证:

很简单,我们只需要使用 clang 的一个命令来编译我们的 OC 源文件即可。

// 编译底层源码
clang -rewrite-objc main.m -o main.cpp

// 存在UIkit系统其他动态库引用问题 则使用:
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk ViewController.m
// xcrun xcode 命令
xcrun -sdk iphonesimulator clang -rewrite-objc ViewController.m
xcrun -sdk iphoneos clang -rewrite-objc ViewController.m

clang -rewrite-objc main.m -o main.cpp 这行命令会把我们的 main.m 文件编译成 C++ 格式,输出为 main.cpp。

cpp.png

通过观察,我们可以看到有一个 NSObject_IVARS 这个其实是 NSObject 里面的成员变量。

  • 成员变量 (不会生成 getter 和 setter)
  • 实例变量是一种特殊的成员变量,是由类声明而来
  • 属性 (LLVM 会帮我们自动生成 getter 和 setter )

附上isa初始化图

isa初始化.png

你可能感兴趣的:(iOS底层探索之对象原理(三))