一. 成员变量
在类的原理分析(上)通过LLDB调试打印了方法
,属性
和协议
,它们都存储在类的bits
变量所指向的内存区
class_data_bits_t *bits
通过调用bits->data()
方法获得class_rw_t
的指针,通过这个指针分别调用properties()
,methods()
和protocols()
,获得属性列表,方法列表和协议列表。但是成员变量iVars
和类的方法(+
前缀修饰的方法)却没有找到。
成员变量和属性的区别是,属性是由下划线+属性名
的成员变量,setter和getter方法组成。
下面通过LLDB调试来打印一下iVars
(lldb) p/x LGPerson.class
(Class) $29 = 0x0000000100008720 LGPerson
(lldb) p (class_data_bits_t*)0x0000000100008740
(class_data_bits_t *) $30 = 0x0000000100008740
(lldb) p $30->data()
(class_rw_t *) $31 = 0x0000000101d053d0
(lldb) p *$31
(class_rw_t) $32 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000360
}
}
firstSubclass = LGTeacher
nextSiblingClass = NSUUID
}
(lldb) p $32.ro()
(const class_ro_t *) $33 = 0x0000000100008128
(lldb) p *$33
(const class_ro_t) $34 = {
flags = 0
instanceStart = 8
instanceSize = 32
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic<const char *> = "LGPerson" {
Value = 0x0000000100003ea8 "LGPerson"
}
}
baseMethodList = 0x0000000100008170
baseProtocols = 0x0000000100008220
ivars = 0x0000000100008238
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000082a0
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $34.ivars
(const ivar_list_t *const) $35 = 0x0000000100008238
(lldb) p *$35
(const ivar_list_t) $36 = {
entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3)
}
(lldb) p $36.get(0)
(ivar_t) $37 = {
offset = 0x00000001000086b8
name = 0x0000000100003d39 "subject"
type = 0x0000000100003edb "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $36.get(1)
(ivar_t) $38 = {
offset = 0x00000001000086c0
name = 0x0000000100003d41 "_name"
type = 0x0000000100003edb "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $36.get(2)
(ivar_t) $39 = {
offset = 0x00000001000086c8
name = 0x0000000100003d47 "_hobby"
type = 0x0000000100003edb "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb)
通过调试输出结果知道,成员变量iVars
通过class_rw_t
结构指针调用ro()
方法,获得class_ro_t
结构的指针,这个结构里有一个ivars
指针,指向ivars
列表。
二.类方法
从前面的分析知道,对象的方法,属性和成员变量信息存储在类里,那么类的方法信息又存在哪里呢?前面提到过对象的isa
指向了类,类的isa
指向元类,那么类方法是不是存在元类里呢?
(lldb) x/4gx LGPerson.class
0x100008408: 0x0000000100008430 0x000000010036a140
0x100008418: 0x0000000101456fb0 0x0002802800000007
(lldb) p/x 0x0000000100008430 & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 0x0000000100008430
(lldb) po $1
LGPerson
(lldb) p (class_data_bits_t*)0x0000000100008450
(class_data_bits_t *) $2 = 0x0000000100008450
(lldb) p $2->data()
(class_rw_t *) $3 = 0x00000001013406f0
(lldb) p *$3
(class_rw_t) $4 = {
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4315156257
}
}
firstSubclass = nil
nextSiblingClass = 0x00007fff8008eeb0
}
(lldb) p $4.methods()
(const method_array_t) $5 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100008340
}
arrayAndFlag = 4295000896
}
}
}
(lldb) p $5.list.ptr
(method_list_t *const) $6 = 0x0000000100008340
(lldb) p *$6
(method_list_t) $7 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $7.get(0).big()
(method_t::big) $8 = {
name = "sayNB"
types = 0x0000000100003f6a "v16@0:8"
imp = 0x0000000100003d40 (KCObjcBuild`+[LGPerson sayNB])
}
(lldb)
从输出结果知道,类方法+[LGPerson sayNB]
的信息存在元类里
三. 类型编码
从上面方法的输出结果里,有没有注意到一个比较有意思的东西
(method_t::big) $8 = {
name = "sayNB"
types = 0x0000000100003f6a "v16@0:8"
imp = 0x0000000100003d40 (KCObjcBuild`+[LGPerson sayNB])
}
name
和imp
都比较好理解,但types
是什么呢?它的值是字符串"v16@0:8"
,这其实是方法的编码,编码意思如下
v
表示方法返回类型是void
16
表示方法总长度是16个字节
@
表示一个object对象地址类型的参数
0
表示object参数存储的起始位置
:
表示一个SEL类型的参数
8
表示SEL参数存放的起始位置
也可以用NSLog()打印出各种类型的编码
NSLog(@"char --> %s",@encode(char));
NSLog(@"int --> %s",@encode(int));
四. 属性getter和setter方法的实现
LGPerson有下面6个属性
@property (nonatomic, copy) NSString *nickName;
@property (atomic, copy) NSString *acnickName;
@property (nonatomic) NSString *nnickName;
@property (atomic) NSString *anickName;
@property (nonatomic, strong) NSString *name;
@property (atomic, strong) NSString *aname;
我们通过clang把OC代码翻译成c++代码
clang -rewrite-objc main.m -o main.cpp
static NSString * _I_LGPerson_nickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nickName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_LGPerson_setNickName_(LGPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _nickName), (id)nickName, 0, 1); }
extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);
static NSString * _I_LGPerson_acnickName(LGPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acnickName), 1); }
static void _I_LGPerson_setAcnickName_(LGPerson * self, SEL _cmd, NSString *acnickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acnickName), (id)acnickName, 1, 1); }
static NSString * _I_LGPerson_nnickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nnickName)); }
static void _I_LGPerson_setNnickName_(LGPerson * self, SEL _cmd, NSString *nnickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nnickName)) = nnickName; }
static NSString * _I_LGPerson_anickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_anickName)); }
static void _I_LGPerson_setAnickName_(LGPerson * self, SEL _cmd, NSString *anickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_anickName)) = anickName; }
static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)) = name; }
static NSString * _I_LGPerson_aname(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_aname)); }
static void _I_LGPerson_setAname_(LGPerson * self, SEL _cmd, NSString *aname) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_aname)) = aname; }
五个属性的getter
方法都通过内存平移来访问属性的
{(*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)) = name;}
self
指针内存平移来访问属性
其中有四个属性的setter
方法也是通过内存平移来访问属性的
@property (nonatomic) NSString *nnickName;
@property (atomic) NSString *anickName;
@property (nonatomic, strong) NSString *name;
@property (atomic, strong) NSString *aname;
但是两个使用了copy
修饰符的属性
@property (nonatomic, copy) NSString *nickName;
@property (atomic, copy) NSString *acnickName;
nickName
的getter
方法使用内存平移,setter
使用objc_setProperty
acnickName
的getter
方法使用objc_getProperty
,setter
方法使用objc_setProperty
// 内存平移
static NSString * _I_LGPerson_nickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nickName)); }
// objc_setProperty
static void _I_LGPerson_setNickName_(LGPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _nickName), (id)nickName, 0, 1); }
// objc_getProperty
static NSString * _I_LGPerson_acnickName(LGPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acnickName), 1); }
// objc_setProperty
static void _I_LGPerson_setAcnickName_(LGPerson * self, SEL _cmd, NSString *acnickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acnickName), (id)acnickName, 1, 1); }
copy
修饰符是属性方法是否调用objc_getProperty
和objc_setProperty
来实现的重要依据,可以从llvm的clang的CodeGen源码看到
/// 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();
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;
}
...
}
五. isKindOfClass
和isMemberOfClass
分析
void lgKindofDemo(void){
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; // YES:根元类的super class是NSObject.class
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; // NO: 元类不等于类
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]]; // NO: 元类的继承链没有LGPerson.class
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; // NO: 元类不等于类
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; // YES:实例的类继承链中有NSObject.class
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; // YES:实例的类类等于NSObject.class
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; // YES:实例的类继承链中有LGPerson.class
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; // YES:实例的类类等于LGPerson.class
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
输出结果
re1 :1
re2 :0
re3 :0
re4 :0
re5 :1
re6 :1
re7 :1
re8 :1
(lldb)
打个断点看看汇编
KCObjcBuild`lgKindofDemo:
0x100003910 <+0>: pushq %rbp
0x100003911 <+1>: movq %rsp, %rbp
0x100003914 <+4>: pushq %rbx
0x100003915 <+5>: pushq %rax
-> 0x100003916 <+6>: movq 0x4a0b(%rip), %rdi ; (void *)0x000000010036a140: NSObject
0x10000391d <+13>: callq 0x100003d84 ; symbol stub for: objc_opt_class
0x100003922 <+18>: movq %rax, %rbx
0x100003925 <+21>: movq 0x49fc(%rip), %rdi ; (void *)0x000000010036a140: NSObject
0x10000392c <+28>: callq 0x100003d84 ; symbol stub for: objc_opt_class
0x100003931 <+33>: movq %rbx, %rdi
0x100003934 <+36>: movq %rax, %rsi
0x100003937 <+39>: callq 0x100003d8a ; symbol stub for: objc_opt_isKindOfClass
0x10000393c <+44>: movb %al, -0x10(%rbp)
0x10000393f <+47>: movq 0x49e2(%rip), %rdi ; (void *)0x000000010036a140: NSObject
0x100003946 <+54>: callq 0x100003d84 ; symbol stub for: objc_opt_class
0x10000394b <+59>: movq %rax, %rbx
0x10000394e <+62>: movq 0x49d3(%rip), %rdi ; (void *)0x000000010036a140: NSObject
0x100003955 <+69>: callq 0x100003d84 ; symbol stub for: objc_opt_class
0x10000395a <+74>: movq 0x49af(%rip), %rsi ; "isMemberOfClass:"
0x100003961 <+81>: movq %rbx, %rdi
0x100003964 <+84>: movq %rax, %rdx
0x100003967 <+87>: callq *0x693(%rip) ; (void *)0x00000001002ea2c0: objc_msgSend
0x10000396d <+93>: movb %al, -0xf(%rbp)
0x100003970 <+96>: movq 0x49b9(%rip), %rdi ; (void *)0x00000001000083b0: LGPerson
0x100003977 <+103>: callq 0x100003d84 ; symbol stub for: objc_opt_class
0x10000397c <+108>: movq %rax, %rbx
0x10000397f <+111>: movq 0x49aa(%rip), %rdi ; (void *)0x00000001000083b0: LGPerson
0x100003986 <+118>: callq 0x100003d84 ; symbol stub for: objc_opt_class
0x10000398b <+123>: movq %rbx, %rdi
0x10000398e <+126>: movq %rax, %rsi
0x100003991 <+129>: callq 0x100003d8a ; symbol stub for: objc_opt_isKindOfClass
0x100003996 <+134>: movb %al, -0xe(%rbp)
0x100003999 <+137>: movq 0x4990(%rip), %rdi ; (void *)0x00000001000083b0: LGPerson
0x1000039a0 <+144>: callq 0x100003d84 ; symbol stub for: objc_opt_class
0x1000039a5 <+149>: movq %rax, %rbx
0x1000039a8 <+152>: movq 0x4981(%rip), %rdi ; (void *)0x00000001000083b0: LGPerson
0x1000039af <+159>: callq 0x100003d84 ; symbol stub for: objc_opt_class
0x1000039b4 <+164>: movq 0x4955(%rip), %rsi ; "isMemberOfClass:"
0x1000039bb <+171>: movq %rbx, %rdi
0x1000039be <+174>: movq %rax, %rdx
0x1000039c1 <+177>: callq *0x639(%rip) ; (void *)0x00000001002ea2c0: objc_msgSend
0x1000039c7 <+183>: leaq 0x642(%rip), %rdi ; @" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n"
0x1000039ce <+190>: movb %al, -0xd(%rbp)
0x1000039d1 <+193>: movsbl -0x10(%rbp), %esi
0x1000039d5 <+197>: movsbl -0xf(%rbp), %edx
0x1000039d9 <+201>: movsbl -0xe(%rbp), %ecx
0x1000039dd <+205>: movsbl -0xd(%rbp), %r8d
0x1000039e2 <+210>: movb $0x0, %al
0x1000039e4 <+212>: callq 0x100003d60 ; symbol stub for: NSLog
0x1000039e9 <+217>: movq 0x4938(%rip), %rdi ; (void *)0x000000010036a140: NSObject
0x1000039f0 <+224>: callq 0x100003d66 ; symbol stub for: objc_alloc
0x1000039f5 <+229>: movq %rax, %rbx
0x1000039f8 <+232>: movq 0x4929(%rip), %rdi ; (void *)0x000000010036a140: NSObject
0x1000039ff <+239>: callq 0x100003d84 ; symbol stub for: objc_opt_class
0x100003a04 <+244>: movq %rbx, %rdi
0x100003a07 <+247>: movq %rax, %rsi
0x100003a0a <+250>: callq 0x100003d8a ; symbol stub for: objc_opt_isKindOfClass
0x100003a0f <+255>: movb %al, -0xc(%rbp)
0x100003a12 <+258>: movq 0x490f(%rip), %rdi ; (void *)0x000000010036a140: NSObject
0x100003a19 <+265>: callq 0x100003d66 ; symbol stub for: objc_alloc
0x100003a1e <+270>: movq %rax, %rbx
0x100003a21 <+273>: movq 0x4900(%rip), %rdi ; (void *)0x000000010036a140: NSObject
0x100003a28 <+280>: callq 0x100003d84 ; symbol stub for: objc_opt_class
0x100003a2d <+285>: movq 0x48dc(%rip), %rsi ; "isMemberOfClass:"
0x100003a34 <+292>: movq %rbx, %rdi
0x100003a37 <+295>: movq %rax, %rdx
0x100003a3a <+298>: callq *0x5c0(%rip) ; (void *)0x00000001002ea2c0: objc_msgSend
0x100003a40 <+304>: movb %al, -0xb(%rbp)
0x100003a43 <+307>: movq 0x48e6(%rip), %rdi ; (void *)0x00000001000083b0: LGPerson
0x100003a4a <+314>: callq 0x100003d66 ; symbol stub for: objc_alloc
0x100003a4f <+319>: movq %rax, %rbx
0x100003a52 <+322>: movq 0x48d7(%rip), %rdi ; (void *)0x00000001000083b0: LGPerson
0x100003a59 <+329>: callq 0x100003d84 ; symbol stub for: objc_opt_class
0x100003a5e <+334>: movq %rbx, %rdi
0x100003a61 <+337>: movq %rax, %rsi
0x100003a64 <+340>: callq 0x100003d8a ; symbol stub for: objc_opt_isKindOfClass
0x100003a69 <+345>: movb %al, -0xa(%rbp)
0x100003a6c <+348>: movq 0x48bd(%rip), %rdi ; (void *)0x00000001000083b0: LGPerson
0x100003a73 <+355>: callq 0x100003d66 ; symbol stub for: objc_alloc
0x100003a78 <+360>: movq %rax, %rbx
0x100003a7b <+363>: movq 0x48ae(%rip), %rdi ; (void *)0x00000001000083b0: LGPerson
0x100003a82 <+370>: callq 0x100003d84 ; symbol stub for: objc_opt_class
0x100003a87 <+375>: movq 0x4882(%rip), %rsi ; "isMemberOfClass:"
0x100003a8e <+382>: movq %rbx, %rdi
0x100003a91 <+385>: movq %rax, %rdx
0x100003a94 <+388>: callq *0x566(%rip) ; (void *)0x00000001002ea2c0: objc_msgSend
0x100003a9a <+394>: leaq 0x58f(%rip), %rdi ; @" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n"
0x100003aa1 <+401>: movb %al, -0x9(%rbp)
0x100003aa4 <+404>: movsbl -0xc(%rbp), %esi
0x100003aa8 <+408>: movsbl -0xb(%rbp), %edx
0x100003aac <+412>: movsbl -0xa(%rbp), %ecx
0x100003ab0 <+416>: movsbl -0x9(%rbp), %r8d
0x100003ab5 <+421>: movb $0x0, %al
0x100003ab7 <+423>: callq 0x100003d60 ; symbol stub for: NSLog
0x100003abc <+428>: addq $0x8, %rsp
0x100003ac0 <+432>: popq %rbx
0x100003ac1 <+433>: popq %rbp
0x100003ac2 <+434>: retq
从汇编我们发现isKindOfClass
调用objc_opt_isKindOfClass
,isMemberOfClass
通过objc_msgSend
给isMemberOfClass:
发送消息。我们调试到runtime源码里看看
// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
// 实际运行走的这里
for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
// NSObject
// tcls = 根元类 父类 NSObject
// LGPerson
// 元类 -> 根元类 -> NSObject -> nil
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
// objc
// NSObject = tcls VS NSObject
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
isKindOfClass
是取到isa
指向的类/元类,再以这个类/元类的继承链向上搜索。
如果调用的是对象,那么搜索的路径是类的继承链:
类 > 父类 ... > NSObject > nil
如果调用的是类,那么搜索的是元类的继承链:
元类 > 父元类 ... > 根元类 > NSObject > nil
isMemberOfClass
是取到isa
指向的类/元类,和传入的参数进行比较。通过对原码的分析,之前lgKindofDemo
函数的输出结果就比较明了了。