OC底层原理之属性拓展

已知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方法对属性进行赋值。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容