ios 底层原理 02-对象的本质&isa关联类

基础知识

联合体

1.联合体可以定义多个不同类型的成员,联合体的内存大小由其中最大的成员的大小决定。
2.联合体中修改其中的某个变量会覆盖其他变量的值。
3.联合体所有的变量公用一块内存,变量之间互斥
联合体优缺点
优点:内存使用更为灵活,节省内存。
缺点:不够包容。

位域(Bit field)

有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。就可以用位域图片[1]-ios 底层原理 02-对象的本质&isa关联类-一一网
总结:Car1的内存大小是4字节,Car2的内存大小是1字节。1字节包含8位(bit),Car2中所有变量都是按 位(bit)存在这1字节中,具体的存放格式是从右往左往 0000 1111 依次存放front 、back、left 、right

clang 是什么

Clang 是一个C语言C++Objective-C语言的轻量级编译器,是由Apple主导编写的
Clang 主要用于把源文件编译成底层文件,比如把main.m 文件编译成main.cppmain.o或者可执行文件。便于观察底层的逻辑结构,便于我们探究底层。

clang 常见命令
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 main.m
// xcrun命令基于clang基础上进行了封装更好用
//3、模拟器编译
 xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 
//4、真机编译
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp 

 
复制代码

对象的本质

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

// 对象在底层的本质就是结构体
@interface LGPerson : NSObject
@property (nonatomic, strong) NSString *KCName;

@end

@implementation LGPerson

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}

复制代码

clang命令把 main.m 文件编译成 main.cpp
重要的源码如下

// NSObject
// objc : objc_object

#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson
typedef struct objc_object LGPerson;
typedef struct {} _objc_exc_LGPerson;
#endif

extern "C" unsigned long OBJC_IVAR_$_LGPerson$_kcName;
struct LGPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS; // ISA
	NSString *_KCName;
};

// @property (nonatomic, strong) NSString *kcName;

/* @end */


// @implementation LGPerson

// 方法 getter
static NSString * _I_LGPerson_kcName(LGPerson * self, SEL _cmd) {
    return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_kcName))
}

static void _I_LGPerson_setKcName_(LGPerson * self, SEL _cmd, NSString *kcName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_kcName)) = kcName; }
// @end
复制代码

源码分析:LGPerson底层是结构体LGPerson_IMPL结构体中有2个变量,其中KCName是自定义的属性,NSObject_IVARS就是 NSObject中isaLGPerson 继承于NSObject,意味着LGPerson也有NSObject所有的成员变量

NSObject的底层是NSObject_IMPL里面只有一个成员变量是Class isa。通常叫isa叫做isa指针,那么这里的Class应该是个指针类型,在main.cpp文件中全局搜索*Class。代码如下

//对象的底层实现
struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));

typedef struct objc_object *id;
typedef struct objc_selector *SEL;
};
复制代码

源码分析:

Class本质上是一个 objc_class类型的结构体指针,objc_class是所有类的底层实现。所以isa跟类信息应该有一些具体的关联

NSObject的底层实现和对象的底层实现有什么区别?两者成员变量的结构体都是Class isa, 所有对象的底层都是继承objc_object,在OC中基本上所有的对象都是继承NSObject,但是真正的底层实现是objc_object的结构体类型。

对象的本质补充

我们看到上面有 id SEL常用的id原来是一个objc_object结构体指针,这就解释了id修饰变量和作为返回值的时候为什么不加*,意料之外的是SEL也是结构体指针。
在前面的代码里面有

// 方法 getter
static NSString * _I_LGPerson_kcName(LGPerson * self, SEL _cmd) {
    return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_kcName))
}
// 方法 setter
static void _I_LGPerson_setKcName_(LGPerson * self, SEL _cmd, NSString *kcName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_kcName)) = kcName; }
// @end

复制代码

(char *)self + OBJC_IVAR_$_LGPerson$_kcName获取当前变量的位置代码,怎么去获取当前的变量呢?底层实现就是当前对象的首地址+变量的偏移值代码中只定义了一个局部变量 kcName,但是底层代码仅添加了一个变量。而定义的属性,底层自动添加了带_变量以及getset方法的实现

isa 结构

通过alloc流程 alloc –> _objc_rootAlloc –> callAlloc –> _objc_rootAllocWithZone –> _class_createInstanceFromZone,断点在 obj->initInstanceIsa,进入obj->initInstanceIsa

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}
 
复制代码

进入initIsa

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    isa_t newisa(0); // isa初始化

    if (!nonpointer) {
        newisa.setClass(cls, this);//如果是纯指针 isa被直接cls赋值
    } else { 
       ASSERT(!DisableNonpointerIsa);
       ASSERT(!cls->instancesRequireRawIsa());
#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else   
        newisa.bits = ISA_MAGIC_VALUE;
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }
    isa = newisa;
}
复制代码

进入isa_t

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

    uintptr_t bits;

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls;

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };

    bool isDeallocating() {
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() {
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif

    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};
复制代码

源码分析:isa_t是联合体。isa_t有两个变量 一个是bits,一个是cls。通过上面分析联合体是互斥的,那就意味着初始化isa有两种方式:
bits被赋值,cls 没有值或者被覆盖
cls 被赋值,bits没有值或者被覆盖
isa_t中还有一个结构体成员变量ISA_BITFIELD。这是一个宏对应有两个端,一个是__arm64__(iOS),一个是__x86_64__(macOS)。ISA_BITFIELD通过位域存储信息

变量名 值的含义
nonpointer 表示是否对isa指针开启指针优化 。0:纯isa指针;1:不只是类对象地址,isa中包含了类信息,对象的引用计数等等。
has_assoc 关联对象标志位 0:没有;1:存在。
has_cxx_dtor 该对象是否有C++或者Objc的析构器,如果有析构函数,则需要坐析构逻辑,如果没有,则可以更快的释放对象。
shiftcls 存储类指针的值。开启指针优化的情况下,在arm64架构中,有33位用于存储类指针。
magic 用于调试器判断当前对象是真的对象,还是没有初始化的空间。
weakly_referenced 用于表示对象是否被指向或曾将指向一个ARC的若变量,没有弱引用的对象可以更快释放。
deallocating 标志对象是否正在释放内存。
has_sidetable_rc 当对象引用计数大于10的时候,则需要家用该变量存储进位。
extra_rc 表示该对象的引用计数数值,实际上是引用计数值减1。例如:如果对象的引用计数为10,那么extra_rc为9;如果引用计数大于10,则需要用到上面的has_sidetable_rc。

isa结构总结

isa结构分析总结
isa分为nonpointer类型和非nonpointer非nonpointer类型只是一个纯指针nonpointer还包含了类的信息
isa联合体+位域的方式存储信息的。采用这种方式的有点就是节省大量内存。万物皆对象,只要是对象就有isa指针,大量的isa就占用了很多内存,联合体公用一块内存节省了部分内存,而位域更是在节省内存的基础上存储了信息,可以说isa指针的内存得到了充分的利用。

isa与Class地址的关系,如何关联上类的

在继续走一遍流程 首先定义一个LGPerson类,初始化[LGPerson alloc],流程如下:alloc –> _objc_rootAlloc –> callAlloc –> _objc_rootAllocWithZone –> _class_createInstanceFromZone–> obj->initInstanceIsa –>initIsa,现在探究isa到底怎么和类关联起来
图片[2]-ios 底层原理 02-对象的本质&isa关联类-一一网

图中显示 newisa.bits = ISA_MAGIC_VALUEISA_MAGIC_VALUE 是一个宏 ISA_MAGIC_VALUE = 0x001d800000000001,变量值改变的有bits = 8303511812964353cls = 0x001d800000000001nonpointer = 1magic = 59。我们恢复下赋值的过程。如下图

cls = 0x001d800000000001是因为给bits赋值的时候覆盖了cls,isa_t是联合体
第0位的1等于 nonpointer = 1
magic的值怎么算呢,按位计算从47位开始,长度是6位 结果就是111011,从第0个位置开始算111011的10进制就是59.
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享