《招聘一个靠谱的 iOS》—参考答案(下)
说明:面试题来源是微博@我就叫Sunny怎么了的这篇博文:《招聘一个靠谱的 iOS》,其中共55题,除第一题为纠错题外,其他54道均为简答题。
出题者简介: 孙源(sunnyxx),目前就职于百度,负责百度知道 iOS 客户端的开发工作,对技术喜欢刨根问底和总结最佳实践,热爱分享和开源,维护一个叫 forkingdog 的开源小组。
答案为微博@iOS程序犭袁整理,未经出题者校对,如有纰漏,请向微博@iOS程序犭袁指正。
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#索引)索引
25. _objc_msgForward
函数是做什么的,直接调用它将会发生什么?
26. runtime如何实现weak变量的自动置nil?
27. 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
28. runloop和线程有什么关系?
29. runloop的mode作用是什么?
30. 以+ scheduledTimerWithTimeInterval...的方式触发的timer,在滑动页面上的列表时,timer会暂定回调,为什么?如何解决?
31. 猜想runloop内部是如何实现的?
32. objc使用什么机制管理对象内存?
33. ARC通过什么方式帮助开发者管理内存?
34. 不手动指定autoreleasepool的前提下,一个autorealese对象在什么时刻释放?(比如在一个vc的viewDidLoad中创建)
35. BAD_ACCESS在什么情况下出现?
36. 苹果是如何实现autoreleasepool的?
37. 使用block时什么情况会发生引用循环,如何解决?
38. 在block内如何修改block外部变量?
39. 使用系统的某些block api(如UIView的block版本写动画时),是否也考虑引用循环问题?
40. GCD的队列(dispatch_queue_t)分哪两种类型?
41. 如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)
42. dispatch_barrier_async的作用是什么?
43. 苹果为什么要废弃dispatch_get_current_queue?
44. 以下代码运行结果如何?
- (void)viewDidLoad{[super viewDidLoad];NSLog(@"1");dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"2");});NSLog(@"3");}
45. addObserver:forKeyPath:options:context:各个参数的作用分别是什么,observer中需要实现哪个方法才能获得KVO回调?
46. 如何手动触发一个value的KVO
47. 若一个类有实例变量 NSString *_foo ,调用setValue:forKey:时,可以以foo还是 _foo 作为key?
48. KVC的keyPath中的集合运算符如何使用?
49. KVC和KVO的keyPath一定是属性么?
50. 如何关闭默认的KVO的默认实现,并进入自定义的KVO实现?
51. apple用什么方式实现对一个对象的KVO?
52. IBOutlet连出来的视图属性为什么可以被设置成weak?
53. IB中User Defined Runtime Attributes如何使用?
54. 如何调试BAD_ACCESS错误
55. lldb(gdb)常用的调试命令?
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#25-_objc_msgforward函数是做什么的直接调用它将会发生什么)25. _objc_msgForward
函数是做什么的,直接调用它将会发生什么?
_objc_msgForward
是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward
会尝试做消息转发。
我们可以这样创建一个_objc_msgForward
对象:
IMP msgForwardIMP = _objc_msgForward;
在上篇中的《objc中向一个对象发送消息[obj foo]
和objc_msgSend()
函数之间有什么关系?》曾提到objc_msgSend
在“消息传递”中的作用。在“消息传递”过程中,objc_msgSend
的动作比较清晰:首先在 Class 中的缓存查找 IMP (没缓存则初始化缓存),如果没找到,则向父类的 Class 查找。如果一直查找到根类仍旧没有实现,则用_objc_msgForward
函数指针代替 IMP 。最后,执行这个 IMP 。
Objective-C运行时是开源的,所以我们可以看到它的实现。打开 Apple Open Source 里Mac代码里的obj包 下载一个最新版本,找到 objc-runtime-new.mm
,进入之后搜索_objc_msgForward
。
里面有对_objc_msgForward
的功能解释:
enter image description here
/************************************************************************ lookUpImpOrForward.* The standard IMP lookup. * initialize==NO tries to avoid +initialize (but sometimes fails)* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)* Most callers should use initialize==YES and cache==YES.* inst is an instance of cls or a subclass thereof, or nil if none is known. * If cls is an un-initialized metaclass then a non-nil inst is faster.* May return _objc_msgForward_impcache. IMPs destined for external use * must be converted to _objc_msgForward or _objc_msgForward_stret.* If you don't want forwarding at all, use lookUpImpOrNil() instead.**********************************************************************/
对 objc-runtime-new.mm
文件里与_objc_msgForward
有关的三个函数使用伪代码展示下:
// objc-runtime-new.mm 文件里与 _objc_msgForward 有关的三个函数使用伪代码展示// Created by https://github.com/ChenYilong// Copyright (c) 微博@iOS程序犭袁(http://weibo.com/luohanchenyilong/). All rights reserved.// 同时,这也是 obj_msgSend 的实现过程id objc_msgSend(id self, SEL op, ...) { if (!self) return nil; IMP imp = class_getMethodImplementation(self->isa, SEL op); imp(self, op, ...); //调用这个函数,伪代码...}//查找IMPIMP class_getMethodImplementation(Class cls, SEL sel) { if (!cls || !sel) return nil; IMP imp = lookUpImpOrNil(cls, sel); if (!imp) return _objc_msgForward; //_objc_msgForward 用于消息转发 return imp;}IMP lookUpImpOrNil(Class cls, SEL sel) { if (!cls->initialize()) { _class_initialize(cls); } Class curClass = cls; IMP imp = nil; do { //先查缓存,缓存没有时重建,仍旧没有则向父类查询 if (!curClass) break; if (!curClass->cache) fill_cache(cls, curClass); imp = cache_getImp(curClass, sel); if (imp) break; } while (curClass = curClass->superclass); return imp;}
虽然Apple没有公开_objc_msgForward
的实现源码,但是我们还是能得出结论:
_objc_msgForward
是一个函数指针(和 IMP 的类型一样),是用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward
会尝试做消息转发。
在上篇中的《objc中向一个对象发送消息[obj foo]
和objc_msgSend()
函数之间有什么关系?》曾提到objc_msgSend
在“消息传递”中的作用。在“消息传递”过程中,objc_msgSend
的动作比较清晰:首先在 Class 中的缓存查找 IMP (没缓存则初始化缓存),如果没找到,则向父类的 Class 查找。如果一直查找到根类仍旧没有实现,则用_objc_msgForward
函数指针代替 IMP 。最后,执行这个 IMP 。
为了展示消息转发的具体动作,这里尝试向一个对象发送一条错误的消息,并查看一下_objc_msgForward
是如何进行转发的。
首先开启调试模式、打印出所有运行时发送的消息: 可以在代码里执行下面的方法:
(void)instrumentObjcMessageSends(YES);
或者断点暂停程序运行,并在 gdb 中输入下面的命令:
call (void)instrumentObjcMessageSends(YES)
之后,运行时发送的所有消息都会打印到/tmp/msgSend-xxxx
文件里了。
终端中输入命令前往:
open /private/tmp enter image description here
可能看到有多条,找到最新生成的,双击打开
在模拟器上执行执行以下语句(这一套调试方案仅适用于模拟器,真机不可用,关于该调试方案的拓展链接: Can the messages sent to an object in Objective-C be monitored or printed out? ),向一个对象发送一条错误的消息:
//// main.m// CYLObjcMsgForwardTest//// Created by http://weibo.com/luohanchenyilong/.// Copyright (c) 2015年 微博@iOS程序犭袁. All rights reserved.//#import <UIKit/UIKit.h>#import "AppDelegate.h"#import "CYLTest.h"int main(int argc, char * argv[]) { @autoreleasepool { CYLTest *test = [[CYLTest alloc] init]; [test performSelector:(@selector(iOS程序犭袁))]; return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }} enter image description here
你可以在/tmp/msgSend-xxxx
(我这一次是/tmp/msgSend-9805
)文件里,看到打印出来:
enter image description here
- CYLTest NSObject initialize+ CYLTest NSObject alloc- CYLTest NSObject init- CYLTest NSObject performSelector:+ CYLTest NSObject resolveInstanceMethod:+ CYLTest NSObject resolveInstanceMethod:- CYLTest NSObject forwardingTargetForSelector:- CYLTest NSObject forwardingTargetForSelector:- CYLTest NSObject methodSignatureForSelector:- CYLTest NSObject methodSignatureForSelector:- CYLTest NSObject class- CYLTest NSObject doesNotRecognizeSelector:- CYLTest NSObject doesNotRecognizeSelector:- CYLTest NSObject class
结合《NSObject官方文档》,排除掉 NSObject 做的事,剩下的就是_objc_msgForward
消息转发做的几件事:
调用resolveInstanceMethod:
方法 (或 resolveClassMethod:
)。允许用户在此时为该 Class 动态添加实现。如果有实现了,则调用并返回YES,那么重新开始objc_msgSend
流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod
。如果仍没实现,继续下面的动作。
调用forwardingTargetForSelector:
方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非 nil 对象。否则返回 nil ,继续下面的动作。注意,这里不要返回 self ,否则会形成死循环。
调用methodSignatureForSelector:
方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector
抛出异常。如果能获取,则返回非nil:创建一个 NSlnvocation 并传给forwardInvocation:
。
调用forwardInvocation:
方法,将第3步获取到的方法签名包装成 Invocation 传入,如何处理就在这里面了,并返回非ni。
调用doesNotRecognizeSelector:
,默认的实现是抛出异常。如果第3步没能获得一个方法签名,执行该步骤。
上面前4个方法均是模板方法,开发者可以override,由 runtime 来调用。最常见的实现消息转发:就是重写方法3和4,吞掉一个消息或者代理给其他对象都是没问题的
也就是说_objc_msgForward
在进行消息转发的过程中会涉及以下这几个方法:
resolveInstanceMethod:
方法 (或 resolveClassMethod:
)。
forwardingTargetForSelector:
方法
methodSignatureForSelector:
方法
forwardInvocation:
方法
doesNotRecognizeSelector:
方法
为了能更清晰地理解这些方法的作用,git仓库里也给出了一个Demo,名称叫“ _objc_msgForward_demo
”,可运行起来看看。
下面回答下第二个问题“直接_objc_msgForward
调用它将会发生什么?”
直接调用_objc_msgForward
是非常危险的事,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事。
就好像跑酷,干得好,叫“耍酷”,干不好就叫“作死”。
正如前文所说:
_objc_msgForward
是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward
会尝试做消息转发。
如何调用_objc_msgForward
? _objc_msgForward
隶属 C 语言,有三个参数 :
--
_objc_msgForward
参数
类型
所属对象
id类型
方法名
SEL类型
可变参数
可变参数类型
首先了解下如何调用 IMP 类型的方法,IMP类型是如下格式:
为了直观,我们可以通过如下方式定义一个 IMP类型 :
typedef void (*voidIMP)(id, SEL, ...)
一旦调用_objc_msgForward
,将跳过查找 IMP 的过程,直接触发“消息转发”,
如果调用了_objc_msgForward
,即使这个对象确实已经实现了这个方法,你也会告诉objc_msgSend
:
“我没有在这个对象里找到这个方法的实现”
想象下objc_msgSend
会怎么做?通常情况下,下面这张图就是你正常走objc_msgSend
过程,和直接调用_objc_msgForward
的前后差别:
有哪些场景需要直接调用_objc_msgForward
?最常见的场景是:你想获取某方法所对应的NSInvocation
对象。举例说明:
JSPatch (Github 链接)就是直接调用_objc_msgForward
来实现其核心功能的:
JSPatch 以小巧的体积做到了让JS调用/替换任意OC方法,让iOS APP具备热更新的能力。
作者的博文《JSPatch实现原理详解》详细记录了实现原理,有兴趣可以看下。
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#26-runtime如何实现weak变量的自动置nil)26. runtime如何实现weak变量的自动置nil?
runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
在上篇中的《runtime 如何实现 weak 属性》有论述。(注:在上篇的《使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?》里给出的“对象的内存销毁时间表”也提到__weak
引用的解除时间。)
我们可以设计一个函数(伪代码)来表示上述机制:
objc_storeWeak(&a, b)
函数:
objc_storeWeak
函数把第二个参数--赋值对象(b)的内存地址作为键值key,将第一个参数--weak修饰的属性变量(a)的内存地址(&a)作为value,注册到 weak 表中。如果第二个参数(b)为0(nil),那么把变量(a)的内存地址(&a)从weak表中删除,
你可以把objc_storeWeak(&a, b)
理解为:objc_storeWeak(value, key)
,并且当key变nil,将value置nil。
在b非nil时,a和b指向同一个内存地址,在b变nil时,a变nil。此时向a发送消息不会崩溃:在Objective-C中向nil发送消息是安全的。
而如果a是由assign修饰的,则: 在b非nil时,a和b指向同一个内存地址,在b变nil时,a还是指向该内存地址,变野指针。此时向a发送消息极易崩溃。
下面我们将基于objc_storeWeak(&a, b)
函数,使用伪代码模拟“runtime如何实现weak属性”:
// 使用伪代码模拟:runtime如何实现weak属性// http://weibo.com/luohanchenyilong/// https://github.com/ChenYilong id obj1; objc_initWeak(&obj1, obj);/obj引用计数变为0,变量作用域结束/ objc_destroyWeak(&obj1);
下面对用到的两个方法objc_initWeak
和objc_destroyWeak
做下解释:
总体说来,作用是: 通过objc_initWeak
函数初始化“附有weak修饰符的变量(obj1)”,在变量作用域结束时通过objc_destoryWeak
函数释放该变量(obj1)。
下面分别介绍下方法的内部实现:
objc_initWeak
函数的实现是这样的:在将“附有weak修饰符的变量(obj1)”初始化为0(nil)后,会将“赋值对象”(obj)作为参数,调用objc_storeWeak
函数。
obj1 = 0;obj_storeWeak(&obj1, obj);
也就是说:
weak 修饰的指针默认值是 nil (在Objective-C中向nil发送消息是安全的)
然后obj_destroyWeak
函数将0(nil)作为参数,调用objc_storeWeak
函数。
objc_storeWeak(&obj1, 0);
前面的源代码与下列源代码相同。
// 使用伪代码模拟:runtime如何实现weak属性// http://weibo.com/luohanchenyilong/// https://github.com/ChenYilongid obj1;obj1 = 0;objc_storeWeak(&obj1, obj);/* ... obj的引用计数变为0,被置nil ... */objc_storeWeak(&obj1, 0);
objc_storeWeak
函数把第二个参数--赋值对象(obj)的内存地址作为键值,将第一个参数--weak修饰的属性变量(obj1)的内存地址注册到 weak 表中。如果第二个参数(obj)为0(nil),那么把变量(obj1)的地址从weak表中删除。
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#27-能否向编译后得到的类中增加实例变量能否向运行时创建的类中添加实例变量为什么)27. 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
不能向编译后得到的类中增加实例变量;
能向运行时创建的类中添加实例变量;
解释下:
因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list
实例变量的链表 和 instance_size
实例变量的内存大小已经确定,同时runtime 会调用 class_setIvarLayout
或 class_setWeakIvarLayout
来处理 strong weak 引用。所以不能向存在的类中添加实例变量;
运行时创建的类是可以添加实例变量,调用 class_addIvar
函数。但是得在调用 objc_allocateClassPair
之后,objc_registerClassPair
之前,原因同上。
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#28-runloop和线程有什么关系)28. runloop和线程有什么关系?
总的说来,Run loop,正如其名,loop表示某种循环,和run放在一起就表示一直在运行着的循环。实际上,run loop和线程是紧密相连的,可以这样说run loop是为了线程而生,没有线程,它就没有存在的必要。Run loops是线程的基础架构部分, Cocoa 和 CoreFundation 都提供了 run loop 对象方便配置和管理线程的 run loop (以下都以 Cocoa 为例)。每个线程,包括程序的主线程( main thread )都有与之相应的 run loop 对象。
runloop 和线程的关系:
主线程的run loop默认是启动的。
iOS的应用程序里面,程序启动后会有一个如下的main()函数
int main(int argc, char * argv[]) {@autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));}}
重点是UIApplicationMain()函数,这个方法会为main thread设置一个NSRunLoop对象,这就解释了:为什么我们的应用可以在无人操作的时候休息,需要让它干活的时候又能立马响应。
对其它线程来说,run loop默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已确定的任务则不需要。
在任何一个 Cocoa 程序的线程中,都可以通过以下代码来获取到当前线程的 run loop 。
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
参考链接:《Objective-C之run loop详解》。
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#29-runloop的mode作用是什么)29. runloop的mode作用是什么?
model 主要是用来指定事件在运行循环中的优先级的,分为:
NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态
UITrackingRunLoopMode:ScrollView滑动时
UIInitializationRunLoopMode:启动时
NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合
苹果公开提供的 Mode 有两个:
NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
NSRunLoopCommonModes(kCFRunLoopCommonModes)
[
](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#30-以-scheduledtimerwithtimeinterval的方式触发的timer在滑动页面上的列表时timer会暂定回调为什么如何解决)30. 以+ scheduledTimerWithTimeInterval...的方式触发的timer,在滑动页面上的列表时,timer会暂定回调,为什么?如何解决?
RunLoop只能运行在一种mode下,如果要换mode,当前的loop也需要停下重启成新的。利用这个机制,ScrollView滚动过程中NSDefaultRunLoopMode(kCFRunLoopDefaultMode)的mode会切换到UITrackingRunLoopMode来保证ScrollView的流畅滑动:只能在NSDefaultRunLoopMode模式下处理的事件会影响ScrollView的滑动。
如果我们把一个NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环中的时候, ScrollView滚动过程中会因为mode的切换,而导致NSTimer将不再被调度。
同时因为mode还是可定制的,所以:
Timer计时会被scrollView的滑动影响的问题可以通过将timer添加到NSRunLoopCommonModes(kCFRunLoopCommonModes)来解决。代码如下:
// // http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)// https://github.com/ChenYilong//将timer添加到NSDefaultRunLoopMode中[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTick:) userInfo:nil repeats:YES];//然后再添加到NSRunLoopCommonModes里NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerTick:) userInfo:nil repeats:YES];[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
网友评论