前言
通过面试题检验下自己对底层掌握的怎么样吧,顺便也做个记录。
1. load的调用顺序?
父类>子类>分类
在博客dyld加载流程,我们详细分析过load_images,会先递归获取父类的load()方法存进列表,然后再获取子类的load()方法存进列表,最后获取分类的load()方法存进列表。所以调用顺序父类>子类>分类
2.能否向编译后的类中添加实例变量?能否向运行时创建的类中添加实例变量?
- 不能向编译后的类中添加实例变量,因为
编译后类的内存结构已经确定无法修改 - 运行时动态创建的类还没有进入内存,可以添加实例变量
例如:
Class newperson= objc_allocateClassPair(class_getSuperclass(objc_getClass("newPerson")), "newPerson", 0);
class_addIvar(newperson, "name", sizeof(NSString*),log2(alignof(NSString*)), @encode(NSString*));
3. [self class]和[super class]的区别
@implementation LGTeacher
- (instancetype)init{
self = [super init];
if (self) {
NSLog(@"%@ - %@",[self class],[super class]);
}
return self;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGTeacher* lgter=[[LGTeacher alloc]init];
}
return 0;
}
输出结果:
**LGTeacher - LGTeacher**
分析:[self class] 打印LGTeacher能理解,但是[super class]怎么也是打印的LGTeacher呢?我们看源码class源码返回的是什么
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
源码看出class方法返回的是对象self->isa,方法调用默认有两个隐藏参数id和sel,id为消息接收者,此处的self应该就是消息接收者,只要知道消息接受者是谁,返回的就是谁。clang编译下LGTeacher.m文件看下底层编译实现
static instancetype _I_LGTeacher_init(LGTeacher * self, SEL _cmd) {
self = ((LGTeacher *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGTeacher"))}, sel_registerName("init"));
if (self) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_40_l7v4nh0543xbbd0lfg8dy5g00000gn_T_LGTeacher_81f7d8_mi_0,((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")),((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGTeacher"))}, sel_registerName("class")));
}
return self;
}
[self class]在底层是通过objc_msgSend消息转发的,有id和SEL两个参数,id为消息接收者即为LGTeacher;[super class]在底层是通过objc_msgSendSuper消息转发的,有__rw_objc_super和SEL两个参数,看下oc源码rw_objc_super结构
struct objc_super {
/// 消息接收者
__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
};
objc_super结构体中receiver还是LGTeacher,还有一个父类super_class。objc_msgSendSuper的作用就是去父类查找方法仅此而已,并不是返回父类。所以初始化类的时候使用[super init],返回的并不是父类而是自己,只是调用父类的方法初始化自己,如果不用父类方法初始化自己就不能继承父类的属性。
4. 内存平移和压栈
先看demo
- (void)viewDidLoad {
[super viewDidLoad];
LGPerson* p=[[LGPerson alloc]init];
p.kc_name=@"kc";
[p saySomething];
Class cls=[LGPerson class];
void* gy_p=&cls;
[(__bridge id)gy_p saySomething];
}
@implementation LGPerson
- (void)saySomething{
NSLog(@"%s - %@",__func__,self.kc_name);
}
输出:
**-[LGPerson saySomething] - kc**
**-[LGPerson saySomething] - <LGPerson: 0x282075400>**
分析:
- 为什么gy_p可以调用到saySomething方法呢?首先
对象方法saySomething在类LGPerson中,对象p通过isa关联到类LGPerson,只要gy_p和p指向的是同一个类那么就可以调用类中方法,p是一个指向堆区LGPerson对象的指针,p本身作为变量是在栈区的。gy_p是一个指向cls的栈区变量,也就是说gy_p也指向LGPerson类。方法调用的本质是objc_msgSend消息发送,此处的接收者都是LGPerson类,所以gy_p可以调用到saySomething。 - 为什么p调用saySomething,self.kc_name打印的是kc,但是gy_p调用self.kc_name打印的是LGPerson: 0x282075400>对象呢?因为p是对象,对象是需要开辟内存的,而gy_p只是栈区的指针,在栈区调用self.kc_name相当于gy_p
指针平移,指针的大小是8个字节,栈区压栈的规则是先入后出,地址方向是高地址到低地址,gy_p指针的下一个地址就是p的地址,所以打印的是LGPerson对象。lldb调试如下:
p的地址就是gy_p地址指针加8字节。
结构体压栈顺序
- 结构体存在于栈区,
结构体中后面的变量先入栈。
struct kc_struct{
NSNumber *num1;
NSNumber *num2;
} kc_struct;
- (void)viewDidLoad {
[super viewDidLoad];
struct kc_struct kc={@(10),@(20)};
}
lldb看下入栈情况
num2的地址0x000000016b0ddb28大于num1的地址0x000000016b0ddb20,栈区是高地址到地址,地址高的先入栈,所以num2先入栈。
函数参数压栈顺序
- 函数参数入栈顺序
按照参数顺序入栈a、b、c在栈区中地址由高到低,a最先入栈,然后b,最后c。
6. 类方法动态方法决议为什么在后面还要实现 resolveInstanceMethod?
类方法存在元类(以对象方法形式存在), 元类的父类最终是 NSObject 所以我们可以通过resolveInstanceMethod 防止 NSObject 中实现了对象方法!
7.类方法存在哪里?为什么要这么设计
对象方法存储在类中,类方法存储在元类中; 对象方法是由类实例化出来的,类是由元类实例化出来的;
- 底层不用对类方法和对象方法做区分,
本质上都是对象方法 - 这样设计更加的基于对象
8.一个类的类方法没有实现为什么可以调用 NSObject 同名对象方法
在方法快速查找时,会根据类的isa找到元类, 元类中没有改方法就会进入lookUpImpOrForward慢速查找。 在慢速查找流程中,会进行for递归查找,根据superclass查找到元类的父类,也就是根源类,而根元类的superclass指向类NSObject,所以会调用到 NSObject的同名对象方法。