KVO/KVC
KVO 的實現(xiàn)依賴于 Objective-C 強(qiáng)大的 Runtime
當(dāng)觀察某對象A時购披,KVO機(jī)制動態(tài)創(chuàng)建一個對象A當(dāng)前類的子類伟阔,并為這個新的子類重寫了被觀察屬性keyPath的setter 方法洼滚。setter 方法隨后負(fù)責(zé)通知觀察對象屬性的改變狀況。
// 子類實現(xiàn)
-(void)setName:(NSString *)newName{
[self willChangeValueForKey:@"name"]; //KVO在調(diào)用存取方法之前總調(diào)用
[super setValue:newName forKey:@"name"]; //調(diào)用父類的存取方法
[self didChangeValueForKey:@"name"]; //KVO在調(diào)用存取方法之后總調(diào)用
}
觀察者觀察的是屬性伐蒋,只有遵循 KVO 變更屬性值的方式才會執(zhí)行KVO的回調(diào)方法巢墅,例如是否執(zhí)行了setter方法、或者是否使用了KVC賦值炸宵。
①通過KVO辟躏,能觀察父類的屬性值。
②只要知道了keyPath土全,不管有沒有暴露方法捎琐,依舊可以通過KVO方式觀察值的變化,而且同屬性一樣裹匙,可以被繼承瑞凑。
③子類重寫父類的set方法,也并不會影響KVO的觀察概页。
KVC(鍵值編碼)籽御,即Key-Value Coding,一個非正式的Protocol惰匙,使用字符串(鍵)訪問一個對象實例變量的機(jī)制技掏。而不是通過調(diào)用Setter、Getter方法等顯式的存取方式去訪問项鬼。
KVO(鍵值監(jiān)聽)哑梳,即Key-Value Observing,它提供一種機(jī)制,當(dāng)指定的對象的屬性被修改后,對象就會接受到通知绘盟,前提是執(zhí)行了setter方法鸠真、或者使用了KVC賦值悯仙。
Block
Block 中,Block 表達(dá)式截獲所使用的自動變量的值吠卷,即保存該自動變量的瞬間值锡垄。
修飾為 __block 的變量,在捕獲時祭隔,獲取的不再是瞬間值偎捎。
Block 實現(xiàn)方法 在編譯之后變成了一個靜態(tài)函數(shù), Block 本身變成了一個結(jié)構(gòu)體, 包含 函數(shù)實現(xiàn), 以及數(shù)據(jù)
如果 Block 引用了外面的數(shù)據(jù), 那么就會變成 結(jié)構(gòu)體的參數(shù)保存下來
__block 修飾符其實類似于 C 語言中 static、auto序攘、register 修飾符。用于指定將變量值設(shè)置到哪個存儲域中寻拂。
被 __block 標(biāo)記的 參數(shù), 被封裝成了一個結(jié)構(gòu)體, 記錄了參數(shù)的一些屬性, 通過這些屬性, 就能直接修改內(nèi)容了
Block 有三種類型程奠,分別是:
__NSConcreteStackBlock ————————棧中
__NSConcreteGlobalBlock ————————數(shù)據(jù)區(qū)域中
__NSConcreteMallocBlock ————————堆中
設(shè)置在棧上的 Block,如果所屬的變量作用域結(jié)束祭钉,Block 就會被廢棄瞄沙。如果其中用到了 block,block 所屬的變量作用域結(jié)束也會被廢棄慌核。
為了解決這個問題距境,Block 在必要的時候就需要從棧中移到堆中。ARC 有效時垮卓,很多情況下垫桂,編譯器會幫助完成 Block 的 copy,但很多情況下粟按,我們需要手動 copy Block诬滩。
Runloop
OSX/iOS 系統(tǒng)中,提供了兩個這樣的對象:NSRunLoop 和 CFRunLoopRef灭将。
CFRunLoopRef 是在 CoreFoundation 框架內(nèi)的疼鸟,它提供了純 C 函數(shù)的 API,所有這些 API 都是線程安全的庙曙。
NSRunLoop 是基于 CFRunLoopRef 的封裝空镜,提供了面向?qū)ο蟮?API,但是這些 API 不是線程安全的捌朴。
Runloop 是不能被創(chuàng)建的 只能被獲取 CFRunLoopGetMain()
和 CFRunLoopGetCurrent()
線程和 runloop 是一一對應(yīng)的, 他們的關(guān)系保存在全局字典中, 當(dāng)取不到 runloop 的時候會自動創(chuàng)建
Runloop 種類
CFRunLoopRef
CFRunLoopModeRef // 每個 runloop 包含若干 mode, 每個 mode 又包含若干 Source/Timer/Observer
CFRunLoopSourceRef // Source有兩個版本:Source0 和 Source1, source0 不基于 port, source1 基于 port 能主動喚醒 runloop
CFRunLoopTimerRef // 基于時間的觸發(fā)器, 時間到了喚醒 runloop
CFRunLoopObserverRef // runloop 的觀察者 當(dāng) RunLoop 的狀態(tài)發(fā)生變化時吴攒,觀察者就能通過回調(diào)接受到這個變化
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即將進(jìn)入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), // 即將退出Loop
};
蘋果公開提供的 Mode 有兩個:kCFRunLoopDefaultMode (NSDefaultRunLoopMode) 和 UITrackingRunLoopMode,你可以用這兩個 Mode Name 來操作其對應(yīng)的 Mode
1. kCFRunLoopDefaultMode: App的默認(rèn) Mode男旗,通常主線程是在這個 Mode 下運(yùn)行的舶斧。
2. UITrackingRunLoopMode: 界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動察皇,保證界面滑動時不受其他 Mode 影響茴厉。
3. UIInitializationRunLoopMode: 在剛啟動 App 時第進(jìn)入的第一個 Mode泽台,啟動完成后就不再使用。
4: GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode矾缓,通常用不到怀酷。
5: kCFRunLoopCommonModes: 這是一個占位的 Mode,沒有實際作用嗜闻。
Runloop 流程
Runloop 應(yīng)用
自動釋放池
在 kCFRunLoopEntry 使用 _objc_autoreleasePoolPush 創(chuàng)建, 在kCFRunLoopBeforeWaiting 的時候 _objc_autoreleasePoolPop 釋放, 并且創(chuàng)建新的自動釋放池
事件響應(yīng)
蘋果注冊一個 source1(基于 mach port 的) 用來接收系統(tǒng)事件, 當(dāng)發(fā)生事件的時候, 喚醒 runloop, 蘋果把事件封裝成為 UIEvent 下發(fā)
手勢識別
蘋果注冊了一個 Observer 監(jiān)測 BeforeWaiting (Loop即將進(jìn)入休眠) 事件蜕依,這個Observer的回調(diào)函數(shù)是 _UIGestureRecognizerUpdateObserver(),其內(nèi)部會獲取所有剛被標(biāo)記為待處理的 GestureRecognizer琉雳,并執(zhí)行GestureRecognizer的回調(diào)样眠。
界面更新
蘋果注冊了一個 Observer 監(jiān)聽 BeforeWaiting(即將進(jìn)入休眠) 和 Exit (即將退出Loop) 事件,回調(diào)去執(zhí)行一個很長的函數(shù):
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()翠肘。這個函數(shù)里會遍歷所有待處理的 UIView/CAlayer 以執(zhí)行實際的繪制和調(diào)整檐束,并更新 UI 界面
iOS 為什么必須在主線程中操作UI
因為UIKit不是線程安全的。試想下面這幾種情況:
兩個線程同時設(shè)置同一個背景圖片束倍,那么很有可能因為當(dāng)前圖片被釋放了兩次而導(dǎo)致應(yīng)用崩潰被丧。
兩個線程同時設(shè)置同一個UIView的背景顏色,那么很有可能渲染顯示的是顏色A绪妹,而此時在UIView邏輯樹上的背景顏色屬性為B甥桂。
兩個線程同時操作view的樹形結(jié)構(gòu):在線程A中for循環(huán)遍歷并操作當(dāng)前View的所有subView,然后此時線程B中將某個subView直接刪除邮旷,這就導(dǎo)致了錯亂還可能導(dǎo)致應(yīng)用崩潰黄选。
iOS4之后蘋果將大部分繪圖的方法和諸如 UIColor 和 UIFont 這樣的類改寫為了線程安全可用,但是仍然強(qiáng)烈建議講UI操作保證在主線程中執(zhí)行婶肩。
長時間保持一個后臺線程
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; // runloop 必須存在一個 timer/source/observer 才能 run, 這里添加了一個空的
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
...
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; // 需要異步執(zhí)行的時候可以調(diào)用這個方法
優(yōu)化 tableview 設(shè)置 UI 卡頓
不在用戶拖動的時候設(shè)置 UI, 在特定的 mode 中執(zhí)行代碼
UIImage *downLoadImage = ...;
[self.avatarImageView performSelector:@selector(setImage:)
withObject:downloadImage
afterDelay:0
inModes:@[NSDefaultRunLoopMode]];
監(jiān)聽 runloop 的事件
- (void)setupRunloopObserver
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CFRunLoopRef runloop = CFRunLoopGetCurrent();
CFRunLoopObserverRef enterObserver;
enterObserver = CFRunLoopObserverCreate(CFAllocatorGetDefault(),
kCFRunLoopEntry | kCFRunLoopExit,
true,
-0x7FFFFFFF,
BBRunloopObserverCallBack, NULL);
CFRunLoopAddObserver(runloop, enterObserver, kCFRunLoopCommonModes);
CFRelease(enterObserver);
});
}
static void BBRunloopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
switch (activity) {
case kCFRunLoopEntry: {
NSLog(@"enter runloop...");
}
break;
case kCFRunLoopExit: {
NSLog(@"leave runloop...");
}
break;
default: break;
}
}
RunLoop 涉及了自動釋放池糕簿、延遲回調(diào)、觸摸事件狡孔、屏幕刷新等功能的