做iOS将近五年时间了,但是总会觉得自己还是个渣渣,一些底层的东西还是不懂,平常也没有怎样深入的去学习,只有在做面试准备的时候才会临阵磨枪一下,相信很多的童鞋是有同感的。所以从今天开始看到不错的面试题的话,我将都总结到这里,以便自己日后找工作用,哈哈~~
1. 一个objc对象如何进行内存布局(考虑有父类的情况)?
我们先来看一下objc_class的内部结构,如图:
class对象内部结构
从图上可以看出objc_class是一个结构体。
isa:每个对象内部都有一个isa指针,指向它的类对象;在oc中每个类也是一个对象,也会有一个所属类,这个类就是元类。
cache:用于缓存最近使用的方法。对象接收到一个消息时,会根据isa指针去查找能够响应这个消息的对象。
我们知道,在实际使用中,某个对象只有一部分方法是常用的,如果每次有消息来都遍历methodlists方法列表,性能肯定会有所下降。这时候cache就派上用场了。当某个方法调用过之后,就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果没查到,才会去methodLists中查找,这样会提要调用的效率。
关于元类:
当给对象发送消息时,消息是在寻找这个对象的父类的方法列表;当给类发送消息时,消息是在寻找这个类的元类的方法列表。
super_class:指向它的父类对象;
2. runtime是如何通过selector找到对应的IMP地址(分别考虑实例方法和类方法)?Selector、Method和IMP的区别和联系?
先来简单看下各个名词的意思:
@ selector():是SEL类型的,对应的是每个方法的位置ID,当我们寻找方法的时候寻找的是方法的ID。简单来说,SEL只是方法的编号。
//定义一个类方法的指针,selector查找是当前类(包含子类)的方法
SEL method = @selector(func);
IMP:是一个函数指针,保存了方法的地址;
以上,当向一个对象发送消息时,objc_msgSend()函数将会用对象的isa指针找到selector对应的方法编号,然后根据方法编号取得IMP,即
IMP imp = [self methodForSelector:@selector(addNewMethod:)];或者
IMP imp = [NSObject instanceMethodForSelector:@selector(addNewMethod:)];
肯定会疑问为什么不直接获取函数指针,而要从SEL这个编号走一圈再回到函数指针呢?
有了SEL这个中间过程,我们就可以对一个编号和什么方法映射做些操作,也就是说我们可以一个SEL指向不同的函数指针(方法实现),这样就可以完成一个方法名在不同时候执行不同的函数体。另外可以将SEL作为参数传递给不同的类执行。也就是说我们某些业务我们只知道方法名旦需要根据不同的情况让不同的类执行的时候,SEL可以帮助我们。
3 .消息的转发机制?
- OC最重要的就是消息转发机制了~不接受反驳!!!
首先先看下消息的调用问题。[receiver doSomething];编译后变成 objc_msgsend(receiver @selector(doSomething));则表示你需要向receiver发送一个消息,但是此时receiver不一定调用了doSomething方法,只有到了运行时,才会去看receiver是否响应了这个消息,再决定是执行这个方法,还是其他方法,或者转发给其他对象。
由此也会得到一个经常会被问到的知识点:向nil对象发送消息是不会crash的。
转发机制大概如下:
1)runtime首先会进行消息绑定,通过父类的cache和分发表来绑定消息的执行方法;
2)如果没有,运行时会给类的对象发送+(BOOL)resolveInstanceMethod:(SEL)Name消息,这个消息的执行方法允许你在运行时给该类增加执行方法,如果消息返回是yes,则会对消息绑定;
//当某个对象不能接受某个selector时,向对象所属的类动态添加所需的selector:
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(eat)) {
class_addMethod([Person class], sel, (IMP)eat, "");
//返回Yes将不再调用该方法下面的方法
return Yes;
}
return [super resolveInstanceMethod:sel];
}
void eat(){
NSLog(@"eat");
}
3)如果步骤2还不行,runtime会发送
-(id)forwardingTargetForSelector(SEL)aSelector,这个消息返回另外一个能响应该消息的对象,然后把消息转发给别的对象;
//当某个对象不能接受某个selector时,将对该selector的调用转发给另一个对象
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(eat1)) {
//无法处理的selector转 发给另一个对象
return [[Student alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
4)如果3步骤返回的对象是nil,或者是self,它会发送 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector,这里会返回一个方法签名用于Invocation,即-(void)forwardInvocation:(NSInvocation *)anInvocation.通过这个方法,你可以把消息转发给任意拥有相应执行方法的类(其实就是模拟多继承)
//为另一个类实现的消息创建一个有效的方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector{
NSString *sel = NSStringFromSelector(selector);
if ([sel rangeOfString:@"set"].location == 0){
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}else{
return [NSMethodSignature signatureWithObjCTypes:"@@:"];
}
}
延伸拓展:
消息的调用有两种方式,一种是SEL,即performSelector:withObject:函数可以直接调用这个消息,但是这个函数有局限性,其参数数量不能超过两个,否则要做很麻烦的处理,与之相对,另一种NSInvocation也是一种消息调用的方法,并且它的参数没有个数限制。
/** 这个是没有参数的方法的调用 **/
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
SEL myMethod = @selector(myLog);
//创建一个函数签名,这个签名可以是任意的,但需要注意,签名函数的参数数量要和调用的一致
NSMethodSignature *sig = [NSObject instanceMethodSignatureForSelector:@selector(init)];
//通过签名初始化
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
//设置target
[invocation setTarget:self];
//设置selector
[invocation setSelector:myMethod];
//消息调用
[invocation invoke];
}
-(void)myLog {
NSLog(@"我在这是测试的");
}
/** 这个是多参数的方法的调用 **/
//注意:
//1.这里设置的参数的index需要从2开始,因为前两个被selector和target占用了
//2.传参方式是传递参数的地址
- (void)viewDidLoad {
[super viewDidLoad];
SEL myMethod = @selector(myLog:parm:parm:);
NSMethodSignature * sig = [[self class] instanceMethodSignatureForSelector:myMethod];
NSInvocation * invocatin = [NSInvocation invocationWithMethodSignature:sig];
[invocatin setTarget:self];
[invocatin setSelector:myMethod];
int a=1;
int b=2;
int c=3;
[invocatin setArgument:&a atIndex:2];
[invocatin setArgument:&b atIndex:3];
[invocatin setArgument:&c atIndex:4];
[invocatin invoke];
}
-(void)myLog:(int)a parm:(int)b parm:(int)c{
NSLog(@"MyLog%d:%d:%d",a,b,c);
}
以上两个为没有返回值的消息的调用方式,下面来看一个有返回值的,但是这个返回值并不是其所调用函数的返回值,需要我们手动设置:
- (void)viewDidLoad {
[super viewDidLoad];
SEL myMethod = @selector(myLog:parm:parm:);
NSMethodSignature * sig = [[self class] instanceMethodSignatureForSelector:myMethod];
NSInvocation * invocatin = [NSInvocation invocationWithMethodSignature:sig];
[invocatin setTarget:self];
[invocatin setSelector:myMethod];
ViewController *view = self;
int a=1;
int b=2;
int c=3;
[invocatin setArgument:&view atIndex:0];
[invocatin setArgument:&myMethod atIndex:1];
[invocatin setArgument:&a atIndex:2];
[invocatin setArgument:&b atIndex:3];
[invocatin setArgument:&c atIndex:4];
[invocatin retainArguments];
//我们将c的值设置为返回值
[invocatin setReturnValue:&c];
int d;
//取这个返回值
[invocatin getReturnValue:&d];
NSLog(@"%d",d);//打印出来是 3
[invocatin invoke];
}
-(int)myLog:(int)a parm:(int)b parm:(int)c{
NSLog(@"MyLog%d:%d:%d --%d",a,b,c,a+b+c);//打印出来1,2,3,6
return a+b+c;
}
注意:这里还要提一下NSMethodSignature,一个NSMethodSignature对象记录着某个方法的返回值类型信息以及参数类型信息。它用于转发消息接收者无法响应的消息。可以通过methodSignatureForSelector:获取类方法或实例方法的签名;也可以用instanceMethodSignatureForSelector:获取实例方法的签名。
5)如果4步骤都不行,运行时就会发送消息 - (void)doesNotRecognizeSelector:(SEL)aSelector 给你的对象,执行这个方法将会抛出一个异常,然后程序就崩溃了~







网友评论