復(fù)習(xí)一下 iOS 基礎(chǔ) (2)

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诬滩。

Paste_Image.png

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 流程

Paste_Image.png

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)、觸摸事件狡孔、屏幕刷新等功能的

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末懂诗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子苗膝,更是在濱河造成了極大的恐慌殃恒,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辱揭,死亡現(xiàn)場離奇詭異离唐,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)问窃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進(jìn)店門亥鬓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人域庇,你說我怎么就攤上這事嵌戈「不” “怎么了?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵熟呛,是天一觀的道長宽档。 經(jīng)常有香客問我,道長庵朝,這世上最難降的妖魔是什么吗冤? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮九府,結(jié)果婚禮上椎瘟,老公的妹妹穿的比我還像新娘。我一直安慰自己侄旬,他們只是感情好艘款,可當(dāng)我...
    茶點故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布裆蒸。 她就那樣靜靜地躺著哗戈,像睡著了一般荒椭。 火紅的嫁衣襯著肌膚如雪声旺。 梳的紋絲不亂的頭發(fā)上笔链,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天,我揣著相機(jī)與錄音腮猖,去河邊找鬼鉴扫。 笑死,一個胖子當(dāng)著我的面吹牛澈缺,可吹牛的內(nèi)容都是我干的坪创。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼姐赡,長吁一口氣:“原來是場噩夢啊……” “哼莱预!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起项滑,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤依沮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后枪狂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體危喉,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年州疾,在試婚紗的時候發(fā)現(xiàn)自己被綠了辜限。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡严蓖,死狀恐怖薄嫡,靈堂內(nèi)的尸體忽然破棺而出氧急,到底是詐尸還是另有隱情,我是刑警寧澤岂座,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布态蒂,位于F島的核電站,受9級特大地震影響费什,放射性物質(zhì)發(fā)生泄漏钾恢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一鸳址、第九天 我趴在偏房一處隱蔽的房頂上張望瘩蚪。 院中可真熱鬧,春花似錦稿黍、人聲如沸疹瘦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽言沐。三九已至,卻和暖如春酣栈,著一層夾襖步出監(jiān)牢的瞬間险胰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工矿筝, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留起便,地道東北人。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓窖维,卻偏偏與公主長得像榆综,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子铸史,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,446評論 2 359

推薦閱讀更多精彩內(nèi)容

  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,160評論 30 470
  • 1. 父類實現(xiàn)深拷貝時鼻疮,子類如何實現(xiàn)深度拷貝。父類沒有實現(xiàn)深拷貝時琳轿,子類如何實現(xiàn)深度拷貝陋守。 1.1 深拷貝同淺拷貝...
    iYeso閱讀 1,895評論 0 13
  • 課業(yè)繁重或者工作繁忙時水评,你是否也渴望過一個完整的休息時間,能約上閨蜜去看一部電影媚送、吃一頓甜品和大餐中燥,收拾一下凌亂的...
    莫主編閱讀 953評論 0 0
  • 前天,聽米蘭跟我聊天塘偎,她說她男朋友要跟她訂婚疗涉,我說拿霉,恭喜你,終于要訂婚了咱扣。但是米蘭說绽淘,她還沒有想清楚要不要訂。我問...
    夏洛特沒煩惱閱讀 329評論 0 2
  • 懷柔的山里闹伪,坐在水邊沪铭,喝啤酒,吃烤魚偏瓤,天微涼杀怠,把兒子送到北體為期一周的夏令營,開始享受久違的二人世界厅克。 對懷柔山里...
    misang閱讀 177評論 0 5