iOS 最新面试题(2)
1. runtime
objective-c 是面相运行时的语言(runtime oriented language),就是说它会尽可能的把编译和链接时要执行的逻辑延迟到运行时。这就给了你很大的灵活性,你可以按需要把消息重定向给合适的对象,你甚 至可以交换方法的实现,等等。
runtime简称运行时。就是在运行的时候的一些机制,其中最主要的是消息机制。oc的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编 译阶段,oc可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而c语言在编译阶段就会报错)。只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用。
以下面的代码为例:
[obj maketext];
其中obj是一个对象,maketext是一个函数名称。对于这样一个简单的调用。在编译时runtime会将上述代码转化成
objc_msgsend(obj,@selector(maketext));
首先,编译器将代码[obj maketext];转化为objc_msgsend(obj, @selector (maketext));,在objc_msgsend函数中。首先通过obj的isa指针找到obj对应的class。在class中先去cache中 通过sel查找对应函数method(猜测cache中method列表是以sel为key通过hash表来存储的,这样能提高函数查找速度),若 cache中未找到。再去methodlist中查找,若methodlist中未找到,则取superclass中查找。若能找到,则将method加 入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。
objective-c runtime 是什么?
objective-c 的 runtime 是一个运行时库(runtime library),它是一个主要使用 c 和汇编写的库,为 c 添加了面相对象的能力并创造了 objective-c。这就是说它在类信息(class information) 中被加载,完成所有的方法分发,方法转发,等等。objective-c runtime 创建了所有需要的结构体,让 objective-c 的面相对象变为可能。
method swizzling 原理
在objective-c中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用objective-c的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。imp有点类似函数指针,指向具体的method实现。
我们可以利用 method_exchangeimplementations 来交换2个方法中的imp,
我们可以利用 class_replacemethod 来修改类,
我们可以利用 method_setimplementation 来直接设置某个方法的imp,……
归根结底,都是偷换了selector的imp。
2. gcd实现1,2并行和3串行和45串行,4,5是并行。即3依赖1,2的执行,45依赖3的执行。
队列组的方式
- (void) methodone{ dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, dispatch_get_global_queue(dispatch_queue_priority_default, 0), ^{ nslog(@"%d",1); }); dispatch_group_async(group, dispatch_get_global_queue(dispatch_queue_priority_default, 0), ^{ nslog(@"%d",2); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ nslog(@"3"); dispatch_group_t group1 = dispatch_group_create(); dispatch_group_async(group1, dispatch_get_global_queue(dispatch_queue_priority_default, 0), ^{ nslog(@"%d",4); }); dispatch_group_async(group1, dispatch_get_global_queue(dispatch_queue_priority_default, 0), ^{ nslog(@"%d",5); }); }); }
串行队列:队列中的任务只会顺序执行
dispatch_queue_t q = dispatch_queue_create(“....”, dispatch_queue_serial);
并行队列: 队列中的任务通常会并发执行。
dispatch_queue_t q = dispatch_queue_create("......", dispatch_queue_concurrent);
全局队列:是系统开发的,直接拿过来用就可以;与并行队列类似,但调试时,无法确认操作所在队列 。
dispatch_queue_t q = dispatch_get_global_queue(dispatch_queue_priority_default, 0);
主队列:每一个应用开发程序对应唯一一个主队列,直接get即可;在多线程开发中,使用主队列更新ui。
dispatch_queue_t q = dispatch_get_main_queue();
主队列是gcd自带的串行队列,会在主线程中执行。异步全局并发队列 开启新线程,并发执行。
并行队列里开启同步任务是有执行顺序的,只有异步才没有顺序。
串行队列开启异步任务,是有顺序的。
串行队列开启异步任务后嵌套同步任务造成死锁。
3. 深浅复制和属性为copy,strong值的变化问题
浅复制:只复制指向对象的指针,而不复制引用对象本身。对于浅复制来说,a和a_copy指向的是同一个内存资源,复制的只不个是一个指针,对象本身资源还是只有一份,那如果我们对a_copy执行了修改操作,那么发现a引用的对象同样被修改了。深复制就好理解了,内存中存在了两份独立对象本身。
在objective-c中并不是所有的对象都支持copy,mutablecopy,遵守nscopying协议的类才可以发送copy消息,遵守nsmutablecopying协议的类才可以发送mutablecopy消息。
[immutableobject copy] // 浅拷贝
[immutableobject mutablecopy] //深拷贝
[mutableobject copy] //深拷贝
[mutableobject mutablecopy] //深拷贝
属性设为copy,指定此属性的值不可更改,防止可变字符串更改自身的值的时候不会影响到对象属性(如nsstring,nsarray,nsdictionary)的值。strong此属性的指会随着变化而变化。copy是内容拷贝,strong是指针拷贝。
4.nstimer创建后,会在哪个线程运行。
用scheduledtimerwithtimeinterval创建的,在哪个线程创建就会被加入哪个线程的runloop中就运行在哪个线程。
自己创建的timer,加入到哪个线程的runloop中就运行在哪个线程。
5. kvo,nsnotification,delegate及block区别
kvo就是cocoa框架实现的观察者模式,一般同kvc搭配使用,通过kvo可以监测一个值的变化,比如view的高度变化。是一对多的关系,一个值的变化会通知所有的观察者。
nsnotification是通知,也是一对多的使用场景。在某些情况下,kvo和nsnotification是一样的,都是状态变化之后告知对方。nsnotification的特点,就是需要被观察者先主动发出通知,然后观察者注册监听后再来进行响应,比kvo多了发送通知的一步,但是其优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,使用也更灵活。
delegate 是代理,就是我不想做的事情交给别人做。比如狗需要吃饭,就通过delegate通知主人,主人就会给他做饭、盛饭、倒水,这些操作,这些狗都不需要关心,只需要调用delegate(代理人)就可以了,由其他类完成所需要的操作。所以delegate是一对一关系。
block是delegate的另一种形式,是函数式编程的一种形式。使用场景跟delegate一样,相比delegate更灵活,而且代理的实现更直观。
kvo一般的使用场景是数据,需求是数据变化,比如股票价格变化,我们一般使用kvo(观察者模式)。delegate一般的使用场景是行为,需求是需要别人帮我做一件事情,比如买卖股票,我们一般使用delegate。notification一般是进行全局通知,比如利好消息一出,通知大家去买入。delegate是强关联,就是委托和代理双方互相知道,你委托别人买股票你就需要知道经纪人,经纪人也不要知道自己的顾客。notification是弱关联,利好消息发出,你不需要知道是谁发的也可以做出相应的反应,同理发消息的人也不需要知道接收的人也可以正常发出消息。
6. 如何让计时器调用一个类方法
计时器只能调用实例方法,但是可以在这个实例方法里面调用静态方法。
使用计时器需要注意,计时器一定要加入runloop中,并且选好model才能运行。scheduledtimerwithtimeinterval方法创建一个计时器并加入到runloop中所以可以直接使用。
如果计时器的repeats选择yes说明这个计时器会重复执行,一定要在合适的时机调用计时器的invalid。不能在dealloc中调用,因为一旦设置为repeats 为yes,计时器会强持有self,导致dealloc永远不会被调用,这个类就永远无法被释放。比如可以在viewdiddisappear中调用,这样当类需要被回收的时候就可以正常进入dealloc中了。
7. 调用一个类的静态方法需不需要release?
静态方法,就是类方法,不需要,类方法对象放在autorelease中
8. static作用?
(1)函数体内 static 变量的作用范围为该函数体,不同于 auto 变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
(2)在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
(3)在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明
它的模块内;
(4)在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(5)在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的static 成员变量。
9. nsobject的load和initialize方法
load和initialize的共同特点
- 在不考虑开发者主动使用的情况下,系统最多会调用一次
- 如果父类和子类都被调用,父类的调用一定在子类之前
- 都是为了应用运行提前创建合适的运行环境
- 在使用时都不要过重地依赖于这两个方法,除非真正必要
load和initialize的区别
load方法
调用时机比较早,运行环境有不确定因素。具体说来,在ios上通常就是app启动时进行加载,但当load调用的时候,并不能保证所有类都加载完成且可用,必要时还要自己负责做auto release处理。对于有依赖关系的两个库中,被依赖的类的load会优先调用。但在一个库之内,调用顺序是不确定的。
对于一个类而言,没有load方法实现就不会调用,不会考虑对nsobject的继承。
一个类的load方法不用写明[super load],父类就会收到调用,并且在子类之前。
category的load也会收到调用,但顺序上在主类的load调用之后。
不会直接触发initialize的调用。
initialize方法相关要点
initialize的自然调用是在第一次主动使用当前类的时候。
在initialize方法收到调用时,运行环境基本健全。
initialize的运行过程中是能保证线程安全的。
和load不同,即使子类不实现initialize方法,会把父类的实现继承过来调用一遍。注意的是在此之前,父类的方法已经被执行过一次了,同样不需要super调用。
由于initialize的这些特点,使得其应用比load要略微广泛一些。可用来做一些初始化工作,或者单例模式的一种实现方案。
10. 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
不能向编译后得到的类中增加实例变量;
能向运行时创建的类中添加实例变量;
因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表 和 instance_size 实例变量的内存大小已经确定,同时runtime 会调用 class_setivarlayout 或 class_setweakivarlayout 来处理 strong weak 引用。所以不能向存在的类中添加实例变量;
运行时创建的类是可以添加实例变量,调用 class_addivar 函数。但是得在调用 objc_allocateclasspair 之后,objc_registerclasspair 之前,原因同上。
上一篇: disconf原理 “入坑”指南
下一篇: C语言 - 链表的基本操作