前言
在上一篇文章类的探究分析(上)中,我们对类进行了初步的探究,得到了isa的走位图
,也拿到了类的属性
和方法
。在isa走位图
中我们提到元类
,那么为什么要有元类呢,属性
和实例变量
的区别是什么呢?我们拿到的属性中有@v:
等字符又是什么意思呢?本文将从这几个方面继续对类进行探究。
一、WWDC2020
WWDC2020
中,我们对类进行了新的认知:
从内存加载类的变化:
从内存中加载流程如图所示:
clean Memory
和 Dirty Memory
clean Memory
是加载后不会发生更改的内存。clean Memory
可以进行移除,从而节省更多空间。因为如果需要clean Memory
数据,可以从内存中去加载。Dirty Memory
是运行时会发生更改后的内存。类一经使用就变成了Dirty Memory
,因为运行时会向它写入新数据。例如创建方法缓存并从类中指向它。Dirty Memory
要比clean Memory
昂贵的多,只要进程在运行,就一直存在。
二、属性
和成员变量
- 我们先在
main.m
定义一个WSPerson
的类,然后再用clang
编译出一个main.cpp
文件:
// main.m
@interface WSPerson : NSObject {
// 成员变量
NSString *wsSubject;
int wsAge;
// WSTeacher *teacher; 实例变量
}
// 属性
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSString *nickName;
@end
@implementation WSPerson
// main.cpp
struct WSPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *wsSubject;
int wsAge;
NSString *_name;
NSString *_nickName;
};
// name get
static NSString * _I_WSPerson_name(WSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_WSPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
// name set
static void _I_WSPerson_setName_(WSPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct WSPerson, _name), (id)name, 0, 1); }
// nickName get
static NSString * _I_WSPerson_nickName(WSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_WSPerson$_nickName)); }
// nickName get
static void _I_WSPerson_setNickName_(WSPerson * self, SEL _cmd, NSString *nickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_WSPerson$_nickName)) = nickName; }
// {(struct objc_selector *)"name", "@16@0:8", (void *)_I_WSPerson_name}
// (struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_WSPerson_setName_}
@end
在底层,我们能看到定义的属性以成员变量
的形式在结构体WSPerson_IMPL
中,然后生成对应的setter
和getter
方法,而类的成员变量
只是以成员变量
存在结构体中。
- 结论:
-
- 属性 = 下划线成员变量 +
setter
+getter
。
- 属性 = 下划线成员变量 +
-
- 实例变量 = 特殊的成员变量(类的实例化)。 但这里发现个问题:
-
1.
nickName
的set
方法是根据首地址平移
赋值,但name
就变成了objc_setProperty
,这是什么,它和内存平移
有什么区别?
2. 类似@16@0:8
这种格式的数据是什么?
编码
类似@16@0:8
这种的编码,我们可以 cmd+shift+0
,打开苹果的开发文档,然后搜索ivar_getTypeEncoding
,再点击Discussion
中的 Type Encodings ,这里介绍了编码中相关字符的作用。
- 我们举个例子
@16@0:8
和v24@0:8@16
:
从图上我们更能清晰的了解编码的含义。
objc_setProperty
LLVM分析
- 先下载并编好llvm,然后再里面搜索
objc_setProperty
,在CGObjCMac.cpp
文件中查到了创建objc_setProperty
:
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");
}
- 这里返回的是创建
objc_setProperty
的方法,那么我们反推,这里创建,肯定有地方在调用,再去搜索getSetPropertyFn
方法,发现GetPropertySetFunction
这个方法的返回是getSetPropertyFn
类型,我们再继续查找,找到个这样的枚举:
switch (strategy.getKind()) {
case PropertyImplStrategy::Native: {
// size为0,返回
if (strategy.getIvarSize().isZero())
return;
Address argAddr = GetAddrOfLocalVar(*setterMethod->param_begin());
LValue ivarLValue =
EmitLValueForIvar(TypeOfSelfObject(), LoadObjCSelf(), ivar, /*quals*/ 0);
Address ivarAddr = ivarLValue.getAddress(*this);
llvm::Type *bitcastType =
llvm::Type::getIntNTy(getLLVMContext(),
getContext().toBits(strategy.getIvarSize()));
argAddr = Builder.CreateElementBitCast(argAddr, bitcastType);
ivarAddr = Builder.CreateElementBitCast(ivarAddr, bitcastType);
llvm::Value *load = Builder.CreateLoad(argAddr);
requirements.
llvm::StoreInst *store = Builder.CreateStore(load, ivarAddr);
store->setAtomic(llvm::AtomicOrdering::Unordered);
return;
}
case PropertyImplStrategy::GetSetProperty:
case PropertyImplStrategy::SetPropertyAndExpressionGet: {
llvm::FunctionCallee setOptimizedPropertyFn = nullptr;
llvm::FunctionCallee setPropertyFn = nullptr;
if (UseOptimizedSetter(CGM)) {
// 10.8 and iOS 6.0 code and GC is off
setOptimizedPropertyFn =
CGM.getObjCRuntime().GetOptimizedPropertySetFunction(
strategy.isAtomic(), strategy.isCopy());
if (!setOptimizedPropertyFn) {
CGM.ErrorUnsupported(propImpl, "Obj-C optimized setter - NYI");
return;
}
}
else {
setPropertyFn = CGM.getObjCRuntime().GetPropertySetFunction();
if (!setPropertyFn) {
CGM.ErrorUnsupported(propImpl, "Obj-C setter requiring atomic copy");
return;
}
}
- 从枚举内容都在
PropertyImplStrategy
类中,当值为GetSetProperty
和SetPropertyAndExpressionGet
时,会调用GetPropertySetFunction
,然后我们去查找这个类,里面有一个枚举StrategyKind
,在里面我们找到类GetSetProperty
和SetPropertyAndExpressionGet
:
class PropertyImplStrategy {
public:
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(CodeGenModule &CGM,
const ObjCPropertyImplDecl *propImpl);
- 在
PropertyImplStrategy
中,有一个赋值方法PropertyImplStrategy
,我们再来查看下:
if (IsCopy) {
Kind = GetSetProperty;
return;
}
if (setterKind == ObjCPropertyDecl::Retain) {
if (CGM.getLangOpts().getGC() == LangOptions::GCOnly) {
} else if (CGM.getLangOpts().ObjCAutoRefCount && !IsAtomic) {
if (ivarType.getObjCLifetime() == Qualifiers::OCL_Strong)
Kind = Expression;
else
Kind = SetPropertyAndExpressionGet;
return;
} else if (!IsAtomic) {
Kind = SetPropertyAndExpressionGet;
return;
} else {
Kind = GetSetProperty;
return;
}
}
objc_setProperty
的整个查找流程如下:
验证
- 从上述分析:
Kind = GetSetProperty
或者Kind=SetPropertyAndExpressionGet
,系统会调用objc_setProperty
,我们再来测试下:
// OC main.m
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSString *nickName;
@property (atomic, copy) NSString *hobby;
@property (atomic, strong) NSString *job;
@property (nonatomic, retain) NSString *rname;
@property (atomic, retain) NSString *rnickName;
@property (nonatomic) NSString *nonName;
@property (atomic) NSString *aName;
在WSPerson
定义这样的属性,然后编译成C++
查看:
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_WSPerson_setName_(WSPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct WSPerson, _name), (id)name, 0, 1); }
static NSString * _I_WSPerson_nickName(WSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_WSPerson$_nickName)); }
static void _I_WSPerson_setNickName_(WSPerson * self, SEL _cmd, NSString *nickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_WSPerson$_nickName)) = nickName; }
extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);
static NSString * _I_WSPerson_hobby(WSPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct WSPerson, _hobby), 1); }
static void _I_WSPerson_setHobby_(WSPerson * self, SEL _cmd, NSString *hobby) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct WSPerson, _hobby), (id)hobby, 1, 1); }
static NSString * _I_WSPerson_job(WSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_WSPerson$_job)); }
static void _I_WSPerson_setJob_(WSPerson * self, SEL _cmd, NSString *job) { (*(NSString **)((char *)self + OBJC_IVAR_$_WSPerson$_job)) = job; }
static NSString * _I_WSPerson_rname(WSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_WSPerson$_rname)); }
static void _I_WSPerson_setRname_(WSPerson * self, SEL _cmd, NSString *rname) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct WSPerson, _rname), (id)rname, 0, 0); }
static NSString * _I_WSPerson_rnickName(WSPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct WSPerson, _rnickName), 1); }
static void _I_WSPerson_setRnickName_(WSPerson * self, SEL _cmd, NSString *rnickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct WSPerson, _rnickName), (id)rnickName, 1, 0); }
static NSString * _I_WSPerson_nonName(WSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_WSPerson$_nonName)); }
static void _I_WSPerson_setNonName_(WSPerson * self, SEL _cmd, NSString *nonName) { (*(NSString **)((char *)self + OBJC_IVAR_$_WSPerson$_nonName)) = nonName; }
static NSString * _I_WSPerson_aName(WSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_WSPerson$_aName)); }
static void _I_WSPerson_setAName_(WSPerson * self, SEL _cmd, NSString *aName) { (*(NSString **)((char *)self + OBJC_IVAR_$_WSPerson$_aName)) = aName; }
也就是:
// 得到的结果
@property (nonatomic, copy) NSString *name; // objc_setProperty
@property (nonatomic, strong) NSString *nickName;
@property (atomic, copy) NSString *hobby; // objc_getProperty, objc_setProperty
@property (atomic, strong) NSString *job;
@property (nonatomic, retain) NSString *rname; // objc_setProperty
@property (atomic, retain) NSString *rnickName; // objc_getProperty, objc_setProperty
@property (atomic, copy) NSString *rhobby; // objc_getProperty objc_setProperty
@property (atomic, strong) NSString *rjob;
@property (nonatomic) NSString *nonName;
@property (atomic) NSString *aName;
结论:当属性用
copy
和retain
修饰时,会调用objc_setProperty
我们在刚才的打印中,发现有个objc_getProperty
,这个的出现又和什么有关呢,我们来继续探究下
objc_getProperty
通过上面的经验,我们再次在llvm
中查找:
- 1.找到创建
objc_getProperty
处:
llvm::FunctionCallee getGetPropertyFn() {
CodeGen::CodeGenTypes &Types = CGM.getTypes();
ASTContext &Ctx = CGM.getContext();
// id objc_getProperty (id, SEL, ptrdiff_t, bool)
CanQualType IdType = Ctx.getCanonicalParamType(Ctx.getObjCIdType());
CanQualType SelType = Ctx.getCanonicalParamType(Ctx.getObjCSelType());
CanQualType Params[] = {
IdType, SelType,
Ctx.getPointerDiffType()->getCanonicalTypeUnqualified(), Ctx.BoolTy};
llvm::FunctionType *FTy =
Types.GetFunctionType(
Types.arrangeBuiltinFunctionDeclaration(IdType, Params));
return CGM.CreateRuntimeFunction(FTy, "objc_getProperty");
}
- 2.查找
getGetPropertyFn
:
llvm::FunctionCallee GetPropertyGetFunction() override {
return ObjCTypes.getGetPropertyFn();
}
- 3.再查找
GetPropertyGetFunction
,找到枚举:
case PropertyImplStrategy::GetSetProperty: {
llvm::FunctionCallee getPropertyFn =
CGM.getObjCRuntime().GetPropertyGetFunction();
...
}
-
4.然后我们再去查看
PropertyImplStrategy
给kind
赋值处kind
为GetSetProperty
的地方和上面一致。 -
5.整体流程:
根据objc_setProperty
的测试,我们得出结论:
结论:当属性
元子特性为atomic时,无论是copy
还是retain
修饰时,都会调用objc_setProperty
- 总结如下图:
为什么要有元类
- 在上一篇文章中,我们在
实例方法
里没有找到类方法
,最后在元类
的实例方法
找到了这个类方法
。那么为什么会有元类呢?,我们来举个例子:
@interface WSPerson : NSObject {
NSString *subject;
}
@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSString *nickName;
- (void)sayNB;
+ (void)sayNB;
@end
然后我们用lldb
去一步步拿methods
,得到结果:
(lldb) p $6.get(0).big()
(method_t::big) $7 = {
name = "sayNB"
types = 0x0000000100003f79 "v16@0:8"
imp = 0x0000000100003cd0 (KCObjcBuild`-[WSPerson sayNB])
}
问题来了:
sayNB
到底是类方法
还是实例方法
?所以苹果为了解决这个坑,就创造了元类
。
类方法
其实也是实例方法
,是元类的实例方法
。