1、KVC与KVO
1.1 描述
1)KVC是key-value coding,通过key-value形式直接访问或修改对象属性,这中运行时动态特性可以方便地实现诸多功能。
2)KVO是key-value observing,这是一种观察者模式,通过监听key,来获得value的变化,用来在对象之间监听状态变化。
1.2 原理
1)KVC 当调用当调用setValue:value forKey:@”key“
的代码时,执行如下操作
2)KVO 是通过 isa-swizzling 实现的。
- 当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的 setter 方法。
- 派生类在被重写的 setter 方法中实现真正的通知机制,就如前面手动实现键值观察那样。这么做是基于设置属性会调用 setter 方法,而通过重写就获得了 KVO 需要的通知机制。当然前提是要通过遵循 KVO 的属性设置方式来变更属性值,如果仅是直接修改属性对应的成员变量,是无法实现 KVO 的。
- 同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa 指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。
1.3 接口
NSKeyValueCoding.h
- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
NSKeyValueObserving.h
//Registration
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
//Observing
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
//Notification
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
//remvoe
...
//Customization
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key // 属性依赖
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key; // 是否自动添加通知代码
1.4 线程?同步
1.5 资料
1.6 问题
如何手动触发一个value的KVO?
KVC的keyPath中的集合运算符如何使用?
KVC和KVO的keyPath一定是属性么?
如何关闭默认的KVO的默认实现,并进入自定义的KVO实现?
apple用什么方式实现对一个对象的KVO?
2、Runloop
2.1 Runloop概述
线程创建后任务执行完成,线程就会销毁。为了构造常驻线程处理事件,引入了EventLoop机制。iOS/OSX系统内的这种机制被称为Runloop。Runloop是一个对象,该对象用来管理需要处理的事件和消息其操作流程为:
runloop:
do {
get message
handle message
wait
} while (message != quit)
2.2 Runloop与线程关系
- 线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。
- 线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。两个自动获取的函数:
CFRunLoopGetMain()
和CFRunLoopGetCurrent()
。 - RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。
2.3 RunloopMode
1)一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
image.png image.png
2)Source/Timer/Observer统称为mode item, 一个 item 可以被同时加入多个 mode,如果mode中没有item直接退出Runloop,不进入循环 。
image.png
- CFRunLoopSourceRef 是事件产生的地方,包括source0和source1。source0是非内核消息需要手动唤醒Runloop,source1是内核消息自动唤醒Runloop。
- CFRunLoopTimerRef 注册时间,时间到时触发Runloop
- CFRunLoopObserverRef 会接收到Runloop的状态变化包括:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 即将退出Loop
};
3)一个 Mode 可以将自己标记为”Common”属性(CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
)。苹果公开提供的 Mode 有两个:kCFRunLoopDefaultMode (NSDefaultRunLoopMode)
和 UITrackingRunLoopMode
,都已经标记为common属性
2.4 Runloop内部逻辑
简单来讲,根据observer的状态分别处理Timer,source0,source1消息,然后进入休眠等等这些消息继续唤醒。
2.5 Runloop底层实现
2.6 Runloop应用
- AutoreleasePool ,主线程会注册两个observer,监控_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 的发生时机
- 后台常驻线程
- 定时器
- 卡顿监测
2.7 问题
runloop和线程有什么关系?
runloop的mode作用是什么?
猜想runloop内部是如何实现的?(通过循环实现循环体大概内容,点到即可)
2.8 参考
深入理解RunLoop
深入浅出 RunLoop
runloop应用系列
Threading Programming Guide - Run Loops
3.并发编程的挑战和应对之策
3.1 挑战
多线程导致Data Race,加锁导致Dead Lock, Starvation,Priority Inversion
- Data Race
- Dead Lock
- Starvation
- Priority Inversion (所以通常不要使用不同的优先级,gcd中使用默认优先级)
3.2 Semaphore(信号量)
类比20人去只有10个位的餐厅吃饭
- count = 10,10个座位。
- queue,餐厅位置有限,为了避免混乱,餐厅肯定会吃货们排队。
- wait(),吃货到了餐厅找服务员要位置点餐,这个行为就是wait。
- signal(),吃货吃完了买单离开位置,这个行为就是signal。
3.2 Mutex(互斥量)
3.3 Lock(锁)
对mutex的封装
3.3 Condition(条件变量)
网友评论