2018最新面试题总结(一)

正所谓面试虐你千百遍你还待他如初恋~

1.如果对一个可变数组对象使用copy关键字会发生什么现象?

2.对于一个不可变数组对象经常使用 copy 关键字,为什么?如果改用strong关键字,可能造成什么问题?

3.简述KVO实现原理

4.消息传递的过程

5.nstimer使用需要注意什么?如何去解决?

6.说说响应者链

7.什么是元类?

8.自动释放池@autoreleasepool系统是什么时候释放的

9.多线程GCD,如何进行一个页面多个数据异步请求

10.block内修改成员变量的值会发生什么?如何去解决?为什么?

------------------------------------------------------------------------------------------

思考方向:

1.如果对一个可变数组对象使用copy关键字会发生什么现象?

不管是集合类对象(NSArray、NSDictionary、NSSet ... 之类的对象),还是非集合类对象(NSString, NSNumber ... 之类的对象),接收到copy和mutableCopy消息时,都遵循以下准则:
1. copy 返回的是不可变对象(immutableObject);如果用copy返回值调用mutable对象的方法就会crash。
2. mutableCopy 返回的是可变对象(mutableObject)。

2.对于一个不可变数组对象经常使用 copy 关键字,为什么?如果改用strong关键字,可能造成什么问题?

@property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作(就是把可变的赋值给不可变的),为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。

1. 因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本。
2. 如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。

3.简述KVO实现原理

    首先来看一张图:

      

   1.kvo是基于系统runtime实现的,当某个类的属性对象第一次被观察时,系统就会在运行期动态的去创建这个类的派生类,然后在这个类中重写基类中被观察属性的setting方法.

   2.每个类对象都有一个ISA指针指向当前的类,(关于ISA指针后续总结),当一个类对象第一次被观察的时候,那么此时系统会偷偷的把其ISA指针指向动态生的的派生类.从而在被监控的属性赋值时就执行的是派生类中该属性的setting方法

   3.观察依靠的就是NSObject中的两个基础方法: willChangeValueForKey:和 didChangeValueForKey:

     被观察属性发生改变之前一定会调用 willChangeValueForKey:方法来记录当前旧的值,而属性值发生改变调用 didChangeValueForKey:方法,从而系统的observeValueForKey:ofObject:change:context:方法被调用

4.消息传递的过程

    当一个方法被调用的时候系统会转成底层的一个c函数来实现.也就是objc_msgSend()

    objc_msgSend()函数会一句接受者(调用方法的对象)的类型和选择子(方法名)来调用适当的方法。

    接收者会根据isa指针找到接收者自己所属的类,然后在所属类的方法列表method list)中从上向下遍历。如果能找到与选择子名称相符的方法,就根据IMP指针跳转到方法的实现代码,调用这个方法的实现。如果找不到与选择子名称相符的方法,接收者会根据所属类的superClass指针,沿着类的继承体系继续向上查找(向父类查找),如果 能找到与名称相符的方法,就根据IMP指针跳转到方法的实现代码,调用这个方法的实现。如果在继承体系中还是找不到与选择子相符的方法,此时就会执行消息转发(message forwarding操作。

消息转发流程

     如果在整个类的继承体系中还是找不到与选择子相符的方法,也就是对象或者类对象收到了无法解读的消息,那么就会进入到消息转发环节。

     在编译期,向对象或者类对象发送了其无法解读的消息并不会报错,因为在运行期可以继续向类和元类(metaClass)中添加方法,所以编译器在编译期还无法确定类中到底会不会有某个方法的实现。当对象接收到无法解读的消息后,就会启动消息转发(message forwarding机制,我们可以在消息转发过程中告诉对象应该如处理未知消息。

     消息转发分为两个阶段。第一阶段叫做动态方法解析(dynamic method resolution,或者叫动态方法决议。第二阶段涉及到完整的消息转发机制(full forwarding mechanism,或者叫完整的消息转发原理

动态方法解析

   动态方法解析的意思就是,征询消息接受者所属的类,看其是否能动态添加方法,以处理当前这个未知的选择子(unknown selector。实例对象在接受到无法解读的消息后,首先会调用其所属类的下列类方法:


+ (BOOL)resolveInstanceMethod:(SEL)selector

   类对象在接受到无法解读的消息后,那么运行期系统就会调用另外的一个方法,如下:


+ (BOOL)resolveClassMethod:(SEL)selector

    如果运行期系统已经执行完了动态方法解析,那么消息接受者自己就无法再以动态新增方法的形式来响应包含该未知选择子的消息了,此时就进入了第二阶段——完整的消息转发。运行期系统会请求消息接受者以其他手段来处理与消息相关的方法调用。

完整的消息转发

    完整的消息转发又分为两个阶段,第一阶段称为备援接受者(replacement receiver),第二阶段才是启动完整的消息转发机制。

备援接收者(replacement receiver

    当前接受者如果不能处理这条消息,运行期系统会请求当前接受者让其他接受者处理这条消息,与之对应的方法是:


- (id)forwardingTargetForSelector:(SEL)selector

    方法参数代表未知的选择子,返回值为备援接受者,若当前接受者能找到备援接受者,就直接返回,这个未知的选择子将会交由备援接受者处理。如果找不到备援接受者,就返回nil,此时就会启用完整的消息转发机制

完整的消息转发

    如果转发算法已经来到了这一步,那么代表之前的所有转发尝试都失败了,此时只能启用完整的消息转发机制。完整的消息转发机制是这样的:首先创建NSInvocation对象,把尚未处理的那条消息有关的全部细节封装于这个NSInvocation对象中。此对象中包含选择子(selector)、目标(target)及参数。在触发NSInvocation对象时,消息派发系统(message-dispatch system将亲自触发,把消息派发给目标对象。此步骤中会调用下面这个方法来转发消息:


- (void)forwardInvocation:(NSInvocation *)invocation

消息派发系统触发消息前,会以某种方式改变消息内容,包括 但不限于额外追加一个参数、改变选择子等。

实现此方法时,如果发现调用操作不应该由本类处理,则需要沿着继承体系,调用父类的同名方法,这样一来,继承体系中的每个类都有机会处理这个调用请求,直至rootClass,也就是NSObject类。如果最后调用了NSObject的类方法,那么该方法还会继而调用”doesNotRecognizeSelector:“以抛出异常,此异常表明选择子最终也未能得到处理。消息转发到此结束。does NotRecognizeSelector:你可能感到陌生,但是对于类似于unrecognized selector send to instance xxx这样的错误,你可能并不陌生。这种错误通常是因为调用了某个对象或者某个类里不存在的方法,从而触发了消息转发机制,最终把这个未识别的消息发送给了NSObject的默认实现。

ps:方法缓存

         一个方法的执行其实底层需要很多步骤。正因如此,objc_msgSend()会将调用过且匹配到的方法缓存在快速映射表(fast map中,快速映射表就是方法的缓存表。每个类都有这样一个缓存。所以,即便子类实例从父类的方法列表中取过了某个对象方法,那么子类的方法缓存表中也会缓存父类的这个方法,下次调用这个方法,会优先去当前类(对象所属的类)的方法缓存表中查找这个方法,这样的好处是显而易见的,减少了漫长的方法查找过程,使得方法的调用更快。同样,如果父类实例对象调用了同样的方法,也会在父类的方法缓存表中缓存这个方法。同理,如果用一个子类对象调用某个类方法,也会在子类的metaclass里缓存一份。而当用一个父类对象去调用那个类方法的时候,也会在父类的metaclass里缓存一份。

5.nstimer使用需要注意什么?如何去解决?

     注意循环引用问题:     解决方案:

      1:主动调用销毁方法打破循环

      2:使用代理类

      3:引入块的概念

6.说说响应者链

 

      响应者链:在iOS程序中无论是最后面的UIWindow还是最前面的某个按钮,它们的摆放是有前后关系的,一个控件可以放到另一个控件上面或下面,那么用户点击某个控件时是触发上面的控件还是下面的控件呢,这种先后关系构成一个链条就叫“响应者链”。也可以说,响应者链是由多个响应者对象连接起来的链条。

     事件的传递与响应:
1、当一个事件发生后,事件会从父控件传给子控件,也就是说由UIApplication -> UIWindow -> UIView -> initial view,以上就是事件的传递,也就是寻找最合适的view的过程。

2、接下来是事件的响应。首先看initial view能否处理这个事件,如果不能则会将事件传递给其上级视图(inital view的superView);如果上级视图仍然无法处理则会继续往上传递;一直传递到视图控制器view controller,首先判断视图控制器的根视图view是否能处理此事件;如果不能则接着判断该视图控制器能否处理此事件,如果还是不能则继续向上传 递;(对于第二个图视图控制器本身还在另一个视图控制器中,则继续交给父视图控制器的根视图,如果根视图不能处理则交给父视图控制器处理);一直到 window,如果window还是不能处理此事件则继续交给application处理,如果最后application还是不能处理此事件则将其丢弃

3、在事件的响应中,如果某个控件实现了touches...方法,则这个事件将由该控件来接受,如果调用了[supertouches….];就会将事件顺着响应者链条往上传递,传递给上一个响应者;接着就会调用上一个响应者的touches….方法


7.什么是元类?

元类是一个类对象的类。每一个类有他自己独一无二的元类(因为每个类能够有自己独一无二的方法列表)。这就意味着类对象的类并不是和他们一样的类。

元类能确保类对象有所有底层类的实例和类方法,中间加上所有自己的类方法。所有类继承自NSObject,这意味着NSObject所有的实例和协议方法为所有类(和元类)对象都定义了。

所有元类使用基类的元类(NSObject 元类)来作为他们的类,包括只在运行时自定义的类的元类。


8.自动释放池@autoreleasepool系统是什么时候释放的

 AutoreleasePool是在RunLoop即将进入RunLoop和准备进入休眠这两种状态的时候被创建和销毁的。

所以AutoreleasePool的释放有如下两种情况。 

一是Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop。 

二是手动调用AutoreleasePool的释放方法(drain方法)来销毁AutoreleasePool   

9.多线程GCD,如何进行一个页面多个数据异步请求

    GCD组的概念

10.block内修改成员变量的值会发生什么?如何去解决?为什么?

    Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。__block 所起到的作用就是只要观察到该变量被 block 所持有,就将外部变量在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值