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