iOS 底层原理 iOS 进阶之路

OC底层原理八:剖析isa & clang的使用

2020-09-14  本文已影响0人  markhetao

OC底层原理 学习大纲

对象的本质

1. Clang探索

  • Clang 是一个由Apple主导编写,基于LLVMC/C++/Objective-C轻量级编译器。源代码发布于LLVM BSD协议下。 Clang将支持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字。
  • 它与GNU C语言规范几乎完全兼容(当然,也有部分不兼容的内容, 包括编译命令选项也会有点差异),并在此基础上增加了额外的语法特性,比如C函数重载 (通过__attribute__((overloadable))来修饰函数),其目标(之一)就是超越GCC

2. 操作指令:

//1、将 main.m 编译成 main.cpp
clang -rewrite-objc main.m -o main.cpp

//2、将 ViewController.m 编译成  ViewController.cpp
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk ViewController.m

clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk ViewController.m

//以下两种方式是通过指定架构模式的命令行,使用xcode工具 xcrun
//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 

3. 探索

@interface HTPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation HTPerson
@end

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

同时我们发现,HTPerson_IMPL struct 中有一个NSObject_IMPL struct。 这就是表明HTPerson继承自NSObject

不相信?

我们在main.m中创建一个HTCar类继承自HTPerson

@interface HTCar : HTPerson
@end

@implementation HTCar
@end

int main(int argc, const char * argv[]) {
    
    @autoreleasepool {
        
        HTPerson * person = [[HTPerson alloc]init];
        
        NSLog(@"%@", person);
    }
    return 0;
}

使用clangmain.m编译为main.cpp

clang -rewrite-objc main.m -o main.cpp

main.cpp中搜索HTCar:

image.png

服不服? 😼

结论

4. 探究属性get、set方法

其实上面已经有答案了。对象在底层将属性都进行了记录。并自动实现了他们的getset方法。

image.png

我们打开objc4源码。搜索objc_setProperty

image.png image.png

所有外层属性的set方法。都会来到objc_setProperty方法,调用了reallySetProperty实现set功能。

这是一个典型的封装设计思维。


isa

image.png

到这里,我们可以肯定对象在底层,是通过继承isa来继承父类信息

现在,让我们揭开isa的神秘面纱。

1. union联合体位域

首先了解union联合体位域,isa的类型结构就是union。

小案例
如果我们创建Car对象,我们需要控制它的前后左右4个方向。我们可以这样定义:

@interface Car : NSObject

@property(nonatomic, assign) BOOL front;   // 2字节
@property(nonatomic, assign) BOOL back;   // 2字节
@property(nonatomic, assign) BOOL left;    // 2字节
@property(nonatomic, assign) BOOL right;    // 2字节

@end

系统层面,我们会考虑极致的性能。用4位就实现前后左右的处理,每1位记录一个方向的信息。极大的节约内存空间

image.png
  • 2位更节省,每1位可记录2个信息.
  • 但使用4位存储,每1位独立记录1个信息。可以使用位运算来高效处理,在性能上更有优势)
    image.png

这就是我们要介绍的union联合体位域。

image.png

2. isa结构

我们在objc4源码中找到initIsa

image.png

发现isa的赋值是isa_t结构,进入查看:

image.png

发现isa_t就是使用的union联合体结构。

通常来说,isa指针占用内存大小是8字节,即64位。对于系统来说已经足够了。

进入ISA_BITFILED宏定义,可以看到isa全部结构。 庐山真面目揭开了。

isa结构图
  • nonpointer: 表示是否对 isa 指针开启 0:纯isa指针,1:不止是类对象地址,isa 中包含了类信息、对象的引用计数等

  • has_assoc: 关联对象标志位,0没有,1存在

  • has_cxx_dtor: 该对象是否有 C++Objc的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象

  • magic:用于调试器判断当前对象是真对象还是未初始化的空间

  • weakly_referenced: 对象是否被指向或者曾经指向一个 ARC弱变量, 没有弱引用的对象可以更快释放

  • deallocating:标志对象是否正在释放内存

  • has_sidetable_rc:当对象引用技术大于 10 时,则需要借用该变量存储进位

  • extra_rc:当表示该对象的引用计数值,实际上是引用计数值减 1。
    如: 如果对象的引用计数为 10,那么extra_rc 为 9。如果引用计数大于 10, 则需要使用到has_sidetable_rc

现在我们了解了isa的结构,让我们运行objc4源码来完整了解信息

3. 检验isa

#import "HTPerson.h"

int main(int argc, const char * argv[]) {
    
    @autoreleasepool {
        
        HTPerson * person = [[HTPerson alloc]init];
        
        NSLog(@"%@", person);
    }
    return 0;
}

进入alloc->_objc_rootAlloc->callAlloc->_objc_rootAllocWithZone-> _class_createInstanceFromZone,加入断点:

image.png

继续进入initInstanceIsa->initIsa,加断点:

image.png

需要确保clsHTPerson

SUPPORT_INDEXED_ISA宏定义:

image.png

我们现在是电脑端,所以是这个条件为false,宏的值为0

我们看到1 1101 1

image

这个59是在默认值中设定的。

为何要右移3位?
因为 (uintptr_t)cls是将cls初始化为uintptr_t格式。但是初始化时,前3位是标记符,shiftcls是从第四位才开始。所以要移除前三位。

image.png

我回到上一层_class_createInstanceFromZone,加断点。继续走。

image.png
  • 当我们对isa的结构完全熟悉后。就能理解为什么首地址符有时候打印不出类名了

  • 因为标记符可能存在数据,影响了地址的读取。类的信息只存储在isa的shiftcls中。

  • 我们可以手动左移右移,将前3后17位置的信息全部移除。这样就可以直接读取了。

image.png

拓展

runtime运行时object_getClass(perosn)返回的也是isa地址。

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

梳理了一份👉 【对象、类、isa 的逻辑关系】

拓展答疑:

  • 属性修饰符strongweakretaincopyassign:
    clang编译文件,打开cpp文件,可以发现:
  1. retaincopy都是调用了objc_setProperty。 不同的是objc_setProperty内部实现不同
    (详看objc4源码中的objc_setProperty代码)
  • copymutableCopy:是新开辟空间,旧值release;
  • 其他修饰类型:是新值retain,旧值release。
  1. strongassign类型都是直接使用地址进行赋值(通过对象地址偏移相应字节找到属性地址)

  2. 如果在set方法后加入断点,可以在汇编层看到所有属性赋值后,会调用objc_storeStrong

image.png image.png
运行代码,进入断点,可以看到:
image.png
(在所有赋值完成后,objc_storeStrong在最后执行一次)
  • objc4源码中查看objc_storeStrong代码。可以发现它内部就是对对象进行了retainrelease
    image.png

下一节:OC底层原理九:类的原理分析

上一篇 下一篇

猜你喜欢

热点阅读