什么是KVC,什么是KVO,他們之間關(guān)系.底層實現(xiàn)
鍵值編碼 Key-Value-Coding(KVC)
鍵值編碼是一種使用字符串來標(biāo)識屬性,間接訪問對象的屬性,而不是通過調(diào)用存取方法,直接或通過實例變量訪問的機(jī)制,非對象類型的變量將被自動封裝或者解封成對象,很多情況下會簡化程序代碼;KVC的缺點(diǎn):一旦使用 KVC 你的編譯器無法檢查出錯誤,即不會對設(shè)置的鍵、鍵路徑進(jìn)行錯誤檢查,且執(zhí)行效率要低于合成存取器方法和自定的 setter 和 getter 方法。因為使用 KVC 鍵值編碼,它必須先解析字符串,然后在設(shè)置或者訪問對象的實例變量。
實現(xiàn)分析
KVC運(yùn)用了一個isa-swizzling技術(shù)。isa-swizzling就是類型混合指針機(jī)制。KVC主要通過isa-swizzling,來實現(xiàn)其內(nèi)部查找定位的。isa指針,如其名稱所指,(就是is a kind of的意思),指向維護(hù)分發(fā)表的對象的類。該分發(fā)表實際上包含了指向?qū)崿F(xiàn)類中的方法的指針,和其它數(shù)據(jù)。
比如說如下的一行KVC的代碼:
[site setValue:@"sitename" forKey:@"name"];
就會被編譯器處理成:
SEL sel = sel_get_uid ("setValue:forKey:");
IMP method = objc_msg_lookup (site->isa,sel);
method(site, sel, @"sitename", @“name");
一個對象在調(diào)用setValue的時候,
(1)首先根據(jù)方法名找到運(yùn)行方法的時候所需要的環(huán)境參數(shù)。
(2)他會從自己isa指針結(jié)合環(huán)境參數(shù),找到具體的方法實現(xiàn)的接口。
(3)再直接查找得來的具體的方法實現(xiàn)。
鍵值觀察 Key-Value Observing (KVO)
鍵值觀察機(jī)制是一種能使得對象獲取到其他對象屬性變化的通知 ,極大的簡化了代碼。
實現(xiàn)分析 :
KVO的實現(xiàn)是基于runtime運(yùn)行時的
1、當(dāng)一個object有觀察者時,動態(tài)創(chuàng)建這個object的類的子類在addObserver:forKeyPath:options:context:之后。對象的isa變?yōu)榱俗宇惻缮怤SKVONotifying_XX。實際上是對象p的isa即NSKVONotifying_XX類的setAge方法,并非原類的setAge方法。
2、對于每個被觀察的property,重寫其set方法
3、在重寫的set方法中調(diào)用- willChangeValueForKey:和- didChangeValueForKey:通知觀察者
4、當(dāng)一個property沒有觀察者時,刪除重寫的方法
5、當(dāng)沒有observer觀察任何一個property時,刪除動態(tài)創(chuàng)建的子類
什么是block,delegate,通知中心Notification,使用區(qū)別
block
block被ObjC看成是對象,它封裝了一段代碼,這段代碼可以在任何時候執(zhí)行。Blocks可以作為函數(shù)參數(shù)或者函數(shù)的返回值,而其本身又可以帶輸入?yún)?shù)或返回值。它和傳統(tǒng)的函數(shù)指針很類似,但是有區(qū)別:blocks是inline的,并且它對局部變量是只讀的。block類似一些其它Web編程語言中的“匿名函數(shù)”。在objc中通常使用block實現(xiàn)代理方法實現(xiàn)的功能,也就是回調(diào)。使用代理需要設(shè)置代理的數(shù)據(jù)接收者,而且代理方法是被分離開來處理的,block可以將這些分離的代碼放到一個代碼塊中。
delegate
delegate,又稱委托或者代理,它是一種設(shè)計模式.delegate主要是用于兩個對象之間的交互,并且解除兩個通信對象的耦合性,iOS大量使用代理模式,主要是用于視圖與使用對象之間的通信交互.
官方文檔翻譯解釋:
代理是一種簡單而功能強(qiáng)大的設(shè)計模式,這種模式用于一個對象“代表”另外一個對象和程序中其他的對象進(jìn)行交互。 主對象(這里指的是delegating object)中維護(hù)一個代理(delegate)的引用并且在合適的時候向這個代理發(fā)送消息。這個消息通知“代理”主對象即將處理或是已經(jīng)處理完了某一個事件。這個代理可以通過更新自己或是其它對象的UI界面或是其它狀態(tài)來響應(yīng)主對象所發(fā)送過來的這個事件的消息。或是在某些情況下能返回一個值來影響其它即將發(fā)生的事件該如何來處理。代理的主要價值是它可以讓你容易的定制各種對象的行為。注意這里的代理是個名詞,它本身是一個對象,這個對象是專門代表被代理對象來和程序中其他對象打交道的。委托是objC中使用非常頻繁的一種設(shè)計模式,它的實現(xiàn)與協(xié)議的使用是分不開的.
Notification
通知中心概述:通知中心實際上是在程序內(nèi)部提供了消息廣播的一種機(jī)制。通知中心不能在進(jìn)程間進(jìn)行通信。實際上就是一個二傳手,把接收到的消息,根據(jù)內(nèi)部的一個消息轉(zhuǎn)發(fā)表,來將消息轉(zhuǎn)發(fā)給需要的對象。通知中心是基于觀察者模式的,它允許注冊、刪除觀察者。
一個NSNotificationCenter可以有許多的通知消息NSNotification,對于每一個NSNotification可以有很多的觀察者Observer來接收通知。
使用區(qū)別
delegate與block一般用于兩個對象1對1之間的通信交互、delegate需要定義協(xié)議方法,代理對象需要實現(xiàn)協(xié)議方法并且需要建立代理關(guān)系才可以實現(xiàn)通信。 block更加簡潔,不需要定義繁瑣的協(xié)議方法,但是如果通信時間比較多的話,建議使用delgate。 Notfication主要用于1對多的通信,而且通信對象之間不需要建立關(guān)系,但是使用通知,代碼的可讀性差。
內(nèi)存管理MRC、ARC管理機(jī)制的區(qū)別
iOS中采用的是引用計數(shù)的機(jī)制來管理內(nèi)存,如果一塊內(nèi)存區(qū)域的引用計數(shù)不為0,那么就說明有對象持或者是在使用這一塊內(nèi)存,如果引用計數(shù)為0的話那么說明這塊內(nèi)存沒有對象使用,可以被系統(tǒng)回收掉。iOS借助于引用計數(shù)的增減來輔助我們進(jìn)行內(nèi)存的申請和釋放.
內(nèi)存管理的原則:
1、自己創(chuàng)建的對象,自己可以持有(比如以alloc、new、copy、mutableCopy開頭的方法可以創(chuàng)建對象);
2、不是自己創(chuàng)建的對象也可以持有,通過retain;
3、自己持有的對象在不需要使用的時候要負(fù)責(zé)釋放、釋放可以通過release或者是autorelease進(jìn)行釋放,
4、不是自己持有的對象不能進(jìn)行釋放,比如便利構(gòu)造器得到的對象。
在內(nèi)存管理的過程中我們需要謹(jǐn)記的原則就是我們造成的引用計數(shù)的增加和我們造成的引用計數(shù)的減少要保持一致。在我們使用屬性的過程中,要注意不同的語義控制(assign、retain、copy)的setter方法實現(xiàn)的不同,對于retain和copy來說,他們的內(nèi)部實現(xiàn)都是先把舊值release和把新值retain。另外對于發(fā)送autorelease消息的對象會被加到最近的自動釋放池中,當(dāng)自動釋放池釋放的時候,會給里面的所有對象發(fā)送一次release消息。iOS5.0之后蘋果推出了ARC、ARC是編譯器的特性,不是OC的語言特性,是編譯器在靜態(tài)編譯的基礎(chǔ)上(command + shift + B),編譯器在合適的地方給我們加了retain、release、autorelease這些代碼,不用我們自己去手動寫這些代碼了。ARC中屬性的關(guān)鍵字是strong和weak,其中strong和MRC下的retain作用相同,都是持有一個對象,weak和MRC下的assigin類型,是一個弱引用,不持有一個對象,但是weak只能修飾對象類型,不能修飾基本類型,并且weak會在指向的對象被銷毀的時候指針自動置nil。
#import、#include、@class、#import<>和#import”"的區(qū)別
import
- 是 Objective-C 導(dǎo)入頭文件的關(guān)鍵字,完整地包含某個文件的內(nèi)容
- 會自動導(dǎo)入一次,不會重復(fù)導(dǎo)入
- 不會引發(fā)交叉編譯; 因為在 Objective-C 中會存在C/C++和Objective-C 混編的問題,如果用 #include 引入頭文件,會導(dǎo)致交叉編譯。
- include
- C/C++ 導(dǎo)入頭文件的關(guān)鍵字,完整地包含某個文件的內(nèi)容
- @class 1. 僅僅是聲明一個類名,并不會包含類的完整聲明2.能解決循環(huán)包含的問題:當(dāng)兩個類文件有循環(huán)依賴關(guān)系 ( A 引用 B , B 引用 A ) 時,需要用 @class
import<> 和 import”"
- <> : 引用系統(tǒng)文件,它用于對系統(tǒng)自帶的頭文件的引用,編譯器會在系統(tǒng)文件目錄下去查找該文件.
- "": 用戶自定義的文件用雙引號引用,編譯器首先會在用戶目錄下查找,然后到安裝目錄中查
iOS assign,weak,strong,copy ,atomic, nonatomic詳解
assign 與weak區(qū)別
- assign適用于基本數(shù)據(jù)類型,weak是適用于NSObject對象,并且是一個弱引用。assign其實也可以用來修飾對象。那么我們?yōu)槭裁床挥盟揎棇ο竽??因為被assign修飾的對象(一般編譯的時候會產(chǎn)生警告:Assigning retained object to unsafe property; object will be released after assignment)在釋放之后,指針的地址還是存在的,也就是說指針并沒有被置為nil,造成野指針。對象一般分配在堆上的某塊內(nèi)存,如果在后續(xù)的內(nèi)存分配中,剛好分到了這塊地址,程序就會崩潰掉。
- 那為什么可以用assign修飾基本數(shù)據(jù)類型?因為基礎(chǔ)數(shù)據(jù)類型一般分配在棧上,棧的內(nèi)存會由系統(tǒng)自己自動處理,不會造成野指針。weak修飾的對象在釋放之后,指針地址會被置為nil。所以現(xiàn)在一般弱引用就是用weak。
- weak使用場景:在ARC下,在有可能出現(xiàn)循環(huán)引用的時候,往往要通過讓其中一端使用weak來解決,比如: delegate代理屬性,通常就會聲明為weak。自身已經(jīng)對它進(jìn)行一次強(qiáng)引用,沒有必要再強(qiáng)引用一次時也會使用weak。比如:自定義 IBOutlet控件屬性一般也使用weak,當(dāng)然也可以使用strong。
strong 與copy的區(qū)別
- strong 與copy都會使引用計數(shù)(retain)加1,但strong是兩個指針指向同一個內(nèi)存地址,copy會在內(nèi)存里拷貝一份對象,兩個指針指向不同的內(nèi)存地址
- __block與__weak的區(qū)別
- __block是用來修飾一個變量,這個變量就可以在block中被修改 __block:使用 __block修飾的變量在block代碼塊中會被retain(ARC下會retain,MRC下不會retain)__weak:使用__weak修飾的變量不會在block代碼塊中被retain 同時,在ARC下,要避免block出現(xiàn)循環(huán)引用 __weak typedof(self)weakSelf = self;
- block變量定義時為什么用copy?block是放在哪里的?
- block的循環(huán)引用并不是strong導(dǎo)致的…在ARC環(huán)境下,系統(tǒng)底層也會做一次copy操作使block從棧區(qū)復(fù)制一塊內(nèi)存空間到堆區(qū)…所以strong和copy在對block的修飾上是沒有本質(zhì)區(qū)別的,只不過copy操作效率高而已
nonatomic/atomic
- atomic的意思就是setter/getter這兩個函數(shù)的一個原語操作。如果有多個線程同時調(diào)用setter的話,不會出現(xiàn)某一個線程執(zhí)行setter全部語句之前,另一個線程開始執(zhí)行setter情況,相當(dāng)于函數(shù)頭尾加了鎖一樣。 nonatomic不保證setter/getter的原語行,所以你可能會取到不完整的東西。 比如setter函數(shù)里面改變兩個成員變量,如果你用nonatomic的話,getter可能會取到只更改了其中一個變量時候的狀態(tài)。 atomic是線程安全的,nonatomic是線程不安全的。如果只是單線程操作的話用nonatomic最好,因為后者效率高一些。
OC之面向?qū)ο蟮娜筇卣?/h2>
- 封裝
封裝是對象和類概念的主要特性。它是隱藏內(nèi)部實現(xiàn),穩(wěn)定外部接口,可以看作是“包裝”。封裝,也就是把客觀事物封裝成抽象的類,并且類可以把自己的數(shù)據(jù)和方法只讓可信的類或者對象操作,對不可信的進(jìn)行信息隱藏。
好處:使用更簡單變量更安全可以隱藏內(nèi)部實現(xiàn)細(xì)節(jié)開發(fā)速度加快
OC中一個類可以繼承另一個類,被繼承的類成為超類(superclass),繼承的類是子類(childclass),可以直接擁有父類中所有非私有成員(相關(guān)實例變量)、方法。繼承的實現(xiàn)在接口中使用符號“:”。
舉個例子:@interfaceStudent:NSObject{}不過大家要注意的是:屬性封裝實例變量,方法封裝具體實現(xiàn)代碼,類封裝屬性和方法。子類可繼承父類中的方法,還可重寫父類方法。
- 多態(tài)
多態(tài)性(polymorphism)是允許你將父對象設(shè)置成為和一個或更多的他的子對象相等的技術(shù),賦值之后,父對象就可以根據(jù)當(dāng)前賦值給它的子對象的特性以不同的方式運(yùn)作。簡單的說,就是一句話:允許將子類類型的指針賦值給父類類型的指針。不同對象以自己的方式響應(yīng)相同的消息的能力叫做多態(tài)。意思就是假設(shè)生物類(life)都用有一個相同的方法-eat;那人類屬于生物,豬也屬于生物,都繼承了life后,實現(xiàn)各自的eat,但是調(diào)用是我們只需調(diào)用各自的eat方法。也就是不同的對象以自己的方式響應(yīng)了相同的消息(響應(yīng)了eat這個選擇器)。
實現(xiàn)多態(tài),有二種方式,覆蓋,重載。
1)覆蓋:是指子類重新定義父類的虛函數(shù)的做法。
2)重載:是指允許存在多個同名函數(shù),而這些函數(shù)的參數(shù)表不同(或許參數(shù)個數(shù)不同,或許參數(shù)類型不同,或許兩者都不同)。
- 繼承
面向?qū)ο缶幊?OOP)語言的一個主要功能就是“繼承”。繼承是指這樣一種能力:它可以使用現(xiàn)有類的所有功能,并在無需重新編寫原來的類的情況下對這些功能進(jìn)行擴(kuò)展。
通過繼承創(chuàng)建的新類稱為“子類”或“派生類”,被繼承的類稱為“基類”、“父類”或“超類”。繼承的過程,就是從一般到特殊的過程。在考慮使用繼承時,有一點(diǎn)需要注意,那就是兩個類之間的關(guān)系應(yīng)該是“屬于”關(guān)系。例如,Employee是一個人,Manager也是一個人,因此這兩個類都可以繼承Person類。但是Leg類卻不能繼承Person類,因為腿并不是一個人。
小結(jié):封裝可隱藏實現(xiàn)細(xì)節(jié),,使代碼模塊化;繼承可擴(kuò)展已存在的代碼模塊(類);它們最終需要的結(jié)果(代碼重用)。多態(tài)是為了實現(xiàn)另一個目的(接口重用)。多態(tài)的作用,就是為了類在繼承和派生的時候,保證使用“家譜”中任一類的實例的某一屬性時的正確調(diào)用。
事件響應(yīng)者鏈的概念(responder chain);
響應(yīng)者鏈表示一系列的響應(yīng)者對象.事件被交給由第一響應(yīng)者對象處理,如果第一響應(yīng)者不處理,事件被沿著響應(yīng)者鏈向上傳遞,交給下一響應(yīng)者(next responder).一般來說,第一響應(yīng)者是視圖對象或者其子類對象,當(dāng)其被觸摸后事件被交由它處理,如果它不處理,事件就會被傳遞給它的視圖控制器對象(如果存在),然后就是它的父視圖(superView)對象(如果存在),以此類推,直到頂層視圖.接下來會沿著頂層視圖(top view)到窗口(UIWindow對象)再到程序(UIApplication對象).如果整個過程都沒有響應(yīng)這個事件,該事件就會被丟棄.一般情況下,在響應(yīng)者中只要由對象處理事件,事件就停止傳遞.但有時候可以在視圖扥響應(yīng)方法中根據(jù)一些條件判斷來決定是否需要繼續(xù)傳遞事件.
請簡述對MVC / MVVM 設(shè)計模式的理解
MVC設(shè)計模式
Model:負(fù)責(zé)存儲、定義、操作數(shù)據(jù);
View:負(fù)責(zé)呈現(xiàn)畫面以及與用戶進(jìn)行操作交互
Controller:Model和View的協(xié)調(diào)者,Controller把Model中的數(shù)據(jù)拿過來給View用。
Controller可以直接與Model和View進(jìn)行通信,而View不能和Controller直接通信。View與Controller通信需要利用代理協(xié)議的方式,當(dāng)有數(shù)據(jù)更新時,Model也要與Controller進(jìn)行通信,這個時候就要用Notification和KVO,這個方式就像廣播一樣,Model發(fā)信號,Controller設(shè)置監(jiān)聽接收信號,當(dāng)有數(shù)據(jù)更新時就發(fā)信號給Controller,Model和View不能直接進(jìn)行通信,這樣會違背MVC設(shè)計模式。
如何理解MVVM設(shè)計模式
ViewModel層,就是View和Model層的粘合劑,他是一個放置用戶輸入驗證邏輯,視圖顯示邏輯,發(fā)起網(wǎng)絡(luò)請求和其他各種各樣的代碼的極好的地方。說白了,就是把原來ViewController層的業(yè)務(wù)邏輯和頁面邏輯等剝離出來放到ViewModel層。
View層,就是ViewController層,他的任務(wù)就是從ViewModel層獲取數(shù)據(jù),然后顯示。
請簡述HTTP協(xié)議中g(shù)et請求和post請求的區(qū)別,同步請求和異步請求的區(qū)別
get請求
參數(shù)在地址后拼接,進(jìn)行請求數(shù)據(jù),不安全(因為所有參數(shù)都拼接在地址后面),不適合傳輸大量數(shù)據(jù)(長度限制,為256個字節(jié))。get提交、請求的數(shù)據(jù)會附在URL之后,即把數(shù)據(jù)放置在HTTP協(xié)議頭中。以分割URL和傳輸數(shù)據(jù),多個參數(shù)用&連接。如果數(shù)據(jù)是英文字母或數(shù)字,原樣發(fā)送,如果是空格,轉(zhuǎn)換為+,如果是中文/其他字符,則直接把字符串用BASE64加密。
post請求
參數(shù)在請求數(shù)據(jù)區(qū)放著,相對get請求更安全,并且數(shù)據(jù)大小沒有限制(1G)。把提交的數(shù)據(jù)放置在HTTP包的包體中,轉(zhuǎn)換成NSData進(jìn)行傳輸。
get提交的數(shù)據(jù)會在地址欄顯示出來,而post提交,地址欄不會改變。
傳輸數(shù)據(jù)的大小:
get請求時,傳輸數(shù)據(jù)就會受到URL長度限制,POST由于不是通過URL傳輸,理論上不受限。
- 安全性:post的安全性要比get的安全性高;通過get提交數(shù)據(jù),用戶名和密碼將明文出現(xiàn)在URL上,比如登錄界面有可能被瀏覽器緩存。
同步請求/異步請求
- 1.同步請求可以從網(wǎng)絡(luò)請求數(shù)據(jù),一旦發(fā)送同步請求,程序?qū)⑼V古c用戶交互,直到服務(wù)器返回數(shù)據(jù)完成,才可以進(jìn)行下一步操作;
- 2.異步請求不會阻塞主線程,而會建立一個新的線程來操作,用戶發(fā)出異步請求后,依然可以對UI進(jìn)行操作,程序可以繼續(xù)運(yùn)行;
多線程編程N(yùn)SThread/NSoperationQueue/NSObject/GCD 區(qū)別
單線程
只有一個線程的程序即為單線程.該線程被稱為主線程.在程序一運(yùn)行的時候就存在了.
問題:
因為只有一個線程,所有代碼的執(zhí)行只能從上往下順序執(zhí)行.如果有耗時的操作(網(wǎng)絡(luò)請求,數(shù)據(jù)解析等),會造成界面假死,影響用戶體驗/
解決:
將一些耗時的操作放在子線程中去執(zhí)行,主線程只用來UI的展示和刷新;
多線程
除了主線程外,還存在其他線程的程序即為多線程程序
注意事項:
所有跟UI相關(guān)的操作都必須放在主線程中;
問題:
既然多線程程序有這么多的好處,該如何開辟子線程?
解決方案:
iOS中可以使用NSThread/NSoperationQueue/NSObject/GCD來開辟子線程.
問題:
無論開辟了多少子線程,最終的目的都要回到主線程刷新UI,那么該如何從子線程回主線程?
解決方案:
1.使用performSelectorOnMainThread方法;
2.使用GCD中dispatch_async(dispath_get_mainQueue(),);
問題:
既然多線程可以解決界面假死現(xiàn)象,那么是不是開辟的線程越多越好?
答:
不是。開辟子線程需要消耗內(nèi)存,而且不管開辟了多少子線程。最終都是要回到主線程的。所以開辟的子線程越多對于CPU性能的消耗越大。所以根據(jù)需要開辟適當(dāng)?shù)木€程。
問題:
如何讓實現(xiàn)線程同步(里面任務(wù)一個個執(zhí)行)
答:
1.GCD里面使用串行隊列.
2.NSOperationQueue里面當(dāng)把最大并發(fā)數(shù)發(fā)送設(shè)置1的時候,也可以實現(xiàn)線程同步;
NSObject
@interface NSObject (NSThreadPerformAdditions)
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);
- 使用NSObject開辟子線程方式:performSelectorInBackground;該方法調(diào)用一次,開辟一個子線程;
- 使用performSelectorOnMainThread方法回到主線程
NSThread:
當(dāng)需要進(jìn)行一些耗時操作時會把耗時的操作放到線程中。線程同步:多個線程同時訪問一個數(shù)據(jù)會出問題,NSlock、線程同步塊、@synchronized(self){}。
NSThread開辟子線程的方式有兩種
/**
* 第一種方式
* - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)
* @param target 目標(biāo)
* @param selector 方法(如果方法有參數(shù),那么參數(shù)即為object傳入的參數(shù))
* @param object 參數(shù)
*
* @return NSThread對象
* 注意事項:需要手動調(diào)用start方法
*/
/**
* 第二種方法(NSThread類方法)
*
* + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
* @param SEL 方法(如果方法有參數(shù),那么參數(shù)即為object傳入的參數(shù))
*
* 自動開啟線程,需手動取消
*/
使用事項:
使用NSThread開辟的子線程中需要添加@autoreleasepool來釋放在對應(yīng)子線程中開辟的內(nèi)存,其實在主線程中也設(shè)有@autoreleasepool.
NSOperationQueue
NSOperationQueue操作隊列(不需考慮線程同步問題)。編程的重點(diǎn)都放在main里面,使用NSInvocationOperation、NSBlockOperation自定義Operation。創(chuàng)建一個操作綁定相應(yīng)的方法,當(dāng)把操作添加到操作隊列中時,操作綁定的方法就會自動執(zhí)行了,當(dāng)把操作添加到操作隊列中時,默認(rèn)會調(diào)用main方法。
NSOPeration是系統(tǒng)提供的一個用來封裝任務(wù)的抽象類.
注意事項:
1.NSOperation沒有線程之分.把它放在主線程,它就在主線程中執(zhí)行.把它放在子線程中,它就在子線程中執(zhí)行.
2.NSOperation是抽象類,在真實的開發(fā)中不會直接使用NSOperation,而是使用其子類:NSInvocationOperation(target-action)和NSBlockOperation(block);NSOperationQueue: 操作隊列,存放的是一個個操作(NSOperation)
-
Tips
- 1.NSOperationQueue可以實現(xiàn)多線程,即它會根據(jù)放在隊列的任務(wù)來自行開辟子線程,由系統(tǒng)來決定,跟程序員沒有半毛錢關(guān)系.(坑:我現(xiàn)在往操作隊列中放兩個任務(wù), 那么需要幾線程?)
- 2.當(dāng)把操作放在操作隊列中之后,無需手動調(diào)用start.
- 3.最大并發(fā)數(shù)決定了當(dāng)前一共有幾個任務(wù)正在執(zhí)行.如果設(shè)置為1;則意味著當(dāng)前只有一個任務(wù)正在執(zhí)行.可以實現(xiàn)線程同步(串行執(zhí)行)和線程個數(shù)沒有關(guān)系.因為線程個數(shù)由系統(tǒng)來決定的.
GCD
GCD(`Grand Central Dispatch)宏大的中央調(diào)度,串行隊列、并發(fā)隊列、主線程隊列;
同步和異步:同步指第一個任務(wù)不執(zhí)行完,不會開始第二個,異步是不管第一個有沒有執(zhí)行完,都開始第二個。
串行和并行:串行是多個任務(wù)按一定順序執(zhí)行,并行是多個任務(wù)同時執(zhí)行;
代碼是在分線程執(zhí)行,在主線程嘟列中刷新UI。GCD:GCD核心是將任務(wù)放在分發(fā)隊列中去執(zhí)行.
分發(fā)隊列分為兩種:串行隊列:SerialQueue.如果將任務(wù)放在一個串行隊列中.那么任務(wù)會順序執(zhí)行.遵循FIFO,可以實現(xiàn)線程同步.但是當(dāng)有多個serialQueue時,串行隊列和串行隊列中的任務(wù)可以實現(xiàn)并發(fā)執(zhí)行.
串行隊列兩種:
- 系統(tǒng)自帶的 : 主隊列;當(dāng)把任務(wù)放在主隊列中時,任務(wù)會按照順序在主隊列中執(zhí)行.
dispatch_queue_t queue = dispatch_get_main_queue();
- 自己創(chuàng)建的 : 自己創(chuàng)建的隊列中的任務(wù)會在其他線程中順序執(zhí)行.
dispatch_queue_t queue = dispatch_queue_create(“com.xxw.serialQueue", DISPATCH_QUEUE_SERIAL);
并行隊列分為兩種:
1.系統(tǒng)自帶的: 使用系統(tǒng)提供好的globalQueue會開辟子線程來執(zhí)行任務(wù),任務(wù)的執(zhí)行是并發(fā)執(zhí)行的.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2.自己創(chuàng)建的: 一般情況下,不回你自己創(chuàng)建并行隊列,因為系統(tǒng)提供好的globalQueue(在子線程)可以滿足需求.
dispatch_queue_t queue = dispatch_queue_create("com.fy.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
GCD其他用法
1.使用dispatch_after 延遲特定的時間去做某事
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ });
2.dispatch_sync函數(shù)會阻塞當(dāng)前線程,去對應(yīng)的隊列中去執(zhí)行block中的任務(wù),當(dāng)任務(wù),執(zhí)行完之后才會回到原來的線程中
dispatch_sync(dispatch_get_main_queue(), ^{ });
3..dispatch_apply重復(fù)執(zhí)行
dispatch_apply(arr.count, dispatch_queue_create("com.fy.apply", DISPATCH_QUEUE_SERIAL), ^(size_t i) {});
4.barrier 函數(shù)在使用的時候隊列必須是自己創(chuàng)建的的并行隊列,否則barrier順序起不到作用;
dispatch_queue_t queue = dispatch_queue_create("com.fy.barrier", DISPATCH_QUEUE_CONCURRENT); dispatch_barrier_async(queue, ^{ dispatch_async(queue, ^{ }); dispatch_async(queue, ^{ }); });
5.使用group_notify函數(shù)/ 只有當(dāng)小組內(nèi)所有任務(wù)完成之后,才會執(zhí)行g(shù)roup_notify里面的內(nèi)容(PS:小組內(nèi)最起碼要有一組任務(wù));
dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, dispatch_get_main_queue(), ^{ }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ }); dispatch_group_async(group, dispatch_get_main_queue(), ^{ });
6.dispatch_once_t
static dispatch_once_t oneToken; dispatch_once(&oneToken, ^{ NSLog(@"你就是我的唯一"); });
總結(jié):
在真正開發(fā)的過程中,一般會使用自己創(chuàng)建的串行隊列或者是系統(tǒng)提供好的并行隊列;
NSOPerationQueue 與 GCD 的區(qū)別與選用
GCD 技術(shù)是一個輕量的,底層實現(xiàn)隱藏的神奇技術(shù),我們能夠通過GCD和block輕松實現(xiàn)多線程編程,有時候,GCD相比其他系統(tǒng)提供的多線程方法更加有效,當(dāng)然,有時候GCD不是最佳選擇,另一個多線程編程的技術(shù) NSOprationQueue 讓我們能夠?qū)⒑笈_線程以隊列方式依序執(zhí)行,并提供更多操作的入口,這和 GCD 的實現(xiàn)有些類似。
這種類似不是一個巧合,在早期,MacOX 與 iOS 的程序都普遍采用Operation Queue來進(jìn)行編寫后臺線程代碼,而之后出現(xiàn)的GCD技術(shù)大體是依照前者的原則來實現(xiàn)的,而隨著GCD的普及,在iOS 4 與 MacOS X 10.6以后,Operation Queue的底層實現(xiàn)都是用GCD來實現(xiàn)的。
那這兩者直接有什么區(qū)別呢?
- GCD是底層的C語言構(gòu)成的API,而NSOperationQueue及相關(guān)對象是Objc的對象。在GCD中,在隊列中執(zhí)行的是由block構(gòu)成的任務(wù),這是一個輕量級的數(shù)據(jù)結(jié)構(gòu);而Operation作為一個對象,為我們提供了更多的選擇;
- 在NSOperationQueue中,我們可以隨時取消已經(jīng)設(shè)定要準(zhǔn)備執(zhí)行的任務(wù)(當(dāng)然,已經(jīng)開始的任務(wù)就無法阻止了),而GCD沒法停止已經(jīng)加入queue的block(其實是有的,但需要許多復(fù)雜的代碼);
- NSOperation能夠方便地設(shè)置依賴關(guān)系,我們可以讓一個Operation依賴于另一個Operation,這樣的話盡管兩個Operation處于同一個并行隊列中,但前者會直到后者執(zhí)行完畢后再執(zhí)行;
- 我們能將KVO應(yīng)用在NSOperation中,可以監(jiān)聽一個Operation是否完成或取消,這樣子能比GCD更加有效地掌控我們執(zhí)行的后臺任務(wù);
- 在NSOperation中,我們能夠設(shè)置NSOperation的priority優(yōu)先級,能夠使同一個并行隊列中的任務(wù)區(qū)分先后地執(zhí)行,而在GCD中,我們只能區(qū)分不同任務(wù)隊列的優(yōu)先級,如果要區(qū)分block任務(wù)的優(yōu)先級,也需要大量的復(fù)雜代碼;
- 我們能夠?qū)SOperation進(jìn)行繼承,在這之上添加成員變量與成員方法,提高整個代碼的復(fù)用度,這比簡單地將block任務(wù)排入執(zhí)行隊列更有自由度,能夠在其之上添加更多自定制的功能。
總的來說,Operation queue 提供了更多你在編寫多線程程序時需要的功能,并隱藏了許多線程調(diào)度,線程取消與線程優(yōu)先級的復(fù)雜代碼,為我們提供簡單的API入口。從編程原則來說,一般我們需要盡可能的使用高等級、封裝完美的API,在必須時才使用底層API。但是我認(rèn)為當(dāng)我們的需求能夠以更簡單的底層代碼完成的時候,簡潔的GCD或許是個更好的選擇,而Operation queue 為我們提供能更多的選擇。
NSOperationQueue和GCD的區(qū)別,以及在什么場合下使用
- GCD是純C語言的API 。NSOperationQueue是基于GCD的OC的封裝。
- GCD只支持FIFO隊列,NSOperationQueue可以方便設(shè)置執(zhí)行順序,設(shè)置最大的并發(fā)數(shù)量。
- NSOperationQueue可是方便的設(shè)置operation之間的依賴關(guān)系,GCD則需要很多代碼。
- NSOperationQueue支持KVO,可以檢測operation是否正在執(zhí)行(isExecuted),是否結(jié)束(isFinished),是否取消(isCanceled)
- GCD的執(zhí)行速度比NSOperationQueue快。
使用場合
- 任務(wù)之間不太相互依賴:GCD
- 任務(wù)之間有依賴或要監(jiān)聽任務(wù)的執(zhí)行情況:NSOperationQueue
iOS中的沙盒機(jī)制
iOS應(yīng)用程序只能對自己創(chuàng)建的文件系統(tǒng)讀取文件,這個獨(dú)立、封閉、安全的空間,叫做沙盒。每個ios應(yīng)用都有自己的應(yīng)用沙盒,應(yīng)用沙盒就是文件系統(tǒng)目錄,與其他應(yīng)用的文件系統(tǒng)隔離。它一般存放著程序包文件(可執(zhí)行文件)、圖片、音頻、視頻、plist文件、sqlite數(shù)據(jù)庫以及其他文件。每個應(yīng)用程序都有自己的獨(dú)立的存儲空間(沙盒)一般來說應(yīng)用程序之間是不可以互相訪問的,在ios8中已經(jīng)開放訪問,模擬器沙盒的位置路徑:/User/userName/Library/Application Support/iPhone Simulator;當(dāng)我們創(chuàng)建應(yīng)用程序時,在每個沙盒中含有三個文件,分別是Document、Library(下面有Caches和Preferences目錄)和temp。
應(yīng)用程序包:包含所有的資源文件和可執(zhí)行文件。
對Document、Library(下面有Caches和Preferences目錄)和temp做簡單介紹
- Document:一般需要持久的數(shù)據(jù)都放在此目錄中,可以在當(dāng)中添加子文件夾,iTunes備份和恢復(fù)的時候,會包括此目錄。
- Library:設(shè)置程序的默認(rèn)設(shè)置和其他狀態(tài)信息,iTunes會自動備份該目錄,例如雜志、新聞、地圖應(yīng)用使用的數(shù)據(jù)庫緩存文件和可下載內(nèi)容應(yīng)該保存到這個文件夾。一般可以重新下載或者重新生成的數(shù)據(jù)應(yīng)該保存在 <Application_Home>/Library/Caches 目錄下面。
Libaray/Caches:存放緩存文件,iTunes不會備份此目錄,此目錄下文件不會在應(yīng)用退出刪除。一般存放體積比較大,不是特別重要的資源。
Libaray/PreferencePanes:保存應(yīng)用的所有偏好設(shè)置,ios的Settings(設(shè)置)應(yīng)用會在該目錄中查找應(yīng)用的設(shè)置信息,iTunes會自動備份該目錄。 - temp:創(chuàng)建臨時文件的目錄,當(dāng)iOS設(shè)備重啟時,文件會被自動清除,只是臨時使用的
防止iCloud backup
- 正確放置文件 見上面一條
- 使用 NSURLIsExcludedFromBackupKey or kCFURLIsExcludedFromBackupKey 防止文件 或者整個文件夾 iCloud 備份
- (BOOL)addSkipBackupAttributeToItemAtPath:(NSString *) filePathString
{
NSURL* URL= [NSURL fileURLWithPath: filePathString];
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error];
if(!success){
NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
}
return success;
}
initialize 和 load
在Objective-C中,runtime會自動調(diào)用每個類的兩個方法。+load會在類初始加載時調(diào)用,+initialize會在第一次調(diào)用類的類方法或?qū)嵗椒ㄖ氨徽{(diào)用。這兩個方法是可選的,且只有在實現(xiàn)了它們時才會被調(diào)用。
共同點(diǎn):兩個方法都只會被調(diào)用一次。