runloop
runloop
對(duì)于一個(gè)標(biāo)準(zhǔn)的iOS開(kāi)發(fā)來(lái)說(shuō)都不陌生辙谜,應(yīng)該說(shuō)熟悉runloop是標(biāo)配,下面就隨便列幾個(gè)典型問(wèn)題吧
1. app如何接收到觸摸事件的
- 首先跷睦,手機(jī)中處理
觸摸事件
的是硬件系統(tǒng)進(jìn)程 筷弦,當(dāng)硬件系統(tǒng)進(jìn)程識(shí)別到觸摸事件后肋演,會(huì)將這個(gè)事件進(jìn)行封裝
抑诸,并通過(guò)machPort
,將封裝的事件發(fā)送給當(dāng)前活躍的APP進(jìn)程。 - 由于APP的主線程中runloop注冊(cè)了這個(gè)machPort端口爹殊,就是用于接收處理這個(gè)事件的蜕乡,所以這里APP收到這個(gè)消息后,開(kāi)始尋找
響應(yīng)鏈
梗夸。 - 尋找到響應(yīng)鏈后层玲,開(kāi)始
分發(fā)事件
,它會(huì)優(yōu)先發(fā)送給手勢(shì)集合
反症,來(lái)過(guò)濾這個(gè)事件辛块,一旦手勢(shì)集合中其中一個(gè)手勢(shì)識(shí)別了這個(gè)事件,那么這個(gè)事件將不會(huì)發(fā)送給響應(yīng)鏈對(duì)象铅碍。 - 手勢(shì)沒(méi)有識(shí)別到這個(gè)事件润绵,事件將會(huì)發(fā)送給響應(yīng)鏈對(duì)象
UIResponser
。
UIResponder
每個(gè)響應(yīng)者都是一個(gè)UIResponder對(duì)象胞谈,即所有派生自UIResponder的對(duì)象尘盼,本身都具備響應(yīng)事件的能力。因此以下類的實(shí)例都是響應(yīng)者:
- UIView
- UIViewController
- UIApplication
- AppDelegate
響應(yīng)者之所以能響應(yīng)事件烦绳,因?yàn)槠涮峁┝?個(gè)處理觸摸事件的方法:
//手指觸碰屏幕卿捎,觸摸開(kāi)始
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//手指在屏幕上移動(dòng)
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//手指離開(kāi)屏幕,觸摸結(jié)束
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//觸摸結(jié)束前径密,某個(gè)系統(tǒng)事件中斷了觸摸午阵,例如電話呼入
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
事件的三徒弟UIResponder、UIGestureRecognizer享扔、UIControl
手勢(shì)識(shí)別器比UIResponder具有更高的事件響應(yīng)優(yōu)先級(jí)5坠稹括细!
UIControl
是系統(tǒng)提供的能夠以target-action
模式處理觸摸事件
的控件,iOS中UIButton
戚啥、UISegmentedControl
奋单、UISwitch
等控件都是UIControl的子類。當(dāng)UIControl跟蹤到觸摸事件時(shí)猫十,會(huì)向其上添加的target發(fā)送事件以執(zhí)行action览濒。值得注意的是,UIConotrol是UIView的子類拖云,因此本身也具備UIResponder應(yīng)有的身份贷笛。
關(guān)于UIControl
,此處介紹兩點(diǎn):
- target-action執(zhí)行時(shí)機(jī)及過(guò)程
- 觸摸事件優(yōu)先級(jí)
UIControl
比其父視圖
上的手勢(shì)識(shí)別器具有更高的事件響應(yīng)優(yōu)先級(jí)
同一控件
上宙项,手勢(shì)識(shí)別器比UIControl
高
2. 為什么只有主線程的runloop是開(kāi)啟的
app啟動(dòng)前會(huì)調(diào)用main函數(shù)乏苦,具體如下:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
mian函數(shù)中調(diào)用UIApplicationMain
,這里會(huì)創(chuàng)建一個(gè)主線程尤筐,用于UI處理
汇荐,為了讓程序可以一直運(yùn)行
,所以在主線程中開(kāi)啟一個(gè)runloop盆繁,讓主線程常駐掀淘。
3. 為什么只在主線程刷新UI
原因一:安全+效率
UIKit不是線程安全的,UI操作涉及到渲染訪問(wèn)各種View對(duì)象的屬性油昂,如果異步操作
下會(huì)存在讀寫
問(wèn)題革娄,而為其加鎖
則會(huì)耗費(fèi)大量資源
并拖慢運(yùn)行速度。
另一方面因?yàn)檎麄€(gè)程序的起點(diǎn)UIApplication
是在主線程
進(jìn)行初始化冕碟,所有的用戶事件都是在主線程上進(jìn)行傳遞(如點(diǎn)擊拦惋、拖動(dòng)),所以view
只能在主線程
上才能對(duì)事件進(jìn)行響應(yīng)
安寺。而在渲染方面由于圖像的渲染需要以60幀的刷新率在屏幕上 同時(shí)更新厕妖,在非主線程異步化的情況下無(wú)法確定這個(gè)處理過(guò)程能夠?qū)崿F(xiàn)同步更新。
4. PerformSelector和runloop的關(guān)系
一 基礎(chǔ)用法
performSelecor
響應(yīng)了OC語(yǔ)言的動(dòng)態(tài)性
:延遲到運(yùn)行時(shí)才綁定方法我衬。當(dāng)我們?cè)谑褂靡韵路椒〞r(shí):
[obj performSelector:@selector(xxx)];
[obj performSelector:@selector(xxx:) withObject:@"xxx"];
[obj performSelector:@selector(xxx: with:) withObject:@"xxx" withObject:@"xxx"];
編譯階段并不會(huì)去檢查方法是否有效存在叹放,只會(huì)給出警告。所以在實(shí)際開(kāi)發(fā)中挠羔,為了避免運(yùn)行時(shí)突然報(bào)錯(cuò)找不到方法等問(wèn)題井仰,少使用performSelector方法。
二 延遲執(zhí)行
[obj performSelector:@selector(xxx:) withObject:@"xxx" afterDelay:4.f];
NSTimer
:當(dāng)一個(gè)NSTimer注冊(cè)到Runloop后破加,Runloop會(huì)重復(fù)的在相應(yīng)的時(shí)間點(diǎn)注冊(cè)事件俱恶,當(dāng)然Runloop為了節(jié)省資源并不會(huì)在準(zhǔn)確
的時(shí)間點(diǎn)觸發(fā)事件。
而performSelector:withObject:afterDelay:
其實(shí)就是在內(nèi)部創(chuàng)建了一個(gè)NSTimer
,然后會(huì)添加到當(dāng)前線程的Runloop
中合是。所以當(dāng)該方法添加到子線程中時(shí)了罪,需要格外的注意兩個(gè)地方:
① 在子線程中執(zhí)行會(huì)不會(huì)調(diào)用test方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[self performSelector:@selector(test) withObject:nil afterDelay:2];
});
會(huì)發(fā)現(xiàn)test方法并沒(méi)有被調(diào)用,因?yàn)樽泳€程中的runloop默認(rèn)是沒(méi)有啟動(dòng)的狀態(tài)聪全。使用run方法開(kāi)啟當(dāng)前線程的runloop泊藕,但是一定要注意run方法和執(zhí)行該延遲方法的順序。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[[NSRunLoop currentRunLoop] run];
[self performSelector:@selector(test) withObject:nil afterDelay:2];
});
會(huì)發(fā)現(xiàn)即便添加了run方法难礼,但是test方法還是沒(méi)有被調(diào)用娃圆,在最后打印當(dāng)前線程的runloop,會(huì)發(fā)現(xiàn):
timers = <CFArray 0x6000002a8100 [0x109f67bb0]>{type = mutable-small, count = 1, values = (
0 : <CFRunLoopTimer 0x6000001711c0 [0x109f67bb0]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 544280547 (1.98647892 @ 3795501066754), callout = (Delayed Perform) lZLearningFromInterviewController test (0x105ea0d9c / 0x104b2e2c0) (), context = <CFRunLoopTimer context 0x600000470080>}
子線程的runloop中確實(shí)添加了一個(gè)CFRunLoopTimer的事件蛾茉,但是到最后都不會(huì)被執(zhí)行讼呢。
將run方法和performSelector延遲方法調(diào)換順序后運(yùn)行:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[self performSelector:@selector(test) withObject:nil afterDelay:2];
[[NSRunLoop currentRunLoop] run];
});
所以在子線程中兩者的順序必須是先執(zhí)行performSelector
延遲方法之后再執(zhí)行run
方法。因?yàn)閞un方法只是嘗試想要開(kāi)啟當(dāng)前線程中的runloop谦炬,但是如果該線程中并沒(méi)有任何事件(source悦屏、timer里覆、observer)的話耍鬓,并不會(huì)成功的開(kāi)啟.
② test方法中執(zhí)行的線程
[self performSelector:@selector(test) withObject:nil afterDelay:2];
如果在子線程中調(diào)用該performSelector延遲方法,會(huì)發(fā)現(xiàn)調(diào)用該延遲方法的子線程和test方法中執(zhí)行的子線程是同一個(gè)韧涨,也就是說(shuō):
對(duì)于該performSelector延遲方法而言稚机,如果在主線程中調(diào)用幕帆,那么test方法也是在主線程中執(zhí)行获搏;如果是在子線程中調(diào)用赖条,那么test也會(huì)在該子線程中執(zhí)行。
在回答完延遲方法之后常熙,會(huì)將該方法和performSelector:withObject:
作對(duì)比纬乍,那么performSelector:withObject:在不添加到子線程的Runloop中時(shí)是否能執(zhí)行?
performSelector:withObject:只是一個(gè)單純的消息發(fā)送裸卫,和時(shí)間沒(méi)有一點(diǎn)關(guān)系仿贬。所以不需要添加到子線程的Runloop中也能執(zhí)行。
三 異步執(zhí)行
如何在不使用GCD和NSOperation的情況下墓贿,實(shí)現(xiàn)異步線程茧泪?
- NSThread
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
[NSThread detachNewThreadSelector:@selector(test) toTarget:self withObject:nil];
[NSThread detachNewThreadWithBlock:^{
NSLog(@"block中的線程 ---- %@",[NSThread currentThread]);
}];
- performSelectorInBackground
[self performSelectorInBackground:@selector(test) withObject:nil];
該方法一目了然,開(kāi)啟新的線程在后臺(tái)執(zhí)行test方法
- performSelector:onThread:在指定線程執(zhí)行
waitUntilDone 當(dāng)這個(gè)參數(shù)為YES,時(shí)表示當(dāng)前runloop循環(huán)中的時(shí)間馬上響應(yīng)這個(gè)事件聋袋,
如果為NO則runloop會(huì)將這個(gè)事件加入runloop隊(duì)列在合適的時(shí)間執(zhí)行這個(gè)事件
[self performSelector:@selector(test)
onThread:[NSThread currentThread]
withObject:nil
waitUntilDone:YES];
要注意队伟,在執(zhí)行performSelector:onThread:withObject:waitUntilDone
方法時(shí)候,如果是在另外一個(gè)線程執(zhí)行幽勒,必須保證另外的線程
是有一個(gè)runloop
.具體的使用可以參考AFNetworking.
四 多參傳遞
performSelector如何進(jìn)行多值傳輸嗜侮?
- NSArray或者NSDictionary或者自定義Model的形式
//封裝參數(shù)
NSDictionary *dic = @{@"param1":@"this is a string",@"param2":@[@2,@3,@3],@"param3":@123};
//調(diào)用方法
[self performSelector:@selector(testFunctionWithParams:) withObject:dic];
- (void)testFunctionWithParams:(NSDictionary *)paramDic {
NSLog(@"%s dic:%@",__FUNCTION__, paramDic);
}
- objc_msgSend:
{
NSNumber *age = [NSNumber numberWithInt:20];
NSString *name = @"李周";
NSString *gender = @"女";
NSArray *friends = @[@"謝華華",@"亞呼呼"];
SEL selector = NSSelectorFromString(@"getAge:name:gender:friends:");
NSArray *array = @[age,name,gender,friends];
((void(*)(id,SEL,NSNumber*,NSString*,NSString*,NSArray*)) objc_msgSend)(self,selector,age,name,gender,friends);
}
- (void)getAge:(NSNumber *)age name:(NSString *)name gender:(NSString *)gender friends:(NSArray *)friends
{
NSLog(@"%d----%@---%@---%@",[age intValue],name,gender,friends[0]);
}
- 用NSInvocation傳遞
//可以傳多個(gè)參數(shù)的方法
- (id)performSelector:(SEL)selector withObjects:(NSArray *)objects
{
// 方法簽名(方法的描述)
NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:selector];
if (signature == nil) {
//可以拋出異常也可以不操作。
}
// NSInvocation : 利用一個(gè)NSInvocation對(duì)象包裝一次方法調(diào)用(方法調(diào)用者、方法名锈颗、方法參數(shù)顷霹、方法返回值)
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = self;
invocation.selector = selector;
// 設(shè)置參數(shù)
NSInteger paramsCount = signature.numberOfArguments - 2; // 除self、_cmd以外的參數(shù)個(gè)數(shù)
paramsCount = MIN(paramsCount, objects.count);
for (NSInteger i = 0; i < paramsCount; i++) {
id object = objects[i];
if ([object isKindOfClass:[NSNull class]]) continue;
[invocation setArgument:&object atIndex:i + 2];
}
// 調(diào)用方法
[invocation invoke];
// 獲取返回值
id returnValue = nil;
if (signature.methodReturnLength) { // 有返回值類型击吱,才去獲得返回值
[invocation getReturnValue:&returnValue];
}
return returnValue;
}
//調(diào)用方法
NSArray *paramArray = @[@"112",@[@2,@13],@12];
[self performSelector:@selector(textFunctionWithParam:param2:param3:) withObjects:paramArray];
//要調(diào)用的方法
-(void)textFunctionWithParam:(NSString *)param1 param2:(NSArray *)param2 param3:(NSInteger)param3 {
NSLog(@"param1:%@, param2:%@, param3:%ld",param1, param2, param3);
}
5. 如何使線程绷艿恚活
- 在NSThread執(zhí)行的方法中添加while(true){},這樣是模擬runloop的運(yùn)行原理覆醇,結(jié)合GCD的信號(hào)量绅喉,在{}中處理任務(wù)。參考這篇文章
- 采用runloop的方式叫乌。參考這篇文章
KVO
1. 實(shí)現(xiàn)原理
KVO
的實(shí)現(xiàn)也依賴于 Objective-C 強(qiáng)大的 Runtime
簡(jiǎn)單概述下 KVO 的實(shí)現(xiàn):
當(dāng)你觀察一個(gè)對(duì)象時(shí)柴罐,一個(gè)新的類NSKVONotifying_XXX
會(huì)動(dòng)態(tài)被創(chuàng)建。這個(gè)類繼承
自該對(duì)象的原本的類憨奸,并重寫了被觀察屬性的setter
方法革屠。自然,重寫的 setter 方法會(huì)負(fù)責(zé)在調(diào)用
原 setter 方法之前
和之后
排宰,通知所有觀察對(duì)象值的更改似芝。最后把這個(gè)對(duì)象的isa
指針 ( isa 指針告訴 Runtime 系統(tǒng)這個(gè)對(duì)象的類是什么 ) 指向這個(gè)新創(chuàng)建的子類
,對(duì)象就神奇的變成了新創(chuàng)建的子類的實(shí)例
setter
方法前后會(huì)調(diào)用:
- willChangeValueForKey
- didChangeValueForKey
2. 如何手動(dòng)關(guān)閉kvo
- 重寫被觀察對(duì)象的
automaticallyNotifiesObserversForKey
方法板甘,返回NO - 重寫
automaticallyNotifiesObserversOf<key>
党瓮,返回NO
注意:關(guān)閉kvo后,需要手動(dòng)在賦值前后添加willChangeValueForKey
和didChangeValueForKey
盐类,才可以收到觀察通知寞奸。
參考這篇文章
3. 通過(guò)KVC修改屬性會(huì)觸發(fā)KVO么
會(huì)
4. 哪些情況下使用kvo會(huì)崩潰,怎么防護(hù)崩潰
- 多次重復(fù)移除同一個(gè)屬性在跳,移除了未注冊(cè)的觀察者
解決辦法:根據(jù)實(shí)際情況枪萄,增加一個(gè)添加keyPath的標(biāo)記,在dealloc中根據(jù)這個(gè)標(biāo)記猫妙,刪除觀察者瓷翻。 - dealloc 沒(méi)有移除 kvo 觀察者
解決方案:創(chuàng)建一個(gè)中間對(duì)象,將其作為某個(gè)屬性的觀察者割坠,然后dealloc的時(shí)候去做移除觀察者齐帚,而調(diào)用者是持有中間對(duì)象的,調(diào)用者釋放了彼哼,中間對(duì)象也釋放了对妄,dealloc 也就移除觀察者了; - 添加了觀察者沪羔,但未實(shí)現(xiàn) observeValueForKeyPath:ofObject:change:context:方法饥伊,導(dǎo)致崩潰象浑;
- 添加或者移除時(shí) keypath == nil,導(dǎo)致崩潰琅豆;
以下解決方案出自 iOS 開(kāi)發(fā):『Crash 防護(hù)系統(tǒng)』(二)KVO 防護(hù) 一文愉豺。
其實(shí)還可以將觀察者observer委托給另一個(gè)類去完成,這個(gè)類弱引用被觀察者茫因,當(dāng)這個(gè)類銷毀的時(shí)候蚪拦,移除觀察者對(duì)象,參考KVOController冻押。
5. kvo的優(yōu)缺點(diǎn)
缺點(diǎn)補(bǔ)充:
- 只能通過(guò)重寫
-observeValueForKeyPath:ofObject:change:context:
方法來(lái)獲得通知驰贷。 - 不同通過(guò)指定
selector
的方式獲取通知。 - 不能通過(guò)
block
的方式獲取通知洛巢。
參考這篇文章
6. 如何去實(shí)現(xiàn)帶Block的KVO括袒?
首先,我們創(chuàng)建 NSObject 的 Category稿茉,并在頭文件中添加兩個(gè) API:
typedef void(^PGObservingBlock)(id observedObject, NSString *observedKey, id oldValue, id newValue);
@interface NSObject (KVO)
- (void)PG_addObserver:(NSObject *)observer
forKey:(NSString *)key
withBlock:(PGObservingBlock)block;
- (void)PG_removeObserver:(NSObject *)observer forKey:(NSString *)key;
@end
接下來(lái)锹锰,實(shí)現(xiàn) ·PG_addObserver:forKey:withBlock: ·方法。邏輯并不復(fù)雜:
- 檢查對(duì)象的類有沒(méi)有相應(yīng)的 setter 方法漓库。如果沒(méi)有拋出異常恃慧;
- 檢查對(duì)象 isa 指向的類是不是一個(gè) KVO 類。如果不是渺蒿,新建一個(gè)繼承原來(lái)類的子類痢士,并把 isa 指向這個(gè)新建的子類;
- 檢查對(duì)象的 KVO 類重寫過(guò)沒(méi)有這個(gè) setter 方法茂装。如果沒(méi)有怠蹂,添加重寫的 setter 方法;
- 添加這個(gè)觀察者
- (void)PG_addObserver:(NSObject *)observer
forKey:(NSString *)key
withBlock:(PGObservingBlock)block
{
// Step 1: Throw exception if its class or superclasses doesn't implement the setter
SEL setterSelector = NSSelectorFromString(setterForGetter(key));
Method setterMethod = class_getInstanceMethod([self class], setterSelector);
if (!setterMethod) {
// throw invalid argument exception
}
Class clazz = object_getClass(self);
NSString *clazzName = NSStringFromClass(clazz);
// Step 2: Make KVO class if this is first time adding observer and
// its class is not an KVO class yet
if (![clazzName hasPrefix:kPGKVOClassPrefix]) {
clazz = [self makeKvoClassWithOriginalClassName:clazzName];
object_setClass(self, clazz);
}
// Step 3: Add our kvo setter method if its class (not superclasses)
// hasn't implemented the setter
if (![self hasSelector:setterSelector]) {
const char *types = method_getTypeEncoding(setterMethod);
class_addMethod(clazz, setterSelector, (IMP)kvo_setter, types);
}
// Step 4: Add this observation info to saved observation objects
PGObservationInfo *info = [[PGObservationInfo alloc] initWithObserver:observer Key:key block:block];
NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers));
if (!observers) {
observers = [NSMutableArray array];
objc_setAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[observers addObject:info];
}
第一步里训唱,先通過(guò) setterForGetter()
方法獲得相應(yīng)的 setter 的名字(SEL)褥蚯。也就是把 key 的首字母大寫,然后前面加上 set 后面加上 :况增,這樣 key 就變成了 setKey:。然后再用 class_getInstanceMethod 去獲得 setKey: 的實(shí)現(xiàn)(Method)训挡。如果沒(méi)有澳骤,自然要拋出異常。
第二步澜薄,我們先看類名有沒(méi)有我們定義的前綴为肮。如果沒(méi)有,我們就去創(chuàng)建新的子類肤京,并通過(guò) object_setClass() 修改 isa 指針颊艳。
- (Class)makeKvoClassWithOriginalClassName:(NSString *)originalClazzName
{
NSString *kvoClazzName = [kPGKVOClassPrefix stringByAppendingString:originalClazzName];
Class clazz = NSClassFromString(kvoClazzName);
if (clazz) {
return clazz;
}
// class doesn't exist yet, make it
Class originalClazz = object_getClass(self);
Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String, 0);
// grab class method's signature so we can borrow it
Method clazzMethod = class_getInstanceMethod(originalClazz, @selector(class));
const char *types = method_getTypeEncoding(clazzMethod);
class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);
objc_registerClassPair(kvoClazz);
return kvoClazz;
}
動(dòng)態(tài)創(chuàng)建新的類需要用 objc/runtime.h 中定義的 objc_allocateClassPair() 函數(shù)茅特。傳一個(gè)父類,類名棋枕,然后額外的空間(通常為 0)白修,它返回給你一個(gè)類。然后就給這個(gè)類添加方法重斑,也可以添加變量兵睛。這里,我們只重寫了 class 方法窥浪。哈哈祖很,跟 Apple 一樣,這時(shí)候我們也企圖隱藏這個(gè)子類的存在漾脂。最后 objc_registerClassPair() 告訴 Runtime 這個(gè)類的存在假颇。
第三步,重寫 setter 方法骨稿。新的 setter 在調(diào)用原 setter 方法后拆融,通知每個(gè)觀察者(調(diào)用之前傳入的 block )
static void kvo_setter(id self, SEL _cmd, id newValue)
{
NSString *setterName = NSStringFromSelector(_cmd);
NSString *getterName = getterForSetter(setterName);
if (!getterName) {
// throw invalid argument exception
}
id oldValue = [self valueForKey:getterName];
struct objc_super superclazz = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
// cast our pointer so the compiler won't complain
void (*objc_msgSendSuperCasted)(void *, SEL, id) = (void *)objc_msgSendSuper;
// call super's setter, which is original class's setter method
objc_msgSendSuperCasted(&superclazz, _cmd, newValue);
// look up observers and call the blocks
NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers));
for (PGObservationInfo *each in observers) {
if ([each.key isEqualToString:getterName]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
each.block(self, getterName, oldValue, newValue);
});
}
}
}
細(xì)心的同學(xué)會(huì)發(fā)現(xiàn)我們對(duì) objc_msgSendSuper 進(jìn)行類型轉(zhuǎn)換。在 Xcode 6 里啊终,新的 LLVM 會(huì)對(duì) objc_msgSendSuper 以及 objc_msgSend 做嚴(yán)格的類型檢查镜豹,如果不做類型轉(zhuǎn)換。Xcode 會(huì)抱怨有 too many arguments 的錯(cuò)誤蓝牲。(在 WWDC 2014 的視頻 What new in LLVM 中有提到過(guò)這個(gè)問(wèn)題趟脂。)
最后一步,把這個(gè)觀察的相關(guān)信息存在 associatedObject
里例衍。觀察的相關(guān)信息(觀察者昔期,被觀察的 key, 和傳入的 block )
封裝在 PGObservationInfo 類里。
@interface PGObservationInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *key;
@property (nonatomic, copy) PGObservingBlock block;
@end
就此佛玄,一個(gè)基本的 KVO 就可以 work 了硼一。當(dāng)然,這只是一個(gè)一天多做出來(lái)的小東西梦抢,會(huì)有 bug,也有很多可以優(yōu)化完善的地方奥吩。
完整的例子可以從這里下載:ImplementKVO
7. KVOController的原理哼蛆?
KVO 存在的問(wèn)題
KVO 本身寫起來(lái)并不友好,存在一些問(wèn)題:
- 需要手動(dòng)移除觀察者
- 處理
觀察事件
需要和注冊(cè)觀察事件
割裂開(kāi)
那么如何解決呢霞赫?
沒(méi)有什么是一個(gè)中間變量
不能解決的腮介。可以創(chuàng)建一個(gè)實(shí)例端衰,觀察的事件由它分發(fā)叠洗,在其 dealloc
方法中移除觀察者
甘改。這樣就不用在外部業(yè)務(wù)方法中移除了。KVOController
也是這么做的灭抑。
總結(jié)
整個(gè)庫(kù)的流程是:
KVOController
把觀察的對(duì)象
作為其NSMapTable
屬性 _objectInfomap 的鍵
十艾,把整個(gè)回調(diào)環(huán)境
組成的對(duì)象 KVOInfo 作為值
保存起來(lái)。同時(shí)通過(guò)一個(gè)單例的KVOSharedController
執(zhí)行具體的注冊(cè)與監(jiān)聽(tīng)
方法名挥。KVO 自動(dòng)取消監(jiān)聽(tīng)的核心在于讓
KVOController
這類的中間類的生命周期和被監(jiān)聽(tīng)的object 同步
疟羹,而不是和 Observer 同步。因?yàn)橘骶螅挥性诒槐O(jiān)聽(tīng)對(duì)象回收的時(shí)候取消監(jiān)聽(tīng)才能真正避免 crash
的危險(xiǎn)榄融。
NSMapTable
NSMapTable 相比較 NSDictionary 的優(yōu)勢(shì)有:
- NSDictionary 必須是
key-obj
的形式,key 必須是滿足 NSCopying 協(xié)議的救湖;NSMapTable 則是obj-obj
的形式 - NSDictionary 的 obj 是
強(qiáng)引用
愧杯;NSMapTable 的 key 和 value 都可以自己決定
是強(qiáng)引用還是弱引用。如果弱引用
回收后鞋既,會(huì)自動(dòng)刪除
力九。
[[NSMapTable alloc] initWithKeyOptions:keyOptions
valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality
capacity:0];
NSMapTable 的選項(xiàng)
NSMapTableStrongMemory (a “memory option”)
NSMapTableWeakMemory (a “memory option”)
NSMapTableObjectPointerPersonality (a “personality option”)
NSMapTableCopyIn (a “copy option”)