iOS Kit

iOS底层原理08:类结构分析——bits属性

2021-06-27  本文已影响0人  黑白森林无间道

iOS底层原理07:类 & 类结构分析中我们对类结构有了大概的认识,本文主要探索objc_classbits属性,探索成员变量属性方法(对象方法、类方法)、协议等是如何存储的

【WWDC2020】类数据结构的优化

WWDC2020中关于数据结构的变化(Class data structures changes)视频地址
Object-C运行时会使用这些数据结构来跟踪类,👇下面我们先来了解 Clean MemoryDirty Memory的区别,方便我们更好得理解类数据结构的优化

Clean Memory 和 Dirty Memory的区别

Clean Memory

Dirty Memory

因此,苹果为了性能优化,类数据被分成两部分,可以保持清洁的数据越多越好,通过分离出那些永远不会更改的数据(即class_ro_t),可以把大部分的类数据存储为clean memory

类结构的优化

虽然class_ro_t这些数据足够我们使用类,但因为OC的动态特性,运行时需要跟踪每个类的更多信息,所以当一个类首次被使用runtime会为它分配额外的存储空间

优化之前,类结构如下👇


image.png

所有的都会链接成一个树状结构,通过使用First SubclassNext Sibling Class指针实现的,这允许运行时遍历当前使用的所有类。

【问题】 为什么class_rw_tclass_ro_t中都存在方法、属性呢?

在任何给定的设备中,都有许多类在使用,苹果开发人员在iPhone上的整个系统中测量了,class_rw_t结构占用了相当多的内存,【切记】我们在读取-编写部分需要这些东西,因为他们可以在运行时更改。
【问题】如果缩小class_rw_t的结构呢?

所以我们可以拆掉那些平时不用的部分,以达到内存优化,如下图所示:

image.png

我们可以通过heap来检查正在运行的进程所使用的堆内存

//查看微信进程,在活动监视器中查看,微信进程=591
heap 591 | egrep "class_rw|COUNT|class_ro"
image.png

总结

class_rw_t优化,其实就是对class_rw_t不常用的部分进行了剥离。如果需要用到这部分就从扩展记录中分配一个,滑到类中供其使用。现在大家对类应该有个更清楚的认识。

lldb调试分析

准备工作

定义两个类

@interface HTPerson : NSObject
{
    NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
- (void)sayHello;
+ (void)sayBye;
@end

@implementation HTPerson
- (void)sayHello {
    NSLog(@"%@",__func__);
}
+ (void)sayBye {
    NSLog(@"%@",__func__);
}
@end
@interface HTTeacher : HTPerson
@end

@implementation HTTeacher
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        HTPerson *p1 = [HTPerson alloc];
        HTPerson *p2 = [[HTPerson alloc] init];
        HTTeacher *t = [[HTTeacher alloc] init];
        NSLog(@"end--%@--%@", @"123", p1);
    }
    return 0;
}

lldb调试class_rw_t结构

断点调试步骤如下

image.png image.png image.png

属性探究

image.png image.png image.png
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
image.png

👇通过lldb断点来查看HTPerson的属性

image.png

通过class_rw_t -> properties()获取的属性列表,只存储了两个属性:nameage
【问题】我们声明的变量-hobby保存在哪里呢?

成员变量探究

属性列表中没有存储变量,观察发现class_rw_t还有一个获取class_ro_t *的方法const class_ro_t *ro() const {},成员变量会不会在class_ro_t中,源码查看class_ro_t结构体定义

    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    union {
        const uint8_t * ivarLayout;
        Class nonMetaclass;
    };

    explicit_atomic<const char *> name;
    // With ptrauth, this is signed if it points to a small list, but
    // may be unsigned if it points to a big list.
    void *baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
    _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
    // ...省略

class_ro_t是结构体类型,有一个const ivar_list_t * ivars;变量。从名字我们可以猜到里面应该存储变量。👇通过lldb验证如下图:

image.png

总结

方法探究

实例方法探究

lldb调试如下👇


image.png

从上图打印结构可以看出,类会为属性提供默认的set、get方法,
但是我们没有发现HTPerson的类方法+ (void)sayBye;

类方法探究

对象的方法是存储在中,那么类方法可能存储在元类中。按照这个思路探究下

image.png

从打印结果可以得知:类方法存储在元类方法列表

协议探索

@protocol HTPersonProtocol <NSObject>
- (void)protocolMethod1;
- (void)protocolMethod2;
@end

@interface HTPerson : NSObject<HTPersonProtocol>
{
    NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
@property(nonatomic, assign) NSInteger age;
- (void)sayHello;
+ (void)sayBye;
@end
image.png
上一篇 下一篇

猜你喜欢

热点阅读