OC 类原理探索 系列文章
- OC 类原理探索:类的结构分析
- OC 类原理探索:类结构分析补充
- OC 类原理探索:属性的底层原理
一、成员变量和属性
main.m中创建SSLPerson类:
@interface SSLPerson : NSObject {
    NSString *_hobby;
    NSObject *_obj;
    int _age;
}
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, strong) NSString *name;
@property (atomic, copy) NSString *career;
@end
复制代码成员变量和实例变量
- 成员变量:像_hobby、_obj、_age这些在花括号中的被称为成员变量;
- 实例变量:像_hobby、_obj这些属于NSObject类或者继承于NSObject类的又被称为实例变量`;
属性
- 像nickName、name、career这些被@property修饰的称为属性。
获取类的属性和成员变量
下面这个方法可以打印类的所有成员变量和属性:
#ifdef DEBUG
#define SSLog(format, ...) printf("%s\n", [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#define SSLog(format, ...);
#endif
void logObjc_copyIvar_copyProperies(Class pClass){
    
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Ivar const ivar = ivars[i];
        //获取实例变量名
        const char*cName = ivar_getName(ivar);
        NSString *ivarName = [NSString stringWithUTF8String:cName];
        SSLog(@"class_copyIvarList:%@",ivarName);
    }
    free(ivars);
    unsigned int pCount = 0;
    objc_property_t *properties = class_copyPropertyList(pClass, &pCount);
    for (unsigned int i=0; i < pCount; i++) {
        objc_property_t const property = properties[i];
        //获取属性名
        NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
        //获取属性值
        SSLog(@"class_copyProperiesList:%@",propertyName);
    }
    free(properties);
}
复制代码成员变量和属性的区别
clang -rewrite-objc main.m -o main.cpp编译main.m,得到main.cpp文件:
extern "C" unsigned long OBJC_IVAR_$_SSLPerson$_nickName;
extern "C" unsigned long OBJC_IVAR_$_SSLPerson$_name;
struct SSLPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSString *_hobby;
    NSObject *_obj;
    int _age;
    NSString *_nickName;
    NSString *_name;
    NSString *_career;
};
// @property (nonatomic, copy) NSString *nickName;
// @property (nonatomic, strong) NSString *name;
// @property (atomic, copy) NSString *career;
static NSString * _I_SSLPerson_nickName(SSLPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SSLPerson$_nickName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_SSLPerson_setNickName_(SSLPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SSLPerson, _nickName), (id)nickName, 0, 1); }
static NSString * _I_SSLPerson_name(SSLPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SSLPerson$_name)); }
static void _I_SSLPerson_setName_(SSLPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_SSLPerson$_name)) = name; }
extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);
static NSString * _I_SSLPerson_career(SSLPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct SSLPerson, _career), 1); }
static void _I_SSLPerson_setCareer_(SSLPerson * self, SEL _cmd, NSString *career) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SSLPerson, _career), (id)career, 1, 1); }
复制代码- 成员变量_hobby、_obj、_age在底层只是单纯的成员变量,没有其他;
- 属性nickName、name、career在底层被编译成了带_的成员变量和相应的getter、setter方法。
我们还发现了一些问题:
- nickName和- career的- setter方法用到了- objc_setProperty函数赋值;
- name的- setter方法却用到了- self + OBJC_IVAR_$_SSLPerson$_name内存平移的方式赋值;
- 我们还看到了一些类型编码{"nickName", "@16@0:8", _I_SSLPerson_nickName}相关的东西,它们都代表什么呢?
接下来解决这些问题。
二、编码
官方类型编码表

查找方式:cmd + shift + 0 -> documentation -> 搜索ivar_getTypeEncoding -> 点击Type Encodings -> 官方编码地址。
代码获取类型编码
void sslTypes(void){
    NSLog(@"char --> %s",@encode(char));
    NSLog(@"int --> %s",@encode(int));
    NSLog(@"short --> %s",@encode(short));
    NSLog(@"long --> %s",@encode(long));
    NSLog(@"long long --> %s",@encode(long long));
    NSLog(@"unsigned char --> %s",@encode(unsigned char));
    NSLog(@"unsigned int --> %s",@encode(unsigned int));
    NSLog(@"unsigned short --> %s",@encode(unsigned short));
    NSLog(@"unsigned long --> %s",@encode(unsigned long long));
    NSLog(@"float --> %s",@encode(float));
    NSLog(@"bool --> %s",@encode(bool));
    NSLog(@"void --> %s",@encode(void));
    NSLog(@"char * --> %s",@encode(char *));
    NSLog(@"id --> %s",@encode(id));
    NSLog(@"Class --> %s",@encode(Class));
    NSLog(@"SEL --> %s",@encode(SEL));
    int array[] = {1,2,3};
    NSLog(@"int[] --> %s",@encode(typeof(array)));
    typedef struct person{
        char *name;
        int age;
    }Person;
    NSLog(@"struct --> %s",@encode(Person));
    
    typedef union union_type{
        char *name;
        int a;
    }Union;
    NSLog(@"union --> %s",@encode(Union));
    int a = 2;
    int *b = {&a};
    NSLog(@"int[] --> %s",@encode(typeof(b)));
}
复制代码类型编码 实际分析
我们打开main.cpp文件,用下面代码进行分析:
{{(struct objc_selector *)"nickName", "@16@0:8", (void *)_I_SSLPerson_nickName},
{(struct objc_selector *)"setNickName:", "v24@0:8@16", (void *)_I_SSLPerson_setNickName_},
复制代码- @16@0:8- @:- id类型的返回值;
- 16: 占用内存;
- @:- id类型的参数;
- 0: 从- 0号位置开始;
- ::- SEL;
- 8: 从- 8号位置开始,反推- 8 + 8 = 16;
 
- v24@0:8@16- v:- void;
- 24: 占用内存;
- @: 第一个参数为- id类型;
- 0: 从- 0号位置开始;
- ::- SEL;
- 8: 从- 8号位置开始;
- @: 第二个参数为- id类型;
- 16: 从- 16号位置开始,反推- 16 + 8 = 24;
 
三、LLVM 探索属性方法
LLVM 探索 objc_setProperty
我们用LLVM探索,属性赋值什么情况会使用objc_setProperty,下载LLVM源码,用Visual Studio Code打开。
我们的探索思路是用反推法,先找到objc_setProperty函数的创建 -> 再找到调用objc_setProperty函数的地方 -> 查找为什么调用或者什么条件下调用函数 -> 探索到最终答案。
在VS中搜索objc_setProperty,会看到很多的相关代码,我们慢慢找,最终会找到下面这段代码:

找到了函数的创建CGM.CreateRuntimeFunction(FTy, "objc_setProperty"),接下来我们看看什么地方调用了getSetPropertyFn。
搜索getSetPropertyFn,寻找调用这个函数的地方:

GetPropertySetFunction()是个中间层代码,我们用它继续搜索:

- 我们找到了函数的调用setPropertyFn = CGM.getObjCRuntime().GetPropertySetFunction();;
- 能否调用到这段代码由strategy.getKind()来决定,也就是PropertyImplStrategy类型。
我们先看一下PropertyImplStrategy的定义:

通过注释可以发现StrategyKind为GetSetProperty和SetPropertyAndExpressionGet时调用objc_setProperty,那么我们去初始化方法中查找一下kind的赋值情况。



找到关键方法IsCopy时Kind = GetSetProperty;,所以属性为copy时用objc_setProperty赋值,否则用内存平移的方式赋值。
另外一种探索方式:

结论:当修饰符为copy或者retain时,使用objc_setProperty方式赋值。
下面进行验证:
定义SSLPerson类:
@interface SSLPerson : NSObject
@property (nonatomic, copy) NSString *aName;
@property (atomic, copy) NSString *bName;
@property (nonatomic, strong) NSString *cName;
@property (atomic, strong) NSString *dName;
@end
复制代码clang -rewrite-objc main.m -o main.cpp编译main.m得到main.cpp文件:
static NSString * _I_SSLPerson_aName(SSLPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SSLPerson$_aName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_SSLPerson_setAName_(SSLPerson * self, SEL _cmd, NSString *aName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SSLPerson, _aName), (id)aName, 0, 1); }
extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);
static NSString * _I_SSLPerson_bName(SSLPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct SSLPerson, _bName), 1); }
static void _I_SSLPerson_setBName_(SSLPerson * self, SEL _cmd, NSString *bName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SSLPerson, _bName), (id)bName, 1, 1); }
static NSString * _I_SSLPerson_cName(SSLPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SSLPerson$_cName)); }
static void _I_SSLPerson_setCName_(SSLPerson * self, SEL _cmd, NSString *cName) { (*(NSString **)((char *)self + OBJC_IVAR_$_SSLPerson$_cName)) = cName; }
static NSString * _I_SSLPerson_dName(SSLPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SSLPerson$_dName)); }
static void _I_SSLPerson_setDName_(SSLPerson * self, SEL _cmd, NSString *dName) { (*(NSString **)((char *)self + OBJC_IVAR_$_SSLPerson$_dName)) = dName; }
复制代码- 得到结果
- aName使用- nonatomic、- copy修饰,使用- objc_setProperty赋值。
- bName使用- atomic、- copy修饰,使用- objc_setProperty赋值。
- cName和- dName没有- copy修饰,使用的内存平移赋值。
 
- 结果符合我们的结论。
LLVM 探索 objc_getProperty
objc_getProperty的探索还是用到LLVM,用第二种方式进行探索。
在搜索objc_getProperty时发现了这种实现方式:

这种是拼接的方式,类似于我们编译的c++代码:

完整代码:
void RewriteModernObjC::RewritePropertyImplDecl(ObjCPropertyImplDecl *PID,
                                          ObjCImplementationDecl *IMD,
                                          ObjCCategoryImplDecl *CID) {
  // ...
  unsigned Attributes = PD->getPropertyAttributes();
  if (mustSynthesizeSetterGetterMethod(IMD, PD, true /*getter*/)) {
    // 判断是否生成objc_getProperty(
    bool GenGetProperty =
    // 不是nonatomic 且 用retain或copy修饰
        !(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
  bool GenSetProperty = 
  // 用retain或copy修饰
  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);
}
复制代码看关键代码:

得出结论,不是nonatomic且用retain或copy修饰时使用objc_getProperty。
下面进行验证:
定义SSLPerson类:
@interface SSLPerson : NSObject
@property (nonatomic, copy) NSString *aName;
@property (atomic, copy) NSString *bName;
@property (nonatomic, strong) NSString *cName;
@property (atomic, strong) NSString *dName;
@end
复制代码clang -rewrite-objc main.m -o main.cpp编译main.m得到main.cpp文件:
static NSString * _I_SSLPerson_aName(SSLPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SSLPerson$_aName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_SSLPerson_setAName_(SSLPerson * self, SEL _cmd, NSString *aName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SSLPerson, _aName), (id)aName, 0, 1); }
extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);
static NSString * _I_SSLPerson_bName(SSLPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct SSLPerson, _bName), 1); }
static void _I_SSLPerson_setBName_(SSLPerson * self, SEL _cmd, NSString *bName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SSLPerson, _bName), (id)bName, 1, 1); }
static NSString * _I_SSLPerson_cName(SSLPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SSLPerson$_cName)); }
static void _I_SSLPerson_setCName_(SSLPerson * self, SEL _cmd, NSString *cName) { (*(NSString **)((char *)self + OBJC_IVAR_$_SSLPerson$_cName)) = cName; }
static NSString * _I_SSLPerson_dName(SSLPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SSLPerson$_dName)); }
static void _I_SSLPerson_setDName_(SSLPerson * self, SEL _cmd, NSString *dName) { (*(NSString **)((char *)self + OBJC_IVAR_$_SSLPerson$_dName)) = dName; }
复制代码- 得到结果
- bName使用- atomic、- copy修饰,使用- objc_getProperty取值。
- aName、- cName和- dName没有同时使用- atomic、- copy修饰,使用的内存平移取值。
 
- 结果符合我们的结论。
总结
- 使用retain或copy修饰属性时,用objc_setProperty方式赋值,否则使用内存平移的方式进行赋值。
- 使用非nonatomic修饰,且用retain或copy修饰属性时,用objc_getProperty取值,否则使用内存平移的方式取值。

























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)
