已知JPerson类中有两个属性。通过OC对象本质文章介绍的配置,可把工程编译为cpp文件
@interface JPerson : NSObject
@property (nonatomic, strong) NSString *nickName;
@property (nonatomic, copy) NSString *name;
@end
通过查看.cpp文件的代码,我们可以在方法列表中查看到
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[9];
} _OBJC_$_INSTANCE_METHODS_JPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
9,
{{(struct objc_selector *)"say666", "v16@0:8", (void *)_I_JPerson_say666},
{(struct objc_selector *)"nickName", "@16@0:8", (void *)_I_JPerson_nickName},
{(struct objc_selector *)"setNickName:", "v24@0:8@16", (void *)_I_JPerson_setNickName_},
这里可以看到@16@0:8 这种样式的编码。在Xcode中通过command+shift+0打开iOS参考库,然后搜索ivar_getTypeEncoding,通过For possible values, see Objective-C Runtime Programming Guide> Type Encodings. 可以看到,
@代表id类型、:代表SEL、v表示void。那么 @16@0:8拆开解读,第一个@代表id类型,16代表了占用内存,第二个@代表了id,0代表了从0号位开始,:则是sel,8则是从8号位开始。通过反推,sel占8个字节,从8号位开始,占用内存正好是16。
static NSString * _I_JPerson_nickName(JPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_JPerson$_nickName)); }
static void _I_JPerson_setNickName_(JPerson * self, SEL _cmd, NSString *nickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_JPerson$_nickName)) = nickName; }
static NSString * _I_JPerson_name(JPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_JPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_JPerson_setName_(JPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct JPerson, _name), (id)name, 0, 1); }
通过上述代码可以看出nickName 和 name的 setter 方法不同,nickName直接是首地址加偏移赋值,而name则是使用了objc_setProperty 方法去赋值。因为setter 和 getter的初始化始于编译期,所以这里去LLVM查看一下。
LVVM下载链接
下载完lvvm的源码之后可以使用Visual Studio Code 直接打开lvvm源码。打开源码之后可以通过搜索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");
}
经过搜索查找发现getSetPropertyFn 方法中有创建objc_setProperty方法的流程,利用反推法再往上层查找getSetPropertyFn方法
llvm::FunctionCallee CGObjCMac::GetPropertySetFunction() {
return ObjCTypes.getSetPropertyFn();
}
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)) {
if (!AtomicHelperFn)
// If non-atomic, assignment is called directly.
EmitStmt(propImpl->getSetterCXXAssignment());
else
// If atomic, assignment is called via a locking api.
emitCPPObjectAtomicSetterCall(*this, setterMethod, ivar,
AtomicHelperFn);
return;
}
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 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;
}
}
经过一系列的反推查找操作,最终定位到generateObjCSetterBody方法中,而对于GetPropertySetFunction的操作才是发生于GetSetProperty,SetPropertyAndExpressionGet这两个条件中,条件来源于PropertyImplStrategy的kind,再去查找PropertyImplStrategy的赋值操作
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 getProperty/setProperty.
// TODO: we could actually use setProperty and an expression for non-atomics.
if (IsCopy) {
Kind = GetSetProperty;
return;
}
就会发现,当IsCopy时返回的kind类型是GetSetProperty,通过之前的查找可知GetPropertySetFunction的操作才是发生于GetSetProperty,SetPropertyAndExpressionGet这两个条件中。所以当属性声明使用copy时才会使用objc_setProperty方法对属性进行赋值。