2020 最新iOS面试题之Runloop&KVO(附答案)

前言

今天这一篇我们来讲一下 Runloop和KVO

本章的主要回答的问题如下:

Runloop

  • app如何接收到触摸事件的
  • 为什么只有主线程的runloop是开启的
  • 为什么只在主线程刷新UI
  • PerformSelector和runloop的关系
  • 如何使线程?;?/li>

KVO

  • 实现原理
  • 如何手动关闭kvo
  • 通过KVC修改属性会触发KVO么
  • 哪些情况下使用kvo会崩溃,怎么防护崩溃
  • kvo的优缺点

Runloop

作为一个合格的iOS开发者必须对runloop有一个更深入的了解,下面我们来回答一下 相关问题

1.app如何接收到触摸事件的

回答这个问题前请认真阅读一下 iOS触摸事件全家桶

通过上图可以看出整个流程就是 我们app启动默认会通过machPort监听端口的方式 来接受IOHIDEvent 来接收和处理触摸事件.

2.为什么只有主线程的runloop是开启的

mian()函数中调用UIApplicationMain,这里会创建一个主线程,用于UI处理,为了让程序可以一直运行并接收事件,所以在主线程中开启一个runloop,让主线程常驻.

3.为什么只在主线程刷新UI

我们所有用到的UI都是来自于UIKit这个基础库.因为objc不是一门线程安全的语言所以存在多线程读写不同步的问题,如果使用加锁的方式操作系统开销很大,会耗费大量的系统资源(内存、时间片轮转、cpu处理速度...),加上上面讲到的系统事件的接收处理都在主线程,如果UI异步线程的话 还会存在 同步处理事件的问题,所以多点触摸手势等一些事件要保持和UI在同一个线程相对是最优解.

另一方面是 屏幕的渲染是 60帧(60Hz/秒), 也就是1秒钟回调60次的频率,(iPad Pro 是120Hz/秒),我们的runloop 理想状态下也会按照时钟周期 回调60次(iPad Pro 120次), 这么高频率的调用是为了 屏幕图像显示能够垂直同步 不卡顿.在异步线程的话是很难保证这个处理过程的同步更新. 即便能保证的话 相对主线程而言 系统资源开销 线程调度等等将会占据大部分资源和在同一个线程只专门干一件事有点得不偿失.

4.PerformSelector和runloop的关系

当调用NSObect的 performSelector:相关的时候,内部会创建一个timer定时器添加到当前线程的runloop中,如果当前线程没有启动runloop,则该方法不会被调用.

开发中遇到最多的问题就是这个performSelector: 导致对象的延迟释放,这里开发过程中注意一下,可以用单次的NSTimer替代.

详细可以参考Runloop与performSelector

5.如何使线程?;睿?/h3>

想要线程?;畹幕熬涂舾孟叱痰膔unloop即可,注意:在NSThread执行的方法中添加while(true){},这样是模拟runloop的运行原理,结合GCD的信号量,在{}代码块中处理任务.

但是注意 开启runloop的方法要正确

如下代码

//测试开启线程
- (void)memoryTest {
    for (int i = 0; i < 100000; ++i) {
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
        [thread start];
        [self performSelector:@selector(stopThread) onThread:thread withObject:nil waitUntilDone:YES];
    }
}
//线程停止
- (void)stopThread {
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSThread *thread = [NSThread currentThread];
    [thread cancel];
}
//运行线程的runloop 注意 意添加的那个空port,否则会出现内存泄露
- (void)run {
    @autoreleasepool {
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        if (!self.emptyPort) {
            self.emptyPort = [NSMachPort port];
        }
        [runLoop addPort:self.emptyPort forMode:NSDefaultRunLoopMode];
        [runLoop runMode:NSRunLoopCommonModes beforeDate:[NSDate distantFuture]];
    }
}
//下列代码用于模拟线程内部做的一些耗时任务
- (void)printSomething {
    NSLog(@"current thread = %@", [NSThread currentThread]);
    [self performSelector:@selector(printSomething) withObject:nil afterDelay:1];
}
//模拟手动点击按钮 让 runloop停掉
- (void)stopButtonDidClicked:(id)sender {
    [self performSelector:@selector(stopRunloop) onThread:self.thread withObject:nil waitUntilDone:YES];
}

- (void)stopRunloop {
    CFRunLoopStop(CFRunLoopGetCurrent());
}

详细请参考:iOS开发深入研究Runloop与线程?;?/a>

KVO

在开发过程中我们经常使用KVO,下面解答一下KVO相关的问题.

KVO的实现原理

通过runtime派生子类的方式 复写相关需要KVO监听的属性,在该属性setter之前和之后调用NSObject的监听方法,这样KVO就实现了属性变换前后的回调.

KVO派生的子类具体格式应该是:NSKVONotifying_+类名的类 eg: NSKVONotifying_Person

下面示例代码为Person类的name添加KVO的模拟实验

- (void)setName:(NSString *)name{
    _NSSetObjectValueAndNotify();
}

void _NSSetObjectValueAndNotify {
    [self willChangeValueForKey:@"name"];
    [super setName:name];
    [self didChangeValueForKey:@"name"];
}

- (void)didChangeValueForKey:(NSString *)key{
    [observe observeValueForKeyPath:key ofObject:self change:nil context:nil];
}

问题来了如何动态创建类呢?

//动态创建XXCustomClass
Class customClass = objc_allocateClassPair([NSObject class], "XXCustomClass", 0);
// 添加实例变量
class_addIvar(customClass, "age", sizeof(int), 0, "i");
// 动态添加方法
class_addMethod(customClass, @selector(hahahha), (IMP)hahahha, "V@:");

//需要实现的方法
void hahahha(id self, SEL _cmd)
{
    NSLog(@"hahahha====");
}

- (void)hahahha{

}

//最后注册到运行时环境
objc_registerClassPair(customClass);

V@:表示方法的参数和返回值

具体原理以及自定义实现KVO可以参考KVO详解及底层实现

如何手动关闭KVO?

被观察的对象复写如下方法 返回NO即可关闭KVO

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    return NO;
}

如果关闭后还想触发 KVO的话 修改需要手动调用在变量setter的前后 主动调用 willChangeValueForKey:didChangeValueForKey:

通过KVC修改属性会触发KVO么?

会的

哪些情况下使用kvo会崩溃,怎么防护崩溃?

使用不当 会crash,比如:

  • 添加和移出不是成对出现且存在多线程添加KVO的情况,经常遇到的crash是移出 - 内存dealloc的时候 或者对象销毁前没有正确移出Observer

如何防护?

1.注意移出对象 匹配
2.内存野指针问题,一定要在对象销毁前移出观察者 3.可以使用第三方库BlockKit添加KVO,blockkit内部会自动移除Observer避免crash.

KVO的优缺点

优点:

  • 方便两个对象间同步状态(keypath)更加方便,一般都是在A类要观察B类的属性的变化.
  • 非侵入式的得到某内部对象的状态改变并作出响应.(就是在不改变原来对象类的代码情况下即可做出对该对象的状态变化进行监听)
  • 可以嵌入更改前后的两个时机的状态. - 可以通过Keypaths对嵌套对象的监听.

缺点:

  • 需要手动移除观察者,不移除容易造成crash.
  • 注册和移出成对匹配出现.
  • keypath参数的类型String, 如果对象的成员变量被重构而变化字符串不会被编译器识别而报错.
  • 实现观察的方式是复写NSObjec的相关KVO的方法,应该更加面向protocol的方式会更好.

总结

这一篇我们讲了 runloop和KVO相关的内容,这里面最负责的当属runloop如何处理触摸手势事件.建议认真研读相关链接文章.这样才有一个对runloop更深刻的理解。

推荐

收录:原文地址

?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351