iOS底层

OC对象原理探究(上)

2021-07-09  本文已影响0人  浅墨入画
APP启动流程探索

创建空工程代码如下,并且添加符号断点命名如下图

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"alloc 探索");
    Person *p1 = [Person alloc];
    Person *p2 = [p1 init];
    Person *p3 = [p1 init];
}
添加符号断点

运行工程查看堆栈信息

app启动以及对象加载过程

红色为app启动过程: _dyld_start(dyld开始加载) -> dyld::main -> dyld_initialzeMainExecutable、ImageLoader...等等,表示主程序由_dyld_start开始到main等为启动做准备,包括加载动态库,共享内存,全局C++函数的析构,还有一系列的初始化,注册回调函数都在此步骤内完成。
蓝色为对象加载过程: App启动一系列函数 -> libSystem_initializer -> libdispatch_init -> GCD环境的准备 -> _objc_init

OC对象初始化

分析alloc源码之前,先来查看三个变量内容内存地址的区别:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"alloc 探索");
    Person *p1 = [Person alloc];
    Person *p2 = [p1 init];
    Person *p3 = [p1 init];
    NSLog(@"%@-%p",p1,p1);
    NSLog(@"%@-%p",p2,p2);
    NSLog(@"%@-%p",p3,p3);
}

<!-- 打印信息 -->
alloc 探索
2021-07-07 23:16:11.578015+0800 001-alloc&init探索[5266:540842] <Person: 0x600001994710>-0x600001994710
2021-07-07 23:16:11.578220+0800 001-alloc&init探索[5266:540842] <Person: 0x600001994710>-0x600001994710
2021-07-07 23:16:11.578378+0800 001-alloc&init探索[5266:540842] <Person: 0x600001994710>-0x600001994710
得出结论:
<!-- 查看指针地址 -->
Person *p1 = [Person alloc];
Person *p2 = [p1 init];
Person *p3 = [p1 init];
NSLog(@"%@-%p-%p",p1,p1,&p1);
NSLog(@"%@-%p-%p",p2,p2,&p2);
NSLog(@"%@-%p-%p",p3,p3,&p3);

<!-- 打印信息 -->
alloc 探索
2021-07-08 20:57:54.778216+0800 001-alloc&init探索[7619:869162] <Person: 0x600001720490>-0x600001720490-0x7ffeee444028
2021-07-08 20:57:54.778464+0800 001-alloc&init探索[7619:869162] <Person: 0x600001720490>-0x600001720490-0x7ffeee444020
2021-07-08 20:57:54.778699+0800 001-alloc&init探索[7619:869162] <Person: 0x600001720490>-0x600001720490-0x7ffeee444018
由上图指针地址0x7ffeee444028 0x7ffeee444020 0x7ffeee444018得出结论:
图形详解:内存、指针的关系
连续开辟,指向同一块空间

下面探索alloc做了什么?init做了什么?

探索源码的三种方法

这里使用的模拟器,也可以使用真机探索

方法一
image.png image.png image.png image.png image.png

这里看到了libobjc.A.dylib objc_alloc,也看到了接下来会调用的方法_objc_rootAllocWithZone,objc_msgSend,我们就找到了objc_alloc底层源码来自于哪个动态库,为向下探索提供了线索!

方法二:通过汇编流程查看
image.png image.png
方法三:直接通过已知符号断点设定,直接进入
image.png image.png image.png

直接锁定libobjc.A.dylib +[NSObject alloc],为找到objc_alloc底层源码来自于哪个动态库提供了线索!

汇编结合底层源码调试分析

苹果开源源码汇总: https://opensource.apple.com
Source Browser -> 找到objc4
这里查看的源码是objc4-818.2.tar.gz

image.png image.png image.png image.png image.png image.png
alloc + init 整体源码的探索流程
alloc + init 整体源码的探索流程

编译器优化

编译器优化设置 BuildSetting -> Optimization level(GCC_OPTIMIZATION_LEVEL)指定生成的代码针对速度和二进制大小进行优化的程度

设置 参数
None[-O0] 编译器不会优化代码。编译器的目标是蒋迪编译成本并使调试产生预期的结果,通常在Debug模式下使用。
Fast[-O,O1] 快速,优化编译器需要编译的时间更久,对大型函数需要更多的内存。编译器会尝试减少代码大小和执行时间,而不执行任何需要大量编译时间的优化。
Faster[-O2] 更快速,编译器执行几乎所有不涉及空间速度权衡的受支持优化。使用此设置,编译器不会执行循环展开或函数内联或寄存器命名,次设置会增加编译时间和生成代码的性能。
Fastest[-O3] 设置指定的所有优化,并打开函数内联和寄存器重命名选项,此设置可能会产更大的二进制文件
Fastest,Smallest[-Os] 最快、最小,此设置启用所有通常不会增加代码大小的更快的优化,它还会做减少代码大小的进一步优化

创建新工程编写代码如下,并打开汇编模式Debug->Debug wrokflow->Always Show Disassembly

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

//MARK: - 测试函数
int lgSum(int a, int b){
    return a+b;
}

int main(int argc, char * argv[]) {
    int a = 10;
    int b = 20;
    int c = lgSum(a, b);
    NSLog(@"查看编译器优化情况:%d",c);
    return 0;
}
优化模式
image.png image.png

执行结果:优化掉了a、b两个变量,甚至连lgSum函数都被优化掉了,只剩下了一个结果0x1e存在w8寄存器中。

得出结论:

alloc的主线流程

alloc源码的核心操作主要分为三部分

static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ASSERT(cls->isRealized());

    // Read class's info bits all at once for performance
    //一次性读取类的位信息以提高性能
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;

    // 计算需要开辟的内存空间大小,开辟内存空间
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        // 对obj对象进行新地址的赋值
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone && fast) {
        // obj对象与cls对象进行绑定关联
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (fastpath(!hasCxxCtor)) {
        return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}
cls->instanceSize:计算所需内存大小

字节对齐及其原理

字节对齐优势以空间换取时间
#ifdef __LP64__
#   define WORD_SHIFT 3UL
#   define WORD_MASK 7UL
#   define WORD_BITS 64
#else
#   define WORD_SHIFT 2UL
#   define WORD_MASK 3UL
#   define WORD_BITS 32
#endif

//字节对齐算法
//define WORD_MASK = 7
static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

(8 + 7) & ~7 -> 15 & ~7 -> 8字节对齐,取8的整数,这里为什么是8的倍数?而不是16 32的倍数?因为只有8字节的指针,double类型,没有16字节或者32字节的数据类型

对象的内存对齐

<!-- Person.h -- >
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic) int age;
@property (nonatomic) long height;
@property (nonatomic, copy) NSString *nickName;

- (void)saySomething;

@end

NS_ASSUME_NONNULL_END

<!-- Person.m -- >
#import "Person.h"

@implementation Person
- (void)saySomething{
    NSLog(@"%s",__func__);
}
@end

<!-- main.m类中使用Person类 -- >
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [Person alloc];
        NSLog(@"%@",p);
    }
    return 0;
}
添加断点
<!-- 源码中查看ISA_MASK -->
#   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#     define ISA_MASK        0x007ffffffffffff8ULL
#     define ISA_MAGIC_MASK  0x0000000000000001ULL
#     define ISA_MAGIC_VALUE 0x0000000000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 0
#     define ISA_BITFIELD 
lldb调试
<!-- main.m类中使用Person类 -- >
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [Person alloc];
        p.name      = @"hello";
        p.nickName  = @"h";
        p.age       = 18;
        p.height    = 180;
        NSLog(@"%@",p);
    }
    return 0;
}

<!-- NSLog打印这一行添加断点,进行lldb调试 -->
// x/4gx 表示 以16进制的格式打印4段
(lldb)  x/4gx p
0x1006781a0: 0x011d800100008489 0x0000000000000012
0x1006781b0: 0x0000000100004010 0x00000000000000b4
(lldb) po 0x0000000000000012
18
(lldb) po 0x0000000100004010
hello
(lldb) po 0x00000000000000b4
180

<!-- 修改Person类height属性为Bool -- >
@property (nonatomic) long height;  -> @property (nonatomic) BOOL height;
<!-- 修改p对象height为1,打断点运行 -- >
p.height    = 1;
(lldb) x/4gx p
0x101b477a0: 0x011d800100008489 0x0000001200000001
0x101b477b0: 0x0000000100004010 0x0000000100004030
(lldb) po 0x0000001200000001
77309411329
(lldb) po 0x00000012
18
(lldb) po 0x00000001
1

通过上面打印我们发现int ageBOOL height放在同一处开辟的8字节内存空间中,这就是内存对齐,编译器进行了优化。

上一篇 下一篇

猜你喜欢

热点阅读