一,WWDC2020对runtime的优化
-
视频的观看地址:developer.apple.com/videos/play… (最好用Safari浏览器打开)
-
LLVM源码地址:github.com/apple/llvm-…
看完视频后总结:本次改动不需要改动任何代码,也不用学习新的API,这次主要是runtime关于内存的优化。在这种环境下我们不用改APP也会运行得比之前更快更高效。 -
类结构
- Metaclass
- Superclass
- Flags
- Method cache
二, Clean Memory 和 Dirty Memory
-
Clean Memory
-
clean memory加载后不会发生改变的内存 -
class_ro_t就属于clean memory,因为它是只读的不会,不会对齐内存进行修改 -
clean memory是可以进行移除的,从而节省更多的内存空间,因为如果你有需要clean memory,系统可以从磁盘中重新加载 -
class_ro_t 内存结构
FlagsSizeNameMethodsProtocolsIvarsProperties
-
Dirty Memory
-
Dirty Memory指的是在程序运行中会发生改变的内存。 -
类的结构一经使用就会变成
Dirty Memory,因为运行时会向它写入新的数据。例如往类中添加方法又或者加载类的子类或父类, -
这里指的是
class_rw_t。
class_rw_t 结构
FlagsFirst SubclassNext Sibling ClassMethodsPropertiesProtocolsDemangled Name
First Subclass、Next Subling Class:包含了运行时才会生成的信息First Subclass、Next Subling Class,所有的类都会变成一个树状结构,就是通过First Subclass和Next Subling Class指针实现的,它允许运行时遍历当前使用的所有类Demangled Name:这个字段使用的频率是比较少的,swift中才会使用。
总结
dirty memory要比clean memory更有价值而且要多,只要进行运行它就必须一直存在,通过分离出那些不会被改变的数据,可以把大部分的类数据存储为clean memory,这样才能不断的提高程序的性能。
class_rw_t 优化
当一个类首次被使用时,runtime会为它分配额外的存储容量,运行时分配的存储容量就是class_rw_t。class_rw_t用于读取-编写数据,在这个数据结构中存储的是只有运行时才会生成的新数据 图解如下
问题:为什么方法,属性在class_ro_t中时,class_rw_t还要有方法,属性呢?
- 因为它们可以在运行时进行更改
- 当
category被加载时,它可以向类中添加新的方法 - 通过
runtime API手动向类中添加属性和方法 class_ro_t是只读的,所以我们需要在class_rw_t中来跟踪这些东西
拆分class_rw_t,提取其中的clean memory
在读取-编写属性和方法的时候,只有10%的类都需要修改或者添加的,那么90%类可以说是不被修改的,那么就可以对class_rw_t进行拆分
拆分如图
结果:这样class_rw_t的大小会减少一半
对那些需要修改内存的,需要额外信息的类,我们可以分配这些扩展记录中的一个,并把它滑到类中供其使用。图解如下
总结
- 当有类使用了
category的时候,那么此时的类就有了class_rw_t的结构,如果未使用分类,那么类就是一个单纯的class_ro_t的结构。 - 类结构的优化其实最要是分离出
class_ro_t和class_rw_t优化,其实就是对class_rw_t不常用的部分进行了剥离。如果需要用到这部分就从扩展记录中分配一个,滑到类中供其使用
二,变量
成员变量和实例变量
- 在
Objective-C中写在类声明的大括号中的变量称之为成员变量,例如int a,NSObject *obj - 成员变量用于类内部,无需与外界接触的变量
实例变量
- 变量的数据类型不是
基本数据类型且是一个类则称这个变量为实例变量,例如NSObject *obj - 成员变量包含实例变量
成员变量和属性的区别
- 成员变量:在底层没有其他操作只是变量的声明
- 属性:系统会自动在底层添加了
_属性名变量,同时生成setter和getter方法
属性存放
- 探索源码
struct 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;
三,编码
SEL和IMP关系
SEL:方法编号IMP:函数指针地址
官方类型编码
-
Objective-C不支持long double类型,@encode(long double)返回d,和double类型的编码值一样 -
类型编码图的获取途径:打开
xcode-->command+shift+0--> 搜索ivar_getTypeEncoding--> 点击Type Encodings
2021-07-31 22:40:39.479316+0800 alloc_探索[3177:69006] char --> c
2021-07-31 22:40:39.479534+0800 alloc_探索[3177:69006] int --> i
2021-07-31 22:40:39.479708+0800 alloc_探索[3177:69006] short --> s
2021-07-31 22:40:39.479851+0800 alloc_探索[3177:69006] long --> q
2021-07-31 22:40:39.480077+0800 alloc_探索[3177:69006] long long --> q
2021-07-31 22:40:39.480296+0800 alloc_探索[3177:69006] float --> f
2021-07-31 22:40:39.480427+0800 alloc_探索[3177:69006] double --> d
- 对比表中和返回值和编码类型一致
四,objc_setProperty 和 内存偏移
- 定义NBPerson类
@interface NBPerson : NSObject
{
NSString * newName;
NSObject * objc;
}
@property(nonatomic, copy)NSString * name;
@property(nonatomic,strong)NSString * nickName;
@property(nonatomic,assign)NSInteger age;
@end
@implementation NBPerson
@end
- 生成
.cpp文件,并查找。
// @implementation NBPerson
static NSString * _I_NBPerson_name(NBPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_NBPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_NBPerson_setName_(NBPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct NBPerson, _name), (id)name, 0, 1); }
static NSString * _I_NBPerson_nickName(NBPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_NBPerson$_nickName)); }
static void _I_NBPerson_setNickName_(NBPerson * self, SEL _cmd, NSString *nickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_NBPerson$_nickName)) = nickName; }
static NSInteger _I_NBPerson_age(NBPerson * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_NBPerson$_age)); }
static void _I_NBPerson_setAge_(NBPerson * self, SEL _cmd, NSInteger age) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_NBPerson$_age)) = age; }
// @end
LWName属性底层是通过objc_setProperty实现的,LWNickname和age是通过内存偏移实现的 查找 llvm源码 llvm-project-next/clang/lib/CodeGen/CGObjCMac.cpp找到代码
llvm::FunctionCallee getSetPropertyFn() {
CodeGen::CodeGenTypes &Types = CGM.getTypes();
ASTContext &Ctx = CGM.getContext();
// void objc_setProperty (id, SEL, ptrdiff_t, id, bool, bool)
CanQualType IdType = Ctx.getCanonicalParamType(Ctx.getObjCIdType());
CanQualType SelType = Ctx.getCanonicalParamType(Ctx.getObjCSelType());
CanQualType Params[] = {
IdType,
SelType,
Ctx.getPointerDiffType()->getCanonicalTypeUnqualified(),
IdType,
Ctx.BoolTy,
Ctx.BoolTy};
llvm::FunctionType *FTy =
Types.GetFunctionType(
Types.arrangeBuiltinFunctionDeclaration(Ctx.VoidTy, Params));
return CGM.CreateRuntimeFunction(FTy, "objc_setProperty");
}
CGM.CreateRuntimeFunction(FTy, "objc_setProperty"),创建了objc_setProperty方法。由下层往上层推理,全局搜索getSetPropertyFn()找到源码
llvm::FunctionCallee GetPropertySetFunction() override {
return ObjCTypes.getSetPropertyFn();
}
- 在搜索GetPropertySetFunction()在CGObject.cpp中找到
PropertyImplStrategy strategy(CGM, propImpl);
switch (strategy.getKind()) {
case PropertyImplStrategy::Native: {//代码省略---
}
case PropertyImplStrategy::GetSetProperty:
case PropertyImplStrategy::SetPropertyAndExpressionGet: {
llvm::FunctionCallee setOptimizedPropertyFn = nullptr;
llvm::FunctionCallee setPropertyFn = nullptr;
if (UseOptimizedSetter(CGM)) {//代码省略---
}
else {
setPropertyFn = CGM.getObjCRuntime().GetPropertySetFunction();
if (!setPropertyFn) {
CGM.ErrorUnsupported(propImpl, "Obj-C setter requiring atomic copy");
return;
}
}
- 根据
switch条件PropertyImplStrategy类型调用GetPropertySetFunction() PropertyImplStrategy类型有两种GetSetProperty或者SetPropertyAndExpressionGet,下一步只要知道什么时候给策略赋值
/// Pick an implementation strategy for the given property synthesis.
PropertyImplStrategy::PropertyImplStrategy(CodeGenModule &CGM,
const ObjCPropertyImplDecl *propImpl) {
const ObjCPropertyDecl *prop = propImpl->getPropertyDecl();
ObjCPropertyDecl::SetterKind setterKind = prop->getSetterKind();
IsCopy = (setterKind == ObjCPropertyDecl::Copy);
IsAtomic = prop->isAtomic();
HasStrong = false; // doesn't matter here.
// Evaluate the ivar's size and alignment.
ObjCIvarDecl *ivar = propImpl->getPropertyIvarDecl();
QualType ivarType = ivar->getType();
auto TInfo = CGM.getContext().getTypeInfoInChars(ivarType);
IvarSize = TInfo.Width;
IvarAlignment = TInfo.Align;
// If we have a copy property, we always have to use setProperty.
// If the property is atomic we need to use getProperty, but in
// the nonatomic case we can just use expression.
if (IsCopy) {
Kind = IsAtomic ? GetSetProperty : SetPropertyAndExpressionGet;
return;
}
// Handle retain.
if (setterKind == ObjCPropertyDecl::Retain) {
// In GC-only, there's nothing special that needs to be done.
if (CGM.getLangOpts().getGC() == LangOptions::GCOnly) {
// fallthrough
// In ARC, if the property is non-atomic, use expression emission,
// which translates to objc_storeStrong. This isn't required, but
// it's slightly nicer.
} else if (CGM.getLangOpts().ObjCAutoRefCount && !IsAtomic) {
// Using standard expression emission for the setter is only
// acceptable if the ivar is __strong, which won't be true if
// the property is annotated with __attribute__((NSObject)).
// TODO: falling all the way back to objc_setProperty here is
// just laziness, though; we could still use objc_storeStrong
// if we hacked it right.
if (ivarType.getObjCLifetime() == Qualifiers::OCL_Strong)
Kind = Expression;
else
Kind = SetPropertyAndExpressionGet;
return;
// Otherwise, we need to at least use setProperty. However, if
// the property isn't atomic, we can use normal expression
// emission for the getter.
} else if (!IsAtomic) {
Kind = SetPropertyAndExpressionGet;
return;
// Otherwise, we have to use both setProperty and getProperty.
} else {
Kind = GetSetProperty;
return;
}
}
- LLVM源码流程:
objc_setProperty->getSetPropertyFn->GetPropertySetFunction->PropertyImplStrategy->IsCopy(判断copy关键字) - 结论:无论属性是否是原子性还是非原子性的,用到copy关键字修饰的属性setter方法底层都用
objc_setProperty实现,strong关键字无法通过最后得判断,需要通过首地址+内存偏移的方式实现。