- 上一篇文章我们分析了类的本质,在通过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); }
复制代码
- 仔细观察上面的底层实现代码不难发现:
name
的setter
方法是通过内存平移直接赋值的hobby
的setter
方法中调用了objc_setProperty
- 为什么会出现上述情况呢?由于这里是通过
clang
编译看到的结构,所以这个过程应该是发生在编译阶段,下面我们通过llvm
的底层具体是如何实现的?
llvm调用分析
分析
- 这里分析需要使用到llvm源码(llvm源码下载),由于源码比较大,就没有放到示例代码中,需要的可以自行下载
- 打开
llvm
源码,搜索objc_setProperty
:
- 这里可以逆向推导,看哪里调用了
getSetPropertyFn
:
- 再看哪里调用了
GetPropertySetFunction
:PropertyImplStrategy strategy(CGM, propImpl);
:这是C++
语法,创建了一个PropertyImplStrategy
类型的变量strategy
,并调用PropertyImplStrategy
的构造函数初始化- 根据
strategy.getKind()
条件判断执行switch
的哪个case
分支 - 所以这里重点看下
PropertyImplStrategy
的实现
PropertyImplStrategy
实现:- 从
StrategyKind
中可以看出,GetSetProperty
和SetPropertyAndExpressionGet
都会使用objc_setProperty
- 从构造函数的实现中可以看出,如果
IsCopy
,Kind
变量赋值为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); }
复制代码
- 从上面的结果可以看出:
nsName
和asName
使用strong
修饰,生成的setter
方法使用内存平移的方法赋值ncName
和acName
使用copy
修饰,生成的setter
方法调用objc_setProperty
llvm分析拓展
- 细心的同学可能会发现,上面的验证结果中不仅使用到了
objc_setProperty
,getter
方法中还使用到了objc_getProperty
,那么什么时候会调用objc_getProperty
呢? - 上面的分析流程总体看起来还是比较复杂的,在探索的过程中,我还发现了两一中方法,看起来更简单更直接一下,下面我们一起分析一下
分析
- 这里还是使用
llvm
源码分析,打开llvm
源码,搜索objc_setProperty
,你会发现还有这样的实现方式:
- 这里是直接使用字符串的形式进行拼接,看起来和
clang
编译出的C++
代码更为相似
- 看下这段代码的具体实现:
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
修饰 - 必须使用
retain
或copy
修饰
- 不能使用
- 使用
objc_setProperty
需要满足条件:- 使用
retain
或copy
修饰
- 使用
- 否则,使用内存平移的方式取值或者赋值
- 使用
验证
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
使用atomic
和retain
修饰,生成的setter
方法调用objc_getProperty
acName
使用atomic
和copy
修饰,生成的setter
方法调用objc_getProperty
objc_setProperty
:nrName
使用nonatomic
和retain
修饰,生成的setter
方法调用objc_setProperty
ncName
使用nonatomic
和copy
修饰,生成的setter
方法调用objc_setProperty
arName
使用atomic
和retain
修饰,生成的setter
方法调用objc_setProperty
acName
使用atomic
和copy
修饰,生成的setter
方法调用objc_setProperty
- 其他都是使用内存平移的方式取值或赋值
总结
- 使用非
nonatomic
修饰,且使用retain
或copy
修饰的属性生成的getter
方法会调用objc_getProperty
,否则使用内存平移的方式取值 - 使用
retain
或copy
修饰的属性生成的setter
方法会调用objc_setProperty
,否则使用内存平移的方式进行赋值
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END