WWDC 2020视频中有详细介绍html
在磁盘上,
App
二进制文件中类如图中MYClass
同样,包含指向元类、超类、方法缓存的指针以及指向存储额外信息的指针class_ro_t
。ro
表明只读,它包括像类的名称、方法、协议、实例变量的信息,Swift
和Objective-C
共享这一数据结构。c++
Clean Memory
是指加载后不会发生更改的内存,class_ro_t
就是Clean Memory
,它是只读的。Dirty Memory
是指在进程运行时发生更改的内存,类结构一经使用就会变成Dirty Memory
,由于运行时会向它写入新的数据,例如建立一个新的方法缓存并从类中指向它。Dirty Memory
比Clean Memory
要昂贵得多,只要进程在运行,它就必须一直存在。Clean Memory
能够进行移除从而节省更多的内存空间,由于若是你须要Clean Memory
系统能够从磁盘中从新加载。MacOS
能够选择换出Dirty Memory
但由于iOS
不使用swap
因此Dirty Memory
在iOS
中的代价很大。当一个类首次被使用,运行时会为他分配额外的存储容量,这个运行时分配的存储容量是class_rw_t
用于读取-编写数据。 例如全部的类都会连接生成一个树状结构,这是经过使用
First Subclass
和Next Sibling Class
指针实现的,这容许运行时遍历当前使用的全部类,这对于使方法缓存无效很是有用。面试
为何方法和属性在只读数据class_ro_t
中,class_rw_t
还要方法和属性呢?express
由于它们能够在运行时更改。当Category
被加载时 他能够向类中添加新的方法,由于class_ro_t
是只读的,咱们须要在class_rw_t
中追踪这些东西。缓存
可是class_rw_t
占用的是Dirty Memory
,这样作的话会占用至关多的内存。如何缩小这些结构呢?markdown
能够拆掉那些平时不用的部分,这样class_rw_t
就减小了一半。 对于那些确实须要额外信息的类,咱们能够分配这些扩展记录中的一个,并把它划到类中供其使用。大约90%的类历来不须要这些扩展数据。数据结构
注:只有
Swift
类会使用这个Demangled Name
字段,而且Swift
类并不须要这一字段,除非有东西询问他们的Objective-C
名称时才须要。app
@interface ABPerson : NSObject
{
//成员变量
NSString *hobby;
//实例变量(特殊的成员变量)
NSObject *objc;
}
@property (nonatomic,copy) NSString *nickName;
@property (nonatomic,strong) NSString *name;
@property (atomic,strong) NSString *aname;
@end
@implementation ABPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
ABPerson *p = [ABPerson alloc];
NSLog(@"Hello, World!");
}
return 0;
}
复制代码
编译成.cpp
文件命令:ide
clang -rewrite-objc main.m -o main.cpp
复制代码
ABPerson
结构体oop
struct ABPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS; //ISA
NSString *hobby;
NSObject *objc;
NSString *_nickName;
NSString *_name;
NSString *_aname;
};
复制代码
在结构体中,属性前面被加上了下划线,成员变量没有变。
nickName
的setter
方法:
static void _I_ABPerson_setNickName_(ABPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct ABPerson, _nickName), (id)nickName, 0, 1); }
复制代码
name
的setter
方法:
static void _I_ABPerson_setName_(ABPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_ABPerson$_name)) = name; }
复制代码
对比两个属性的setter
方法,nickName
的setter
方法会调用objc_setProperty
,而name
的setter
是直接经过内存平移赋值。
objc_setProperty
至关于一个封装,将寻找属性的代码封装起来,再调用具体的底层实现。
那么当调用setter
方法为何会定位到objc_setProperty
呢?这多是在编译阶段就完成的工做。
打开LLVM
工程搜索objc_setProperty
往上依次找方法的调用
查看
GetSetPropertyFn
的实现 返回了
setPropertyFn
在这里被调用,那么调用的条件是什么呢?
void CodeGenFunction::generateObjCSetterBody(const ObjCImplementationDecl *classImpl, const ObjCPropertyImplDecl *propImpl, llvm::Constant *AtomicHelperFn) {
省略部分代码
PropertyImplStrategy strategy(CGM, propImpl);//获取策略
switch (strategy.getKind()) {
case PropertyImplStrategy::Native: {
// We don't need to do anything for a zero-size struct.
if (strategy.getIvarSize().isZero())
return;
//下面这一段是在作内存平移
Address argAddr = GetAddrOfLocalVar(*setterMethod->param_begin());
LValue ivarLValue =
EmitLValueForIvar(TypeOfSelfObject(), LoadObjCSelf(), ivar, /*quals*/ 0);
Address ivarAddr = ivarLValue.getAddress(*this);
// Currently, all atomic accesses have to be through integer
// types, so there's no point in trying to pick a prettier type.
llvm::Type *bitcastType =
llvm::Type::getIntNTy(getLLVMContext(),
getContext().toBits(strategy.getIvarSize()));
// Cast both arguments to the chosen operation type.
argAddr = Builder.CreateElementBitCast(argAddr, bitcastType);
ivarAddr = Builder.CreateElementBitCast(ivarAddr, bitcastType);
// This bitcast load is likely to cause some nasty IR.
llvm::Value *load = Builder.CreateLoad(argAddr);
// Perform an atomic store. There are no memory ordering requirements.
llvm::StoreInst *store = Builder.CreateStore(load, ivarAddr);
store->setAtomic(llvm::AtomicOrdering::Unordered);
return;
}
case PropertyImplStrategy::GetSetProperty:
//省略不少个case
}
复制代码
条件是找到匹配的策略调用相应的case
enum StrategyKind {
/// The 'native' strategy is to use the architecture's provided
/// reads and writes.
Native,
/// Use objc_setProperty and objc_getProperty.
GetSetProperty,
/// Use objc_setProperty for the setter, but use expression
/// evaluation for the getter.
SetPropertyAndExpressionGet,
/// Use objc_copyStruct.
CopyStruct,
/// The 'expression' strategy is to emit normal assignment or
/// lvalue-to-rvalue expressions.
Expression
};
复制代码
PropertyImplStrategy::PropertyImplStrategy(CodeGenModule &CGM,
const ObjCPropertyImplDecl *propImpl) {
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;
//当是copy是使用GetSetProperty策略
if (IsCopy) {
Kind = GetSetProperty;
return;
}
省略部分代码
复制代码
当修饰属性为copy
时会调用objc_setProperty
在main.cpp
文件中,咱们看到了:
以
@16@0:8
为例,符号依次介绍: @
:是id
类型,是返回值类型 16
:这个编码所占用内存数 @
:方法参数,指方法的默认参数是id self
0
:表明self
从0
号位置开始 :
:表明SEL
8
:表明SEL
从8
号位置开始
不清楚对应符号什么意思能够查询Objective-C type encodings:
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];
NSLog(@"\n re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];
NSLog(@"\n re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
复制代码
isKindOfClass
会调用objc_opt_isKindOfClass
//x为真得可能性比较大
#define fastpath(x) (__builtin_expect(bool(x), 1))
//x为假的可能性比较大
#define slowpath(x) (__builtin_expect(bool(x), 0))
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
//objc2.0使用下面实现
#if __OBJC2__
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
//不知足调整,调用isKindOfClass
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
复制代码
分析:re1
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
obj是NSObject的类对象
cls是NSObject的元类,即根元类
tcls根元类的父类就是NSObject
otherClass是NSObject
因此 tcls == otherClass 为YES
复制代码
分析:re3
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];
obj是LGPerson的类对象
cls是LGPerson的元类
tcls是元类的父类就是NSObject的元类,即根元类
otherClass是LGPerson
因此 tcls == otherClass 为NO
复制代码
分析:re5
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
obj是NSObject的实例对象
cls是NSObject的类对象
tcls=cls即NSObject的类对象
otherClass是NSObject
因此 tcls == otherClass 为YES
复制代码
分析:re7
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];
obj是LGPerson的实例对象
cls是LGPerson的类对象
tcls=cls即LGPerson的类对象
otherClass是LGPerson
因此 tcls == otherClass 为YES
复制代码
分析:re二、 re4
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
复制代码
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
NSObject的元类是根元类和NSObject不是一个东西,返回NO
同理:
re4也是NO
复制代码
分析:re6
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
NSObject实例对象的类对象就是NSObject
因此re6为YES
复制代码
分析:re8
re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];
LGPerson实例对象的类对象就是LGPerson
因此re8为YES
复制代码
打印结果:
class_rw_t
,用于读取编写数据,经过拆分class_rw_t
,将不经常使用的放到class_rw_ext_t
,当须要的时候加进去,从而达到减小内存的做用setter
方法有两种方式,一种是经过内存平移赋值,一种是经过调用objc_setProperty
copy
时会调用objc_setProperty