遗留
首先先解决一下上一篇遗留的问题:
- ivar在哪里?
- 为什么类结构里没有类方法?
类结构以及元类结构都存了什么方法
其实我们在探索isa的走向图, 以及继承链的时候, isa的指向有元类, 有根元类. 我们不妨看一下元类和根元类的方法都是存的是什么?
首先, 我们在类里面添加成员变量和类方法如下:
@interface LGPerson : NSObject
{
int a;
NSString *b;
}
@property (nonatomic,copy) NSString *cname;
@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSString *nickName;
- (void)test1;
+ (void)test2;
+ (void)test3; //方法未实现
@end
上一篇我们已经看了怎么去对应的结构体中找到对应的方法或者成员并输出, 接下来直接看lldb的打印
lldb调试
(lldb) p/x LGPerson.class
(Class) $0 = 0x0000000100004558
(lldb) x/4gx 0x0000000100004558
0x100004558: 0x0000000100004530 0x00000001003571e0
0x100004568: 0x0000000100649bb0 0x0001802000000003
//找到元类
(lldb) p/x 0x0000000100004530 & 0x00007ffffffffff8
(long) $1 = 0x0000000100004530
//输出元类结构
(lldb) p (class_data_bits_t *)0x100004550
(class_data_bits_t *) $2 = 0x0000000100004550
//找到class_rw_t的结构
(lldb) p *$2->data()
(class_rw_t) $3 = {
flags = 2684878849
witness = 0
ro_or_rw_ext = {
std::__1::atomic = {
Value = 4294984440
}
}
firstSubclass = nil
nextSiblingClass = 0x00007fff8a726cd8
}
//找到方法列表
(lldb) p $3.methods()
(const method_array_t) $4 = {
list_array_tt = {
= {
list = {
ptr = 0x0000000100004340
}
arrayAndFlag = 4294984512
}
}
}
//打印出结构
(lldb) p $4.list
(const method_list_t_authed_ptr) $5 = {
ptr = 0x0000000100004340
}
//找到对应的地址指针
(lldb) p $5.ptr
(method_list_t *const) $6 = 0x0000000100004340
//取出值
(lldb) p *$6
(method_list_t) $7 = {
entsize_list_tt = (entsizeAndFlags = 27, count = 1)
}
//输出list的内容
(lldb) p $7.get(0).big()
(method_t::big) $8 = {
name = "test2"
types = 0x0000000100003f8d "v16@0:8"
imp = 0x0000000100003c00 (KCObjcBuild`+[LGPerson test2])
}
在元类里面的找到了对应的类方法, 我们可以知道, 那我继续往下看想看看根元类里面放了什么, 但是遇到了如下错误
(lldb) p $8.get(0).big()
Assertion failed: (!isSmall()), function big, file /Users/JGK_Pro/Downloads/2021LGK/2021_OC/OC对象上004-alloc流程分析/runtime/objc-runtime-new.h, line 787.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
Assertion failed: (!isSmall())
断点应该是断在了big这个地方:
struct method_t {
small &small() const {
ASSERT(isSmall());
return *(struct small *)((uintptr_t)this & ~(uintptr_t)1);
}
big &big() const {
ASSERT(!isSmall());
return *(struct big *)this;
}
}
直接使用代码打印:
Class metaClass = objc_getMetaClass("NSObject");
unsigned int count = 0;
Method *methods = class_copyMethodList(metaClass, &count);
for (unsigned int i=0; i < count; i++) {
Method const method = methods[i];
//获取方法名
NSString *key = NSStringFromSelector(method_getName(method));
LGLog(@"Method, name: %@", key);
}
free(methods);
通过代码一共打印了145个方法, 太长了我把常见的方法拉出来看看:
Method, name: init
Method, name: dealloc
Method, name: description
Method, name: methodSignatureForSelector:
Method, name: doesNotRecognizeSelector:
Method, name: load
Method, name: instanceMethodSignatureForSelector:
Method, name: _copyDescription
Method, name: resolveClassMethod:
Method, name: isAncestorOfObject:
Method, name: init
Method, name: dealloc
Method, name: isEqual:
Method, name: hash
Method, name: superclass
Method, name: class
Method, name: self
Method, name: performSelector:
Method, name: performSelector:withObject:
Method, name: performSelector:withObject:withObject:
Method, name: isProxy
Method, name: isKindOfClass:
Method, name: isMemberOfClass:
Method, name: conformsToProtocol:
Method, name: respondsToSelector:
Method, name: retain
Method, name: release
Method, name: autorelease
Method, name: retainCount
Method, name: zone
Method, name: description
Method, name: debugDescription
Method, name: copyWithZone:
Method, name: methodSignatureForSelector:
Method, name: forwardInvocation:
Method, name: copy
Method, name: mutableCopy
Method, name: instancesRespondToSelector:
Method, name: forwardingTargetForSelector:
Method, name: doesNotRecognizeSelector:
Method, name: initialize
Method, name: isSubclassOfClass:
Method, name: allocWithZone:
Method, name: alloc
Method, name: new
Method, name: mutableCopyWithZone:
Method, name: methodForSelector:
Method, name: instanceMethodForSelector:
Method, name: isFault
Method, name: instanceMethodSignatureForSelector:
Method, name: _isDeallocating
Method, name: _tryRetain
Method, name: allowsWeakReference
Method, name: retainWeakReference
Method, name: resolveInstanceMethod:
至此类的方法探索基本上已经完成了, 有兴趣的可以继续看一下NSObject的类存了哪些犯法;
ivar的存储
首先继续在objc_class找ivar相关的, 看看是否能找到成员:
我们看到了这样一个东西
struct objc_class : objc_object {
// Return YES if the class's ivars are managed by ARC,
// or the class is MRC but has ARC-style weak ivars.
bool hasAutomaticIvars() {
return data()->ro()->flags & (RO_IS_ARC | RO_HAS_WEAK_WITHOUT_ARC);
}
}
在整个结构中只找到了这么一个ivar相关的, 我们搜索进去看看. 点击进去之后如下:
struct class_rw_t {
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is())) {
return v.get(&ro_or_rw_ext)->ro;
}
return v.get(&ro_or_rw_ext);
}
}
我们继续点击进去看看class_ro_t的结构:
struct class_ro_t {
const ivar_list_t * ivars;
method_list_t *baseMethods() const {
#if __has_feature(ptrauth_calls)
method_list_t *ptr = ptrauth_strip((method_list_t *)baseMethodList, ptrauth_key_method_list_pointer);
if (ptr == nullptr)
return nullptr;
// Don't auth if the class_ro and the method list are both in the shared cache.
// This is secure since they'll be read-only, and this allows the shared cache
// to cut down on the number of signed pointers it has.
bool roInSharedCache = objc::inSharedCache((uintptr_t)this);
bool listInSharedCache = objc::inSharedCache((uintptr_t)ptr);
if (roInSharedCache && listInSharedCache)
return ptr;
// Auth all other small lists.
if (ptr->isSmallList())
ptr = ptrauth_auth_data((method_list_t *)baseMethodList,
ptrauth_key_method_list_pointer,
ptrauth_blend_discriminator(&baseMethodList,
methodListPointerDiscriminator));
return ptr;
#else
return (method_list_t *)baseMethodList;
#endif
}
}
我看到了, 我们刚刚才玩过的methods, 这里也有一个baseMethods, 不知道和刚刚的有什么区别, 也看到了我们想要的ivars, 继续看一下结构:
struct ivar_list_t : entsize_list_tt {
bool containsIvar(Ivar ivar) const {
return (ivar >= (Ivar)&*begin() && ivar < (Ivar)&*end());
}
};
和我们的method_list_t一样的继承, 根据上面的结构查找现在我们开始lldb找一下:
- 第一我们知道是class_rw_t中的ro()获取到class_ro_t结构然后获取到ivars
- 第二我们还是首先获取class_rw_t
- 然后获取class_ro_t
- 然后获取ivars和basemethods
整个lldb的调试如下:
(lldb) p/x LGPerson.class
(Class) $0 = 0x0000000100004610
(lldb) p (class_data_bits_t *)0x0000000100004630
(class_data_bits_t *) $1 = 0x0000000100004630
(lldb) p * $1
(class_data_bits_t) $2 = (bits = 4302495476)
(lldb) p $2.data()
(class_rw_t *) $3 = 0x000000010072def0
(lldb) p *$3
(class_rw_t) $4 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic = {
Value = 4294984544
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
(lldb) p $4.ro()
(const class_ro_t *) $5 = 0x0000000100004360
(lldb) p *$5
(const class_ro_t) $6 = {
flags = 0
instanceStart = 8
instanceSize = 48
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic = "LGPerson" {
Value = 0x0000000100003f7a "LGPerson"
}
}
baseMethodList = 0x00000001000043a8
baseProtocols = 0x0000000000000000
ivars = 0x0000000100004458
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100004500
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $6.ivars
(const ivar_list_t *const) $7 = 0x0000000100004458
(lldb) p *$7
(const ivar_list_t) $8 = {
entsize_list_tt = (entsizeAndFlags = 32, count = 5)
}
(lldb) p $8.get(0)
(ivar_t) $9 = {
offset = 0x0000000100004570
name = 0x0000000100003d73 "a"
type = 0x0000000100003faa "i"
alignment_raw = 2
size = 4
}
(lldb) p $8.get(1)
(ivar_t) $10 = {
offset = 0x0000000100004578
name = 0x0000000100003d75 "b"
type = 0x0000000100003f83 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $8.get(2)
(ivar_t) $11 = {
offset = 0x0000000100004580
name = 0x0000000100003d77 "_cname"
type = 0x0000000100003f83 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $8.get(3)
(ivar_t) $12 = {
offset = 0x0000000100004588
name = 0x0000000100003d7e "_name"
type = 0x0000000100003f83 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $8.get(4)
(ivar_t) $13 = {
offset = 0x0000000100004590
name = 0x0000000100003d84 "_nickName"
type = 0x0000000100003f83 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb)
在这里我们已经找到了ivar, 我又尝试打印了一下元类的ivar, 是空的如下图:
在这里我们看一下ivar_t输出中的 type是什么意思:
文档地址
如果我们不知道, 我们可以引入runtime头文件, 然后输入一个ivar如下:
看到了type关键词, 然后点击进入文档查看:
又看到一个Type Encodings, 继续点击进去如下:
就找到了对应的type编码.
所以, 我们再次就找到了所有的ivars, 属性会生成_name的变量, 变量本身会存在class_data_bits_t中的class_rw_t中的class_ro_t中的ivars. 但是元类之中的ivars是空的.
baseMethod
接着刚刚的调试
(lldb) p $6.baseMethods()
(method_list_t *) $19 = 0x00000001000043a8
(lldb) p *$19
(method_list_t) $20 = {
entsize_list_tt = (entsizeAndFlags = 27, count = 7)
}
(lldb) p $20.get(0).big()
(method_t::big) $21 = {
name = "test1"
types = 0x0000000100003f97 "v16@0:8"
imp = 0x0000000100003b80 (KCObjcBuild`-[LGPerson test1])
}
(lldb) p $20.get(1).big()
(method_t::big) $22 = {
name = "setCname:"
types = 0x0000000100003f9f "v24@0:8@16"
imp = 0x0000000100003bc0 (KCObjcBuild`-[LGPerson setCname:])
}
(lldb) p $20.get(2).big()
(method_t::big) $23 = {
name = "name"
types = 0x0000000100003f8f "@16@0:8"
imp = 0x0000000100003bf0 (KCObjcBuild`-[LGPerson name])
}
(lldb) p $20.get(3).big()
(method_t::big) $24 = {
name = "setName:"
types = 0x0000000100003f9f "v24@0:8@16"
imp = 0x0000000100003c10 (KCObjcBuild`-[LGPerson setName:])
}
(lldb) p $20.get(4).big()
(method_t::big) $25 = {
name = "setNickName:"
types = 0x0000000100003f9f "v24@0:8@16"
imp = 0x0000000100003c60 (KCObjcBuild`-[LGPerson setNickName:])
}
(lldb) p $20.get(5).big()
(method_t::big) $26 = {
name = "nickName"
types = 0x0000000100003f8f "@16@0:8"
imp = 0x0000000100003c40 (KCObjcBuild`-[LGPerson nickName])
}
(lldb) p $20.get(6).big()
(method_t::big) $27 = {
name = "cname"
types = 0x0000000100003f8f "@16@0:8"
imp = 0x0000000100003b90 (KCObjcBuild`-[LGPerson cname])
}
发现一共有7个方法, 6个是属性自己生成的set,get方法, 一个是我们自己实现的test1. 所以想想和rw没有区别, 为什么我们ro和rw都有呢? 后续在讨论.
讲一下types:
- name = "test1" types = 0x0000000100003f97 "v16@0:8"
v:去查type表里面就是void
16:就是一共占用了16个字节
@:An object (whether statically typed or typed id), id, 相当于self
0: 从0字节开始
: : A method selector (SEL)
8: 从8开始计算
等同于: (void)test:(id self, SEL _cmd);
ivar和property的区别
- property会自动生成set, get, ivars
- ivar只会存在于ivars不会有多余的方法生成
- 所以其实从这个层面说只写ivar的话就少了方法调用的消耗, 更节能
isKindOfClass探究
先把isKindOfClass和isMemberOfClass的代码拿出来看看:
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]]; //
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; //
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
分析类方法
先分析类
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
re1
- [NSObject class]获取到NSObject根类
- tcls = self->ISA(), self就是NSObject类
- 第一次: tcls = self->ISA(), 根元类和根类比较, 不同
- 第二次: 根元类的Superclass就是根类NSObject, 相同. 返回YES
re2
- 直接比较根元类和根类, 不相同. 返回NO
re3
- LGPerson继承NSObject
- [LGPerson class]获取到LGPerson类
- 第一次: tcls = self->ISA(). LGPerson元类和类比较, 不相等. tcls获取父类
- 第二次: tcls当前等于LGPerson元类的父类, LGPerson的父类是NSObject, 所以tcls现在就等于NSObject的元类, 继续比较. 不相等. 继续获取父类
- 第三次: tcls=NSObject类, 不相等. 继续比较
- 第三次: tcls=nil, 不相等, 返回NO
re4
直接那LGPerson的类和元类比较, 不相等返回NO.
所以类方法的结果是YES, NO, NO, NO
分析实例方法
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; //
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; //
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
re5
- [NSObject alloc]是实例变量, [NSObject class]是类,
- 第一步: tcls = 实例变量getIsa 等于NSObject类. cls = NSObject类
直接返回YES.
re6
- NSObject的实例变量, cls是NSObject类
- 第一步: tcls = 实例变量getIsa 等于NSObject类. cls = NSObject类
直接返回YES
re7
- LGPerson的实例变量, cls是LGPerson类
- 第一步: tcls就是LGPerson的类, cls=LGPerson类. 返回YES
re8
- LGPerson的实例变量, cls是LGPerson类
- 第一步: tcls就是LGPerson的类, cls=LGPerson类. 返回YES
所以结果为YES, YES, YES, YES.
当前我们macOS Deployment的版本是10.10, 我们断点汇编一下如下图:
现在我们吧macOS Deployment Target的版本是11.00, 看一下:
我们发现除了isMemberOfClass走的还是正常的方法调用之外, 其他的class方法和isKindOfClass方法走的全都是新的符号, 我们把符号找出来:
先去符号表看一下, 是否真的有:
在懒加载符号表里面找到了这两个符号.
先全局找一下代码
objc_opt_class
// Calls [obj class]
Class
objc_opt_class(id obj)
{
#if __OBJC2__
if (slowpath(!obj)) return nil;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
return cls->isMetaClass() ? obj : cls;
}
#endif
return ((Class(*)(id, SEL))objc_msgSend)(obj, @selector(class));
}
objc_opt_isKindOfClass
// 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);
}
__OBJC2__
代码中写着, 调用[obj class]和[obj isKindOfClass]会走到这两个方法, 但是全局没有其他多余的地方有这两个方法. 所以只能是在llvm的层面, 做了符号的重定向和objc_alloc应该是一样的. 暂时先不去找他, 后续所有的符号重定向的地方一起找. 现在先看源码部分做了那些改动:
精简一下代码:
objc_opt_class(id obj)
{
Class cls = obj->getIsa();
return cls->isMetaClass() ? obj : cls;
}
objc_opt_isKindOfClass(id obj, Class otherClass)
{
Class cls = obj->getIsa();
for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
if (tcls == otherClass) return YES;
}
return NO;
}
大概就是这个样子, 所以我们在调用isKindOfClass的时候本质就发生了变化:
OBJC2 re1 根类的类方法比较
- [NSObject class]传入objc_opt_class, getIsa就得到了根元类. 然后返回, 是元类就返回本身, 返回obj就是NSObject本身的类;
第一步: obj就是NSObject类, cls就是NSObject元类, tcls= cls, otherClass就是1锁说的obj本身, NSObject类.
开始比较: tcls是NSObject元类, otherClass是NSObject类, 不相等进入第二步;第二步: tcls获取父类, tcls是根元类父类是NSObject类, tcls和otherClass比较, 相等. 返回YES;
OBJC2 re3 子类的类方法比较
- [LGPerson class]传入objc_opt_class, getIsa得到LGPerson元类. 返回LGPerson类, otherClass就是LGPerson类.
第一步: cls = LGPerson->getIsa = LGPerson元类, otherClass就是LGPerson类, 比较一下不相等.
第二步: tcls = LGPerson元类的父类 = NSObject元类, 继续比较. 不相等
第三步: tcls = NSObject元类的父类 = NSObject类, 继续比较,不相等
第四步: tcls = NSObject类的父类 = nil, 返回NO
OBJC2 re5 根类实例方法比较
- [NSObject alloc], 实例getIsa得到NSObject类, 第一次比较的cls是NSObject类. otherClass就是NSObject类.
- 第一步: tcls = cls = NSObject类, 和otherClass比较, 相等 return YES;
OBJC2 re7 子类实例方法比较
- 第一次比较的cls=[LGPerson alloc]->getisa 是LGPerson类, otherClass就是LGPerson类.
- 第一步: tcls = cls = LGPerson类 和otherClass比较, 相等 return YES;
所以重新分析的1,3,5,7会发现.
- 不管是类还是实例调用class, 走到objc_opt_class返回的始终是类本身.
- 第一次比较的时候, 都是取的是调用者的isa进行比较的. 所以当直接传入任何子类, 进行比较的时候 均为NO.
看一下类属性
实现了, 但是lldb调试没看到, 可以api调用打印出来, 后续继续试
目前遗留的问题
copy和strong修饰符的区别
alloc的objc_alloc, objc_opt_class和objc_opt_isKindOfClass的符号查找
WWDC对类结构的优化
油管链接
官网链接
未完待续....