OC类的原理之属性的底层实现

  • 上一篇文章我们分析了类的本质,在通过clang编译生成C++代码的过程中,你可能发现了属性的setter方法,有时候是通过内存平移赋值的,有时候会调用objc_setProperty,那么你可能会存在这样的疑问:
    • 为什么会出现不同的处理方式呢?
    • 编译器的底层是如何处理的呢?
  • 带着这些疑问,我们一起探索下编译器是如何处理底层的setter的方法的?

clang分析属性setter方法

  • LGPerson定义
@interface LGPerson : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) NSString *hobby;
@end
复制代码
  • 使用clang编译得到.cpp文件:clang -rewrite-objc main.m -o main.cpp
// name的getter方法
static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
// name的setter方法
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)) = name; }
// hobby的getter方法
static NSString * _I_LGPerson_hobby(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_hobby)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
// hobby的setter方法
static void _I_LGPerson_setHobby_(LGPerson * self, SEL _cmd, NSString *hobby) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _hobby), (id)hobby, 0, 1); }
复制代码
  • 仔细观察上面的底层实现代码不难发现:
    • namesetter方法是通过内存平移直接赋值的
    • hobbysetter方法中调用了objc_setProperty
  • 为什么会出现上述情况呢?由于这里是通过clang编译看到的结构,所以这个过程应该是发生在编译阶段,下面我们通过llvm的底层具体是如何实现的?

llvm调用分析

分析

  • 这里分析需要使用到llvm源码(llvm源码下载),由于源码比较大,就没有放到示例代码中,需要的可以自行下载
  • 打开llvm源码,搜索objc_setProperty

image.png

  • 这里可以逆向推导,看哪里调用了getSetPropertyFn

image.png

  • 再看哪里调用了GetPropertySetFunction
    • PropertyImplStrategy strategy(CGM, propImpl);:这是C++语法,创建了一个PropertyImplStrategy类型的变量strategy,并调用PropertyImplStrategy的构造函数初始化
    • 根据strategy.getKind()条件判断执行switch的哪个case分支
    • 所以这里重点看下PropertyImplStrategy的实现

image.png

  • PropertyImplStrategy实现:
    • StrategyKind中可以看出,GetSetPropertySetPropertyAndExpressionGet都会使用objc_setProperty
    • 从构造函数的实现中可以看出,如果IsCopyKind变量赋值为GetSetProperty
    • getKind()中,实际返回的就是这个kind
namespace {
  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
    };

    StrategyKind getKind() const { return StrategyKind(Kind); }
	// ...
    // 构造函数声明
    PropertyImplStrategy(CodeGenModule &CGM,
                         const ObjCPropertyImplDecl *propImpl);

  private:
    unsigned Kind : 8;
    unsigned IsAtomic : 1;
    unsigned IsCopy : 1;
    unsigned HasStrong : 1;

    CharUnits IvarSize;
    CharUnits IvarAlignment;
  };
}

// 构造函数实现
/// Pick an implementation strategy for the given property synthesis.
PropertyImplStrategy::PropertyImplStrategy(CodeGenModule &CGM,
                                     const ObjCPropertyImplDecl *propImpl) {
  const ObjCPropertyDecl *prop = propImpl->getPropertyDecl();
  ObjCPropertyDecl::SetterKind setterKind = prop->getSetterKind();

  IsCopy = (setterKind == ObjCPropertyDecl::Copy);
  IsAtomic = prop->isAtomic();
  // ...
  if (IsCopy) {
    Kind = GetSetProperty;
    return;
  }
  // ...
}
复制代码
  • 从上面的分析可以得出结论:
    • 使用copy修饰的属性,生成的setter方法会调用objc_setProperty

验证

  • LGPerson定义:
@interface LGPerson : NSObject
@property (nonatomic, strong) NSString *nsName;
@property (nonatomic, copy) NSString *ncName;
@property (atomic, strong) NSString *asName;
@property (atomic, copy) NSString *acName;
@end
复制代码
  • 使用clang编译得到.cpp文件:clang -rewrite-objc main.m -o main.cpp
// nsName的getter方法
static NSString * _I_LGPerson_nsName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nsName)); }
// nsName的setter方法
static void _I_LGPerson_setNsName_(LGPerson * self, SEL _cmd, NSString *nsName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nsName)) = nsName; }

// ncName的getter方法
static NSString * _I_LGPerson_ncName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_ncName)); }
// ncName的setter方法
static void _I_LGPerson_setNcName_(LGPerson * self, SEL _cmd, NSString *ncName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _ncName), (id)ncName, 0, 1); }

// asName的getter方法
static NSString * _I_LGPerson_asName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_asName)); }
// asName的setter方法
static void _I_LGPerson_setAsName_(LGPerson * self, SEL _cmd, NSString *asName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_asName)) = asName; }

// acName的getter方法
static NSString * _I_LGPerson_acName(LGPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acName), 1); }
// acName的setter方法
static void _I_LGPerson_setAcName_(LGPerson * self, SEL _cmd, NSString *acName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acName), (id)acName, 1, 1); }
复制代码
  • 从上面的结果可以看出:
    • nsNameasName使用strong修饰,生成的setter方法使用内存平移的方法赋值
    • ncNameacName使用copy修饰,生成的setter方法调用objc_setProperty

llvm分析拓展

  • 细心的同学可能会发现,上面的验证结果中不仅使用到了objc_setPropertygetter方法中还使用到了objc_getProperty,那么什么时候会调用objc_getProperty呢?
  • 上面的分析流程总体看起来还是比较复杂的,在探索的过程中,我还发现了两一中方法,看起来更简单更直接一下,下面我们一起分析一下

分析

  • 这里还是使用llvm源码分析,打开llvm源码,搜索objc_setProperty,你会发现还有这样的实现方式:

image.png

  • 这里是直接使用字符串的形式进行拼接,看起来和clang编译出的C++代码更为相似

image.png

  • 看下这段代码的具体实现:
void RewriteModernObjC::RewritePropertyImplDecl(ObjCPropertyImplDecl *PID,
                                          ObjCImplementationDecl *IMD,
                                          ObjCCategoryImplDecl *CID) {
  // ...
  unsigned Attributes = PD->getPropertyAttributes();
  if (mustSynthesizeSetterGetterMethod(IMD, PD, true /*getter*/)) {
    // 判断是否生成getProperty(条件:不是nonatomic且用retain或copy修饰)
    bool GenGetProperty =
        !(Attributes & ObjCPropertyAttribute::kind_nonatomic) &&
        (Attributes & (ObjCPropertyAttribute::kind_retain |
                       ObjCPropertyAttribute::kind_copy));
    std::string Getr;
    // ...
    Getr += "{ ";
    // Synthesize an explicit cast to gain access to the ivar.
    // See objc-act.c:objc_synthesize_new_getter() for details.
    if (GenGetProperty) { // 使用objc_getProperty
      // return objc_getProperty(self, _cmd, offsetof(ClassDecl, OID), 1)
      // 这里省略了返回类型_TYPE的处理...
      Getr += "return (_TYPE)";
      Getr += "objc_getProperty(self, _cmd, ";
      RewriteIvarOffsetComputation(OID, Getr);
      Getr += ", 1)";
    }
    else // 使用内存平移的方式取值
      Getr += "return " + getIvarAccessString(OID);
    Getr += "; }";
    InsertText(startGetterSetterLoc, Getr);
  }

  if (PD->isReadOnly() ||
      !mustSynthesizeSetterGetterMethod(IMD, PD, false /*setter*/))
    return;

  // Generate the 'setter' function.
  std::string Setr;
  // 判断是否生成setProperty(条件:用retain或copy修饰)
  bool GenSetProperty = Attributes & (ObjCPropertyAttribute::kind_retain |
                                      ObjCPropertyAttribute::kind_copy);
  // ...
  Setr += "{ ";
  // Synthesize an explicit cast to initialize the ivar.
  // See objc-act.c:objc_synthesize_new_setter() for details.
  if (GenSetProperty) { // 使用objc_setProperty
    Setr += "objc_setProperty (self, _cmd, ";
    RewriteIvarOffsetComputation(OID, Setr);
    Setr += ", (id)";
    Setr += PD->getName();
    Setr += ", ";
    if (Attributes & ObjCPropertyAttribute::kind_nonatomic) // 是否是nonatomic
      Setr += "0, ";
    else
      Setr += "1, ";
    if (Attributes & ObjCPropertyAttribute::kind_copy) // 是否是copy
      Setr += "1)";
    else
      Setr += "0)";
  }
  else { // 使用内存平移方式赋值
    Setr += getIvarAccessString(OID) + " = ";
    Setr += PD->getName();
  }
  Setr += "; }\n";
  InsertText(startGetterSetterLoc, Setr);
}
复制代码
  • 这里使用到了ObjCPropertyAttribute,这里定义了所有的属性修饰值,看下都有哪些值:
namespace ObjCPropertyAttribute {
enum Kind {
  kind_noattr = 0x00,
  kind_readonly = 0x01,
  kind_getter = 0x02,
  kind_assign = 0x04,
  kind_readwrite = 0x08,
  kind_retain = 0x10,
  kind_copy = 0x20,
  kind_nonatomic = 0x40,
  kind_setter = 0x80,
  kind_atomic = 0x100,
  kind_weak = 0x200,
  kind_strong = 0x400,
  kind_unsafe_unretained = 0x800,
  /// Indicates that the nullability of the type was spelled with a
  /// property attribute rather than a type qualifier.
  kind_nullability = 0x1000,
  kind_null_resettable = 0x2000,
  kind_class = 0x4000,
  kind_direct = 0x8000,
  // Adding a property should change NumObjCPropertyAttrsBits
  // Also, don't forget to update the Clang C API at CXObjCPropertyAttrKind and
  // clang_Cursor_getObjCPropertyAttributes.
};
}
复制代码
  • 从上面的分析可以得到如下结论:
    • 使用objc_getProperty需满足两个条件:
      • 不能使用nonatomic修饰
      • 必须使用retaincopy修饰
    • 使用objc_setProperty需要满足条件:
      • 使用retaincopy修饰
    • 否则,使用内存平移的方式取值或者赋值

验证

  • LGPerson定义:
@interface LGPerson : NSObject
@property (nonatomic, strong) NSString *nsName;
@property (nonatomic, copy) NSString *ncName;
@property (atomic, strong) NSString *asName;
@property (atomic, copy) NSString *acName;
@end
复制代码
  • 使用clang编译得到.cpp文件:clang -rewrite-objc main.m -o main.cpp
// nsName的getter方法
static NSString * _I_LGPerson_nsName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nsName)); }
// nsName的setter方法
static void _I_LGPerson_setNsName_(LGPerson * self, SEL _cmd, NSString *nsName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nsName)) = nsName; }

// nrName的getter方法
static NSString * _I_LGPerson_nrName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nrName)); }
// nrName的setter方法
static void _I_LGPerson_setNrName_(LGPerson * self, SEL _cmd, NSString *nrName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _nrName), (id)nrName, 0, 0); }

// ncName的getter方法
static NSString * _I_LGPerson_ncName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_ncName)); }
// ncName的setter方法
static void _I_LGPerson_setNcName_(LGPerson * self, SEL _cmd, NSString *ncName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _ncName), (id)ncName, 0, 1); }

// asName的getter方法
static NSString * _I_LGPerson_asName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_asName)); }
// asName的setter方法
static void _I_LGPerson_setAsName_(LGPerson * self, SEL _cmd, NSString *asName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_asName)) = asName; }

// arName的getter方法
static NSString * _I_LGPerson_arName(LGPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _arName), 1); }
// arName的setter方法
static void _I_LGPerson_setArName_(LGPerson * self, SEL _cmd, NSString *arName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _arName), (id)arName, 1, 0); }

// acName的getter方法
static NSString * _I_LGPerson_acName(LGPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acName), 1); }
// acName的setter方法
static void _I_LGPerson_setAcName_(LGPerson * self, SEL _cmd, NSString *acName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acName), (id)acName, 1, 1); }
复制代码
  • 从上面的结果可以看出:
    • objc_getProperty
      • arName使用atomicretain修饰,生成的setter方法调用objc_getProperty
      • acName使用atomiccopy修饰,生成的setter方法调用objc_getProperty
    • objc_setProperty
      • nrName使用nonatomicretain修饰,生成的setter方法调用objc_setProperty
      • ncName使用nonatomiccopy修饰,生成的setter方法调用objc_setProperty
      • arName使用atomicretain修饰,生成的setter方法调用objc_setProperty
      • acName使用atomiccopy修饰,生成的setter方法调用objc_setProperty
    • 其他都是使用内存平移的方式取值或赋值

总结

  • 使用非nonatomic修饰,且使用retaincopy修饰的属性生成的getter方法会调用objc_getProperty,否则使用内存平移的方式取值
  • 使用retaincopy修饰的属性生成的setter方法会调用objc_setProperty,否则使用内存平移的方式进行赋值
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享