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
取值,否则使用内存平移的方式取值。