在《类的原理分析(上)——认识类的结构》中,我们认识了类的基本结构,本篇开始将介绍关于类的方法、属性、成员变量相关的知识。
一、成员变量和属性
1、成员变量和属性的定义
我们对成员变量的认识是,类或者扩展头文件中大括号包含的带 _ 的实例变量。
实际上,实例变量只是一个特殊的成员变量。
属性,实际上是一个成员变量 + setter-getter方法的组合,系统为我们提供了封装,可以命名为:@property(xxx , xxx)Type ivarName 的形式。
那么,一个属性的实现,底层到底经历了什么样的过程,让我们一起探索!
2、分析一个属性的C++代码
#pragma mark - 定义的类
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic) int age;
@end
#pragma mark - 编译为cpp之后的文件
static NSString * _Nonnull _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString * _Nonnull name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _name), (id)name, 0, 1); }
static int _I_LGPerson_age(LGPerson * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_LGPerson$_age)); }
static void _I_LGPerson_setAge_(LGPerson * self, SEL _cmd, int age) { (*(int *)((char *)self + OBJC_IVAR_$_LGPerson$_age)) = age; }
可以看到,类中的属性和方法,在转为c++代码之后,都分别生成了setter和getter方法。
① 使用关键字copy修饰的name,使用了objc_setProperty方法进行了赋值
② 未使用关键字修饰的age,是直接采用内存平移赋值操作
直接进行内存平移赋值的,这点我们可以理解,另外一种是使用objc_setProperty赋值的属性,由于涉及到一个编码问题,稍后我们介绍objc_setProperty的实现。
3、方法体的编码
{{(struct objc_selector *)"init", "@16@0:8", (void *)_I_LGPerson_init},
{(struct objc_selector *)"saySomething", "v16@0:8", (void *)_I_LGPerson_saySomething},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_LGPerson_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LGPerson_setName_},
{(struct objc_selector *)"age", "i16@0:8", (void *)_I_LGPerson_age},
{(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_LGPerson_setAge_},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_LGPerson_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LGPerson_setName_},
{(struct objc_selector *)"age", "i16@0:8", (void *)_I_LGPerson_age},
{(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_LGPerson_setAge_}}
OC中方法体的命名是遵循一定的规则的,如上所示,其中 "v20@0:8i16" 之类的,是代表特定的类型编码,苹果官网也做了相关说明 ( 我们可以通过 Command + Shift + 0,查看TypeEncoding,然后进入文档末端,进入苹果社区官网 ) ,我们直接看图。
那么,了解到这些规则,我们就随便找一个编码进行分析:如: v20@0:8i16
1、v 表示 void
2、20 表示 占用内存为20
3、@ 表示 id
4、0 表示 从0号位置开始
5、: 表示 SEL
6、8 表示 从8号位置开始
7、i 表示 int
8、16 表示 从16号为开始 (如果有参数,看参数类型,分配字节数,多参数以此类推)
了解了成员变量和属性的C++底层实现,我们就从更深层次探索objc_setProperty的实现过程,下面分别从runtime和llvm的源码着手分析。
二、属性的setter-getter方法分析
1、我们可以从runtime源码找到objc_Property函数
#pragma mark - 声明部分
OBJC_EXPORT void
objc_setProperty(id _Nullable self, SEL _Nonnull _cmd, ptrdiff_t offset,
id _Nullable newValue, BOOL atomic, signed char shouldCopy)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
#pragma mark - 实现部分
void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, true, false, false);
}
我们在进行赋值的时候,需要传入的一些参数,类, _cmd ,新值 ,偏移量,根据这些参数对一个类的属性进行存储,下面是具体实现部分。
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
#pragma mark - 如果偏移为0,第一次赋值,直接赋值
if (offset == 0) {
object_setClass(self, newValue);
return;
}
#pragma mark - 如果不是第一次赋值
id oldValue;
id *slot = (id*) ((char*)self + offset);
#pragma mark - copy的逻辑
if (copy) {
#pragma mark - 如果使用了copy关键字,浅层copy,地址
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
#pragma mark - 使用了mutablecopy关键字,进行了地址copy+指针
newValue = [newValue mutableCopyWithZone:nil];
} else {
#pragma mark - 其他情况,返回新值,引用计数 + 1
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
#pragma mark - 原子类型判断
if (!atomic) {
#pragma mark - 非原子类型,直接赋值
oldValue = *slot;
*slot = newValue;
} else {
#pragma mark - 原子类型,加锁操作
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
以上是对属性在runtime层次的分析,下面我们跟踪llvm,查看属性在编译层面做了哪些优化工作?
2、LLVM源码分析属性的实现过程
我们最初并不知道llvm的底层是怎么实现的,所以,就先从objc_setProperty这里开始探索吧!
-
2.1、从 objc_setProperty开始,逆向查找实现过程
#pragma mark - getGetPropertyFn的实现
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 = ...
llvm::FunctionType *FTy = ...(IdType, Params));
#pragma mark - 这里创建了一个objc_getProperty的方法
return CGM.CreateRuntimeFunction(FTy, "objc_getProperty");
}
#pragma mark - getSetPropertyFn的实现
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 = ...
llvm::FunctionType *FTy = ...(IdType, Params));
#pragma mark - 这里创建了一个objc_setProperty的方法
return CGM.CreateRuntimeFunction(FTy, "objc_setProperty");
}
-
2.2、然后查看 getSetPropertyFn, setter方法的执行过程分析
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");
}
-
2.3、根据getSetPropertyFn找到调用层函数,然后找到调用层逻辑代码GetPropertySetFunction的实现。
#pragma mark -调用层函数
llvm::FunctionCallee GetPropertySetFunction() override {
return ObjCTypes.getSetPropertyFn();
}
#pragma mark -调用层函数实现 generateObjCSetterBody
void
CodeGenFunction::generateObjCSetterBody(const ObjCImplementationDecl *classImpl,
const ObjCPropertyImplDecl *propImpl,
llvm::Constant *AtomicHelperFn) {
ObjCIvarDecl *ivar = propImpl->getPropertyIvarDecl();
ObjCMethodDecl *setterMethod = propImpl->getSetterMethodDecl();
// Just use the setter expression if Sema gave us one and it's
// non-trivial.
if (!hasTrivialSetExpr(propImpl)) {
...
}
PropertyImplStrategy strategy(CGM, propImpl);
#pragma mark - 这里是个条件判断,传入不同的值,分别会创建不同的方法
switch (strategy.getKind()) {
#pragma mark - Native类型
case PropertyImplStrategy::Native: {
// We don't need to do anything for a zero-size struct.
//成员变量大小为0字节,直接不创建setter-getter
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
// 所有atomic修饰的都必须是integer类型
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;
}
#pragma mark - GetSetProperty类型
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 {
#pragma mark - !!!!!!!!!!!!!!!!!!!!!!!!!!!
#pragma mark - 这里调用GetPropertySetFunction去创建一个方法
#pragma mark - !!!!!!!!!!!!!!!!!!!!!!!!!!!
setPropertyFn = CGM.getObjCRuntime().GetPropertySetFunction();
if (!setPropertyFn) {
CGM.ErrorUnsupported(propImpl, "Obj-C setter requiring atomic copy");
return;
}
}
...
return;
}
至此,我们可以推理出,llvm是根据不同的条件,执行不同的创建操作,关键看PropertyImplStrategy的值。
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
};
如上,在初期创建属性的时候,会根据修饰词的类型,首先判定属于哪种类型,然后分别生成不同的setter-getter方法,然后根据方法体PropertyImplStrategy,寻找最终调用者。
#pragma mark - 到这里算是找到了入口,包含了对一些关键字的判断,还有方法的生成策略等
PropertyImplStrategy::PropertyImplStrategy(CodeGenModule &CGM,
const ObjCPropertyImplDecl *propImpl) {
// prop 分隔符号
const ObjCPropertyDecl *prop = propImpl->getPropertyDecl();
//获取属性中Kind的类型
ObjCPropertyDecl::SetterKind setterKind = prop->getSetterKind();
#pragam mark - 判断,是否为copy、atomic、strong关键字修饰
IsCopy = (setterKind == ObjCPropertyDecl::Copy);
IsAtomic = prop->isAtomic();
HasStrong = false; // doesn't matter here.
#pragma mark 计算成员变量的大小和对齐
// 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;
#pragma mark **********************************************
#pragma mark 如果存在copy或nonatomic修饰的属性的处理都会定位到这里
#pragma mark ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
#pragma mark ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
#pragma mark ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
if (IsCopy) {
Kind = GetSetProperty;
return;
}
#pragma mark ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
#pragma mark ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
#pragma mark ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
#pragma mark 如果存在retain的处理逻辑,这里是MRC的处理过程,暂时不关注,涉及引用计数
// Handle retain.
if (setterKind == ObjCPropertyDecl::Retain) {
.......
}
#pragma mark 如果不是atomic类型的处理逻辑
// If we're not atomic, just use expression accesses.
if (!IsAtomic) {
Kind = Expression;
return;
}
// Properties on bitfield ivars need to be emitted using expression
// accesses even if they're nominally atomic.
if (ivar->isBitField()) {
Kind = Expression;
return;
}
......
三、总结llvm中属性的执行逻辑
✨ 上面我们采用了逆向推理的方式,查看了llvm中属性生成器的执行规则,再来正向去归纳下执行的过程。
① 在类中声明了@property,系统首先会根据后面修饰的类型,去判断是采用内存平移的方式赋值,还是通过objc_setProperty的方式。
② 如果是基本数据类型,计算机会通过内存平移的方式去赋值
③ 如果是对象类型,在编译中llvm中首先判断修饰的关键字,通过关键字去生成不同的setter方法(具体,凡是copy修饰的成员变量,都会执行objc_setProperty方法)
以上就是对系统setter-getter方法的探究过程,虽然探索过程比较麻烦,但是有这一层认识,后面再进行开发的过程中,会特别注意一些关键字的使用,合理使用可以避免内存使用不当造成的浪费,从深层次提升了自己的能力,这些探索,还只是初步尝试,关于更多的一些细节问题,等有时间再来探索吧!