欢迎阅读iOS探索系列(按序阅读食用效果更加)c++
本文涉及的面试题以下:面试
isKindOfClass
和isMemberOfClass
的区别[self class]
和[super class]
的区别这是一道涉及isa走位图
的面试题,大胆猜想下结果bash
#import <Foundation/Foundation.h>
#import "FXPerson.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];//1
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];// 0
BOOL re3 = [(id)[FXPerson class] isKindOfClass:[FXPerson class]];//0
BOOL re4 = [(id)[FXPerson class] isMemberOfClass:[FXPerson class]];// 0
NSLog(@"\n re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];//1
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];// 1
BOOL re7 = [(id)[FXPerson alloc] isKindOfClass:[FXPerson class]];//1
BOOL re8 = [(id)[FXPerson alloc] isMemberOfClass:[FXPerson class]];// 1
NSLog(@"\n re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
return 0;
}
复制代码
这里先不揭晓答案,先来探索一下isKindOfClass
和isMemberOfClass
的实现函数
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
复制代码
for (int i = 0; i < 3; i ++)
的for循环
object_getClass
获得当前类对象的类——元类
,初始化tcls
tcls
有值就能够继续循环,即当tcls
为nil
时结束for循环tcls
的父类,做为它的新值,继续下一次循环tcls == cls
,返回YES
NO
结论一:+isKindOfClass是元类及其父类 vs 类post
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
复制代码
object_getClass
获得当前类对象的类——元类
,和类自己cls
进行比较+isKindOfClass
少了父类的比较,所以+isMemberOfClass
为YES时能够获得+isKindOfClass
为YES结论二:+isMemberOfClass是元类 vs 类ui
isa走位图
(实线为父类走向)能够得出前面四个打印结果:
NSObject元类
与NSObject类
不相等,NSObject元类的父类
(指向NSObject类
)与NSObject类
相等——YESNSObject元类
与NSObject类
不相等——NOFXPerson元类
与FXPerson类
不相等,FXPerson元类的父类
与FXPerson类
不相等——NOFXPerson元类
与FXPerson类
不相等——NO换成实例对象调用-isKindOfClass
和-isMemberOfClass
atom
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
复制代码
同理可得:-isMemberOfClass
是拿实例对象的类(即当前类)和cls
做比较,-isKindOfClass
多了一步for循环类对象的父类spa
结论三:-isKindOfClass是类自己及其父类 vs 类设计
结论四:-isMemberOfClass是类自己 vs 类3d
后面四个结果分析以下:
NSObject类
与NSObject类
相等——YESNSObject类
与NSObject类
相等——YESFXPerson类
与FXPerson类
相等——YESFXPerson类
与FXPerson类
相等——YESFXSon
继承于FXFather
,主程序初始化FXSon
,求问打印内容以及思路
#import "FXSon.h"
@implementation FXSon
- (instancetype)init {
self = [super init];
if (self) {
NSLog(@"[self class] = %@", NSStringFromClass([self class]));
NSLog(@"[super class] = %@", NSStringFromClass([super class]));
NSLog(@"[self superclass] = %@", NSStringFromClass([self superclass]));
NSLog(@"[super superclass] = %@", NSStringFromClass([super superclass]));
}
return self;
}
@end
复制代码
打印结果以下
[self class]
点进去来到
NSObject.mm
文件查看源码
class
方法返回类superclass
方法返回类的父类+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
+ (Class)superclass {
return self->superclass;
}
- (Class)superclass {
return [self class]->superclass;
}
复制代码
从这段代码能解释[self class]
和[self superclass]
,可是另外两个又怎么解释呢?
终端clang编译代码获得super.cpp
,就能看到初始化的底层代码了
clang -rewrite-objc FXSon.m -o super.cpp
复制代码
objc_msgSend
,那
objc_msgSendSuper
也是发送消息吗?
查看源码中对objc_msgSendSuper
的定义,注释中提示了objc_super
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
/// Specifies the superclass of an instance.
struct objc_msgSendSuper {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};
复制代码
从以上源码得出,使用objc_msgSendSuper
向objc_super
发送消息,而objc_super
在objc2.0
下有两个元素——id
类型的receiver
和Class
类型的super_class
其实早在iOS探索 方法的本质和消息查找流程中就提过这个方法,而后笔者进行了仿写
记得导入<objc/message.h>
,报错Too many arguments
就去修改编译期配置
[super class]
同样的结果了
那苹果为何要这么设计呢?把消息查找
和isa走位图
联系起来就明白了!
son实例对象
的
实例方法
存在
FXSon类
中
[self class]
就是son照着FXSon->FXFather->NSObject
顺序问老爸要-class方法
[super class]
就是son跳过FXSon
,直接经过FXFather->NSObject
查找[super class]
更快找到class方法的写法
[self class]
改成[super class]
,直接找到NSObjct
补充: 当结构体中[self class]
改成[FXFather class]
时,由于类方法存在元类中,会按FXFather元类->NSObject元类->NSObject根元类
找+class
方法,最后也是会输出FXSon
结论:
[self class]
就是发送消息objc_msgSend
,消息接收者是self
,方法编号是class
[super class]
就是发送消息objc_msgSendSuper
,消息接收者是self
,方法编号是class
,只不过objc_msgSendSuper
会跳过self
的查找这是一道比较经典的“丧心病狂”的内存偏移面试题,若是你没有研究过,大几率很难答上来
程序可否运行吗?是否正常输出?
#import "ViewController.h"
@interface FXPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation FXPerson
- (void)printMyProperty {
NSLog(@"当前打印内容为%s", __func__);
}
@end
//——————————————————————————————————————//
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [FXPerson class];
void *obj= &cls;
[(__bridge id)obj printMyProperty];
FXPerson *p = [FXPerson new];
[p printMyProperty];
}
@end
复制代码
运行结果与普通初始化对象如出一辙,可面试的时候不可能只说能或不能,还要说出个因此然来
正常初始化:指针p->实例对象isa->类对象
objc_object
,第一个元素为isa
指针p
存储着FXPerson类
实例出来的对象内存地址,因此指针p
指向对象的首地址——p->实例对象isa
实例对象
的isa指向类对象
——实例对象isa->类对象
骚操做:指针obj->指针cls->类对象
id cls = [LGPerson class]
获取到类对象指针void *obj= &cls
获取到指向该类对象cls的对象obj
修改打印方法printMyProperty
——不但打印方法,同时打印属性name
- (void)printMyProperty {
NSLog(@"当前打印内容为%s——————%@", __func__, self.name);
}
复制代码
从新运行代码,获得结果以下
当前打印内容为-[FXPerson printMyProperty]——————<ViewController: 0x7fc72cd09450>
当前打印内容为-[FXPerson printMyProperty]——————(null)
复制代码
为何属性name尚未赋值,却打印出了ViewController
的内存地址?
先入后出
,viewDidLoad
入栈先拉伸栈空间,而后依次放入self、_cmd
局部变量[super viewDidLoad]
,继续放入super_class、self
self
修改viewDidLoad
——在obj
前面加个临时字符串变量
- (void)viewDidLoad {
[super viewDidLoad];
NSString *temp = @"1";
id cls = [FXPerson class];
void *obj= &cls;
[(__bridge id)obj printMyProperty];
FXPerson *p = [FXPerson alloc];
[p printMyProperty];
}
复制代码
从新运行代码,获得结果以下
当前打印内容为-[FXPerson printMyProperty]——————1
当前打印内容为-[FXPerson printMyProperty]——————(null)
复制代码
一样道理,在obj
入栈前已经有了temp
变量,此时访问self.name
就会访问到temp
去掉临时变量,FXPerson类
新增字符串属性hobby
,打印方法改成打印hobby
,运行
ViewController
就是
obj
偏移16字节拿到的
super_class
([super viewDidLoad]压栈进去的)
①去掉[super viewDidLoad]
,运行
②临时变量改为NSInteger类型
这两种状况就是野指针
——指针偏移的offset不正确,获取不到对应变量的首地址
int a = 1;
int b = 2;
int c = 3;
int d = 4;
NSLog(@"\na = %p\nb = %p\nc = %p\nd = %p\n",&a,&b,&c,&d);
复制代码
打印结果
a = 0x7ffee0ebd1bc
b = 0x7ffee0ebd1b8
c = 0x7ffee0ebd1b4
d = 0x7ffee0ebd1b0
复制代码
局部变量的存放顺序,是根据定义的前后顺序,从函数栈底(高地址)开始,一个一个排列
关于这题还没搞明白的能够看下Runtime笔记(八)—— 面试题中的Runtime里面的图很形象
面试题是面试官用知识点变着法玩你的一种手段,同时也能表现出你掌握知识的熟练度。只有在平时多练习多研究,才能在面试的时候给面试官留下一个好的印象