面向對象的三大特征,并作簡單的介紹。
面向對象的三個基本特征是:封裝、繼承、多態。
- 1.封裝是面向對象的特征之一, 是對象和類概念的主要特性。 封裝,也就是把客觀事物封裝成抽象的類,并且類可以把自己的數據和方法只讓可信的類或者對象操作,對不可信的進行信息隱藏。隱藏對象的屬性和實現細節,僅對外公開接口,提高代碼安全性,封轉程度越高,獨立性越強,使用越方便
- 2.繼承是指這樣一種能力: 它可以使用現有類的所有功能,并在無需重新編寫原來的類的情況下對這些功能進行擴展。 通過繼承創建的新類稱為“子類”或“派生類”。 被繼承的類稱為“基類”、“父類”或“超類”
- 3.多態性: 允許你將父對象設置成為和一個或更多的他的子對象相等的技術,賦值之后,父對象就可以根據當前賦值給它的子 對象的特性以不同的方式運作。簡單的說,就是一句話:允許將子類類型的指針賦值給父類類型的指針
When we call objective c is runtimelanguage what does it mean? 我們說的 obc是動態運行時語言是什么意思?
多態。 主要是將數據類型的確定由編譯時,推遲到了運行時。這個問題其實淺涉及到兩個概念,運行時和多態。簡單來說,運行時機制使我們直到運行時才去決定一個對象的類別,以及調用該類別對象指定方法。 多態:不同對象以自己的方式響應相同的消息的能力叫做多態。意思就是假設生物類(life)都用有一個相同的方法-eat; 那人類屬于生物,豬也屬于生物,都繼承了 life 后,實現各自的 eat,但是調用是我們只需調用各自的 eat 方法。也就是不同的對象以自己的方式響應了相同的消息(響應了 eat 這個選擇器)。 因此也可以說,運行時機制是多態的基礎.
static的作用
- static修飾的函數是一個內部函數,只能在本文件中調用,其他文件不能調用
- static修飾的全局變量是一個內部變量,只能在本文件中使用,其他文件不能使用
- static修飾的局部變量只會初始化一次,并且在程序退出時才會回收內存
堆和棧的區別
- 堆空間的內存是動態分配的,一般存放對象,并且需要手動釋放內存
- 棧空間的內存由系統自動分配,一般存放局部變量等,不需要手動管理內存
http協議的組成和特性
- 組成:http 請求由三部分組成;分別是:請求行、消息報頭、請求正文
HTTP協議的主要特點- 1.支持客戶/服務器模式
- 2.簡單快速:客戶向服務器請求服務時,只需傳送請求方法和路徑。請求方法常用的有 GET、HEAD、POST。每種方法規定了客戶與服務器聯系的類型不同。由于 HTTP 協議簡單,使得 HTTP 服務器的程序規模小,因而通信速度很快
- 3.靈活:HTTP 允許傳輸任意類型的數據對象。正在傳輸的類型由 Content-Type 加以標記
- 4.無連接:無連接的含義是限制每次連接只處理一個請求。服務器處理完客戶的請求,并收到客戶的應答后,即斷開連接。采用這種方式可以節省傳輸時間
- 5.無狀態:HTTP 協議是無狀態協議。無狀態是指協議對于事務處理沒有記憶能力
什么情況下會發生內存泄漏和內存溢出
當程序在申請內存后,無法釋放已申請的內存空間(例如一個對象或者變量使用完成后沒有釋放,這個對象一直占用著內存),一次內存泄露危害可以忽略,但內存泄露堆積后果很嚴重,無論多少內存,遲早會被占光。內存泄露會最終會導致內存溢出!當程序在申請內存時,沒有足夠的內存空間供其使用,出現out of memory;比如申請了一個int,但給它存了long才能存下的數,那就是內存溢出
自動釋放池是什么,如何工作
當您向一個對象發送一個autorelease消息時,Cocoa就會將該對象的一個引用放入到最新的自動釋放池。它仍然是個正當的對象,因此自動釋放池定義的作用域內的其它對象可以向它發送消息。當程序執行到作用域結束的位置時,自動釋放池就會被釋放,池中的所有對象也就被釋放
- 1.ojc-c是通過一種"referring counting"(引用計數)的方式來管理內存的,對象在開始分配內存(alloc)的時候引用計數為1,以后每當碰到有copy,retain的時候引用計數都會加一,每當碰到release和autorelease的時候引用計數就會減1,如果此對象的計數變為了0, 就會被系統銷毀
- 2.NSAutoreleasePool就是用來做引用計數的管理工作的,這個東西一般不用你管的
- 3.autorelease和release沒什么區別,只是引用計數減一的時機不同而已,autorelease會在對象的使用真正結束的時候才做引用計數減一
Swift中Struct和Class的區別
- 主要的區別就在于class是類型引用,而struct是值引用,在Objective-C時代,我們對類型引用和值引用就有了一定的了解,例如在Objective-C中常用的NSArray, NSDictionary, NSString, UIKit等都是類型引用;而NSInteger, CGFloat, CGRect等則是值引用
- swift Foundation框架的SDK,諸如String,Array,Dictionary都是基于struct實現的
- 在swift中,類型引用和值引用的區別在于,對于類型引用(class reference),將變量a賦值給變量b,即b = a,這樣的賦值語句僅僅將b的指針與a的指針一樣,指向同一塊內存區域,此時改變b的值,a也會跟著改變;而對于值引用(value reference),賦值語句b = a處理的過程是開辟一個新的內存b,將a變量的內容拷貝后存放到內存b,這時a和b完全沒有關系的兩個變量,對b的改變不會影響到a,反之亦然。
證書傳遞、驗證和數據加密、解密過程解析
- 1.客戶端發起HTTPS請求
- 2.服務端的配置
采用HTTPS協議的服務器必須要有一套數字證書,可以自己制作,也可以向組織申請。區別就是自己頒發的證書需要客戶端驗證通過,才可以繼續訪問,而使用受信任的公司申請的證書則不會彈出提示頁面(startssl就是個不錯的選擇,有1年的免費服務)。這套證書其實就是一對公鑰和私鑰。如果對公鑰和私鑰不太理解,可以想象成一把鑰匙和一個鎖頭,只是全世界只有你一個人有這把鑰匙,你可以把鎖頭給別人,別人可以用這個鎖把重要的東西鎖起來,然后發給你,因為只有你一個人有這把鑰匙,所以只有你才能看到被這把鎖鎖起來的東西。- 3.傳送證書
這個證書其實就是公鑰,只是包含了很多信息,如證書的頒發機構,過期時間等等。- 4.客戶端解析證書
這部分工作是有客戶端的TLS來完成的,首先會驗證公鑰是否有效,比如頒發機構,過期時間等等,如果發現異常,則會彈出一個警告框,提示證書存在問題。如果證書沒有問題,那么就生成一個隨即值。然后用證書對該隨機值進行加密。就好像上面說的,把隨機值用鎖頭鎖起來,這樣除非有鑰匙,不然看不到被鎖住的內容。- 5.傳送加密信息
這部分傳送的是用證書加密后的隨機值,目的就是讓服務端得到這個隨機值,以后客戶端和服務端的通信就可以通過這個隨機值來進行加密解密了。- 6.服務段解密信息
服務端用私鑰解密后,得到了客戶端傳過來的隨機值(私鑰),然后把內容通過該值進行對稱加密。所謂對稱加密就是,將信息和私鑰通過某種算法混合在一起,這樣除非知道私鑰,不然無法獲取內容,而正好客戶端和服務端都知道這個私鑰,所以只要加密算法夠彪悍,私鑰夠復雜,數據就夠安全。- 7.傳輸加密后的信息
這部分信息是服務段用私鑰加密后的信息,可以在客戶端被還原- 8.客戶端解密信息
客戶端用之前生成的私鑰解密服務段傳過來的信息,于是獲取了解密后的內容。整個過程第三方即使監聽到了數據,也束手無策。
客戶端發送https請求 ->服務器返回證書公鑰 -> 客戶端驗證公鑰并生成一個隨機值,用證書對該隨機值進行加密,傳給服務器 -> 服務端用私鑰解密后,得到了客戶端傳過來的隨機值(私鑰)->客戶端發送加密后的信息->客戶端用之前生成的私鑰解密服務段傳過來的信息
TCP/IP 協議
UDP
- UDP 不提供復雜的控制機制,利用 IP 提供面向無連接的通信服務
- 并且它是將應用程序發來的數據在收到的那一刻,立即按照原樣發送到網絡上的一種機制。即使是出現網絡擁堵的情況,UDP 也無法進行流量控制等避免網絡擁塞行為
- 此外,傳輸途中出現丟包,UDP 也不負責重發
- 甚至當包的到達順序出現亂序時也沒有糾正的功能
- 如果需要以上的細節控制,不得不交由采用 UDP 的應用程序去處理
UDP 常用于一下幾個方面:1.包總量較少的通信(DNS、SNMP等);2.視頻、音頻等多媒體通信(即時通信);3.限定于 LAN 等特定網絡中的應用通信;4.廣播通信(廣播、多播
TCP
- TCP 與 UDP 的區別相當大。它充分地實現了數據傳輸時各種控制功能,可以進行丟包時的重發控制,還可以對次序亂掉的分包進行順序控制。而這些在 UDP 中都沒有。
- 此外,TCP 作為一種面向有連接的協議,只有在確認通信對端存在時才會發送數據,從而可以控制通信流量的浪費
根據 TCP 的這些機制,在 IP 這種無連接的網絡上也能夠實現高可靠性的通信( 主要通過檢驗和、序列號、確認應答、重發控制、連接管理以及窗口控制等機制實現
三次握手
- TCP 提供面向有連接的通信傳輸。面向有連接是指在數據通信開始之前先做好兩端之間的準備工作
所謂三次握手是指建立一個 TCP 連接時需要客戶端和服務器端總共發送三個包以確認連接的建立。在socket編程中,這一過程由客戶端執行connect來觸發
image.png- 第一次握手:客戶端將標志位SYN置為1,隨機產生一個值seq=J,并將該數據包發送給服務器端,客戶端進入SYN_SENT狀態,等待服務器端確認
- 第二次握手:服務器端收到數據包后由標志位SYN=1知道客戶端請求建立連接,服務器端將標志位SYN和ACK都置為1,ack=J+1,隨機產生一個值seq=K,并將該數據包發送給客戶端以確認連接請求,服務器端進入SYN_RCVD狀態
- 第三次握手:客戶端收到確認后,檢查ack是否為J+1,ACK是否為1,如果正確則將標志位ACK置為1,ack=K+1,并將該數據包發送給服務器端,服務器端檢查ack是否為K+1,ACK是否為1,如果正確則連接建立成功,客戶端和服務器端進入ESTABLISHED狀態,完成三次握手,隨后客戶端與服務器端之間可以開始傳輸數據了
四次揮手
- 四次揮手即終止TCP連接,就是指斷開一個TCP連接時,需要客戶端和服務端總共發送4個包以確認連接的斷開。在socket編程中,這一過程由客戶端或服務端任一方執行close來觸發
由于TCP連接是全雙工的,因此,每個方向都必須要單獨進行關閉,這一原則是當一方完成數據發送任務后,發送一個FIN來終止這一方向的連接,收到一個FIN只是意味著這一方向上沒有數據流動了,即不會再收到數據了,但是在這個TCP連接上仍然能夠發送數據,直到這一方向也發送了FIN。首先進行關閉的一方將執行主動關閉,而另一方則執行被動關閉
image.png
中斷連接端可以是客戶端,也可以是服務器端。
- 第一次揮手:客戶端發送一個FIN=M,用來關閉客戶端到服務器端的數據傳送,客戶端進入FIN_WAIT_1狀態。意思是說"我客戶端沒有數據要發給你了",但是如果你服務器端還有數據沒有發送完成,則不必急著關閉連接,可以繼續發送數據
- 第二次揮手:服務器端收到FIN后,先發送ack=M+1,告訴客戶端,你的請求我收到了,但是我還沒準備好,請繼續你等我的消息。這個時候客戶端就進入FIN_WAIT_2 狀態,繼續等待服務器端的FIN報文
- 第三次揮手:當服務器端確定數據已發送完成,則向客戶端發送FIN=N報文,告訴客戶端,好了,我這邊數據發完了,準備好關閉連接了。服務器端進入LAST_ACK狀態
- 第四次揮手:客戶端收到FIN=N報文后,就知道可以關閉連接了,但是他還是不相信網絡,怕服務器端不知道要關閉,所以發送ack=N+1后進入TIME_WAIT狀態,如果Server端沒有收到ACK則可以重傳。服務器端收到ACK后,就知道可以斷開連接了。客戶端等待了2MSL后依然沒有收到回復,則證明服務器端已正常關閉,那好,我客戶端也可以關閉連接了。最終完成了四次握手
對MRC和ARC的理解
程序在運行的過程中通常通過創建一個OC對象/定義一個變量/調用一個函數或者方法,來增加程序的內存暫用,而一個移動設備的內存是有限的,每個軟件所能占用的內存也是有限的,所以我們需要合理的分配內存、清除內存,回收那些不再使用的對象,從而保證程序的穩定性
- 繼承了NSObject的對象存儲在操作系統的堆里面。操作系統的堆:一般由程序員分配釋放,若程序員不釋放,程序結束時可能由操作系統回收,分配方式類似于鏈表。非OC對象一般放在操作系統的棧里面,由操作系統自動分配釋放,存放函數的參數值,局部變量的值等。其操作方式類似于數據結構中的棧(先進先出)。任何繼承NSObject的對象需要進行內存管理,而其他非對象類型(int、char、float、double、struct、enum等)不需要進行內存管理
- 為保證對象的存在,每當創建引用到對象需要給對象發送一條retain消息,可以使引用計數器+1;當不需要對象時,通過給對象發送一條release消息,可以使引用計數器值-1
- 當對象的引用計數為0時,系統就知道這個對象不需要再使用了,所以可以釋放它的內存,通過給對象發送dealloc消息發起這個過程
需要注意的是:release并不代表銷毀/回收對象,僅僅是計數器-1
*使用ARC后,系統會檢測出何時需要保存對象,何時需要自動釋放對象,何時需要釋放對象。編譯器會管理好對象的內存,會在需要的地方插入retain、release、autorelease,通過生成正確的代碼去自動釋放或者保持對象。我們完全不用擔心編譯器會報錯- ARC判斷一個對象是否需要釋放不是通過引用計數來判斷的,而是通過強指針來判斷的
ARC下,不顯式指定任何屬性關鍵字時,默認的關鍵字都有哪些?
對應基本數據類型默認關鍵字是:atomic,readwrite,assign
對于普通的OC對象:atomic,readwrite,strong
runtime實現的機制
- 需要導入<objc/message.h><objc/runtime.h> runtime,運行時機制,它是一套C語言庫。 實際上我們編寫的所有OC代碼,最終都是轉成了runtime庫的東西,比如類轉成了runtime庫里面的結構體等數據類型,方法轉成了runtime庫里面的C語言函數,平時調方法都是轉成了objc_msgSend函數(所以說OC有個消息發送機制)因此,可以說runtime是OC的底層實現,是OC的幕后執行者##
- 運行時機制,runtime庫里面包含了跟類、成員變量、方法相關的API,比如獲取類里面的所有成員變量,為類動態添加成員變量,動態改變類的方法實現,為類動態添加新的方法等。
runtime如何通過selector找到對應的IMP地址?(分別考慮類方法和實例方法)
每一個類對象中都一個方法列表,方法列表中記錄著方法的名稱,方法實現,以及參數類型,其實selector本質就是方法名稱,通過這個方法名稱就可以在方法列表中找到對應的方法實現.
談談消息轉發機制實現
簡而言之,它允許未知的消息被困住并作出反應。換句話說,無論何時發送未知消息,它 都會以一個很好的包發送到您的代碼中,此時您可以隨心所欲地執行任何操作。
消息轉發的步驟
- 首先檢查這個selector是不是要忽略
- 檢測這個selector的target是不是nil,OC允許我們對一個nil對象執行任何方法都不會Crash,因為運行時會被忽略掉
- 開始檢查這個類的實現IMP,先從cache里查找,如果找到了就運行對應的函數去執行相應的代碼
- 如果cache中沒有找,就到類的方法列表中找對應的方法
- 如果類的方法列表中找不到,就到類的父類方法列表中查找,一直找到NSObject
- 如果還是沒有找到,就要進入動態方法解析和消息轉發了
沒有方法實現時,程序會在運行時掛掉并拋出unrecognized selector send to _ 的異常。但在異常拋出前,Objective-C的運行會給你三次拯救程序的機會
- Method resolution
* 首先,Objective-C運行時會調用+(BOOL)resolveInstanceMethod: 或者
+(BOOL)resolveClassMethod:,讓你有機會提供一個函數實現。如果你添加了函數并返回YES,那運行時系統就會重新啟動一次消息發送的過程。
- Fast forwarding
- (id)forwardingTargetForSelector:(SEL)aSelector {
if(aSelector == @selector(foo:)){
return [[BackupClass alloc] init]
}
return [super forwardingTargetForSelector:aSelector];
}
- Normal forwarding
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector==@selector(run)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector: aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL selector =[anInvocation selector];
RunPerson *RP1=[RunPerson new];
RunPerson *RP2=[RunPerson new];
if ([RP1 respondsToSelector:selector]) {
[anInvocation invokeWithTarget:RP1];
}
if ([RP2 respondsToSelector:selector]) {
[anInvocation invokeWithTarget:RP2];
}
}
Fast forwarding和Normal Forwarding區別:前者需要重載一個API,后者需要重載兩個API;前者只能轉發一個對象,后者可以連續轉發給多個對象。
UIViewController 生命周期
loadView()
viewDidLoad()
viewWillAppear
viewWillLayoutSubviews() - Optional((162.0, 308.0, 50.0, 50.0))
viewDidLayoutSubviews() - Optional((67.0, 269.0, 241.0, 129.0))
viewDidAppear
viewWillDisappear
viewDidDisappear
deinit
談談對自動釋放池的理解
自動釋放池
當我們不再使用一個對象的時候,應該將其空間釋放,但是有時候,我們不知道何時應該將其釋放。為了解決這個問題,Objective-C提供了autorelease方法。
使用autorelease的好處:不用再關心對象釋放的時間;不用再關心什么時候調用release
autorelease的本質:只是把對release的調用延遲了,對于每一個autorelease,系統只是把該對象放入了當前的autorelease pool中,當該pool被釋放時,該pool中所有對象會被調用release
自動釋放池在mrc和arc區別
- @autorelease是自動釋放池,讓我們更自由的管理內存,在MRC環境如果有alloc、new、copy如果想延遲釋放,我們都在自動釋放池中加上autorelease來延遲釋放
- 當我們手動創建一個@autoreleasepool,里面創建了很多臨時變量,當@autoreleasepool結束時,里面的內存就會回收
- ARC時代,系統自動管理自己的autoreleasepool,RunLoop就是iOS中的消息循環機制,但是個RunLoop結束時系統才會一次性清理掉被autorelease處理過的對象,其實本質上說是在本次RunLoop迭代結束時清理掉本地迭代期間被放到autoreleasepool中的對象。至于何時RunLoop結束沒有固定的duration。
多層自動釋放池嵌套的對象在哪一層釋放
- 自動釋放池是以棧的形式存在的。 由于棧只有一個入口,所以調用autorelease會將對象放到棧頂的自動釋放池,棧頂就是離autorelease方法最近的自動釋放池
對于block的理解、mrc和arc下有什么區別、使用注意事項
- 在ARC中,__Block修飾的變量被引用到,引用計數+1;在MRC中__Block修飾的變量的引用計數是不變的
- 在ARC中,__Block修飾,會引起循環引用;在MRC中__Block修飾的變量可以避免循環引用
- __Block不管是ARC還是MRC的模式下都可以使用,可以修飾對象,還可以修飾基本數據類型;__weak只能在ARC模式下使用,只能修飾對象不能修飾基本數據類型;
- __Block對象可以在block中被重新賦值,__weak不可以
對于深拷貝和淺拷貝的理解
淺拷貝:拷貝后只是指向了該對象的地址,這兩個對象指向了同一個內存地址,通過任何一個對象修改值,這兩個對象再次獲取值都是獲取修改后的值。
深拷貝:拷貝了對象的內容然后重新開辟了新的內存空間,這是兩個互相獨立的對象,對任意一個修改值,另一個的值都不會改變。
- 不可變字符串/對象: copy/淺拷貝 mutableCopy/深拷貝
- 可變字符串/對象:copy/深拷貝 mutableCopy/深拷貝
- 集合對象的值:這說明集合對象的深拷貝只是單層深拷貝,只是給該對象分配了一個新的內存地址而集合對象里面的值還都指向原來的內存地址。
對于strong、weak理解
strong
- 當一個對象不再有strong類型引用指向它的時候,它就會被釋放,即使該對象還有weak類型指針引用指向它,并且還指向該對象的weak引用會被置為nil,防止野指針的出現
將一個對象類比為一只狗,釋放對象類比為狗要跑掉。strong類型就像是栓住狗的繩子,只要有繩子拴住狗,它就不會跑掉。如果有5條繩子栓一只狗,除非5條繩子都脫落,否則狗是不會跑掉的,就相當于只有每個strong指針都被置為nil時,對象才會被釋放。weak類型就像是看著狗的小孩子,只要狗一直被拴著,那么小孩子就能看到狗,會一直指向它,只要狗的繩子脫落,那么狗就會跑掉,不管有多少的小孩在看著它。最后,狗跑掉以后,小孩也就和狗之間沒什么聯系了。只要最后一個strong型指針不再指向對象,那么對象就會被釋放,同時所有的weak型指針都將會被清除。
weak
- weak最主要的防止strong類型之間形成循環,使大家都不能釋放造成內存泄漏。最明顯的例子就是delegate,以一個viewcontroller和tableView為例,如果viewcontroller中有strong類型指向tableview,而tableView的delegate指向viewcontroller,如果delegate是strong類型,那么要銷毀delegate就要銷毀viewcontroller,而要銷毀viewcontroller則要先銷毀delegate,這樣就形成了循環了。所以要將delegate聲明為weak類型的,這樣才能在viewcontroller需要銷毀的時候進行銷毀。
retain和strong一致的(聲明為強引用);assign和weak是基本一致的(聲明為弱引用),之所以說它們是基本一致的是因為它們還是有所不同的。weak嚴格的說應當叫做“歸零弱引用”,即當對象銷毀后,會自動的把它的指針置為nil,這樣可以防止野指針錯誤。而assign銷毀對象后,不會把對象的指針置為nil,對象已經銷毀,但指針還在癡癡的指向它,這就成了野指針,這是比較危險的。retain和assign都是ARC之前使用的,strong和weak都是在ARC下才加入的,strong是默認的修飾符
weak原理
Runtime維護了一個weak表,用于存儲指向某個對象的所有weak指針。weak表其實是一個hash表,key是所指對象的地址,value是weak指針地址(這個地址的值是所指對象的地址)數組
weak的實現原理可以概括為以下三步:
- 初始化時,runtime會調用objc_initWeak函數,初始化一個新的weak指針指向對象的地址
- 添加引用時:objc_initWeak函數會調用objc_storeWeak()函數,objc_storeWeak()的作用是更新指針指向,創建對應的弱引用表
- 釋放時,調用clearDeallocating函數。clearDeallocating函數首先根據對象地址獲取所有weak指針地址數組,然后遍歷這個數組把其中的數據設置為nil,最后把這個entry從weak表中刪除,最后清理對象的記錄。
簡述下block的實現
Http協議30x的錯誤是什么
1xx 消息——請求已被服務器接收,繼續處理
2xx 成功——請求已成功被服務器接收、理解、并接受
3xx 重定向——需要后續操作才能完成這一請求
4xx 請求錯誤——請求含有詞法錯誤或者無法被執行
5xx 服務器錯誤——服務器在處理某個正確請求時發生錯誤
談談你對runloop得理解:由淺入深
- 一個線程一次只能執行一個任務,執行完成后線程就會退出。RunLoop可以讓線程隨時處理事件但不退出
- 引用RunLoop機制的目的是利用RunLoop機制的特點實現整體省電的效果,并且讓系統和應用可以流暢的運行,提高響應速度,達到極致的用戶體驗。
進程是一家工廠,線程是一個流水線,RunLoop就是流水線主管;當工廠接到商家分配的訂單分配給這個流水線時,RunLoop就會啟動這個流水線,讓流水線運動起來,生產產品;當產品生產完畢時,RunLoop就會暫停流水線,節約資源。RunLoop管理流水線,流水線才不會因為無所事事被工廠銷毀;而不需要流水線時,就會辭退RunLoop這個主管,即退出線程,把所有資源釋放
RunLoop實質上是一個對象,這個對象管理了其需要處理的事件和消息,并提供了入口函數來執行Event Loop的邏輯。線程執行這個函數后,會一直處于這個函數內部,接受消息 -> 等待 -> 執行 的循環中,直到這個循環結束
RunLoop的特性
- 主線程的RunLoop在應用啟動的時候就會自動創建
- 其他線程則需要再該線程下自己啟動
- 不能直接創建RunLoop
- RunLoop并不是線程安全的,所以需要避免在其他線程上嗲偶偶那個當前線程的RunLoop
- RunLoop負責處理消息事件,即輸入源事件和計時事件
談談對多線程理解:由淺入深
進程
- 進程代表當前運行的一個程序
- 是系統分配資源的基本單位
- 每個進程之間是獨立的,每個進程均運行在其專用且受保護的內存空間內
- 比如同時打開QQ、Xcode,系統就會分別啟動2個進程
- 進程可以理解為一個工廠
線程
- 線程是進程的基本執行單元,一個進程(程序)的所有任務都在線程中執行
- 一個進程含有一個線程或多個線程
- 應用程序打開后會默認開辟一個線程叫做主線程或者UI線程
- 線程可以理解為工廠里的工人
串行
- 多個任務按順序執行
- 類似于一個窗口辦公排隊
- 也就是說,在同一時間內,1個線程只能執行1個任務
- 比如在1個線程中下載3個文件(分別是文件A、文件B、文件C)就要依次執行
并行
- 多個任務同一時間一起執行
- 似于多個窗口辦公
- 比如同時開啟3條線程分別下載3個文件(分別是文件A、文件B、文件C),同時執行
并發
- 很多人容易認為并發和并行是一個意思,但實際上他們有本質的區別
- 并發看起來像多個任務同一時間一起執行
- 但實際上是CPU快速的輪轉切換造成的假象
多線程概念
- 本質
* 在一個進程中開啟多個線程并發執行
- 原理
* 同一時間,CPU只能處理1條線程,只有1條線程在工作(執行)
* 多線程并發(同時)執行,其實是CPU快速地在多條線程之間調度(切換)
* 如果CPU調度線程的時間足夠快,就造成了多線程并發執行的假象
- 優點
* 能適當提高程序的執行效率
* 能適當提高資源利用率(CPU、內存利用率)
- 缺點
* 線程需要耗費系統資源
* 線程需要消耗棧空間的1MB資源
* 其他線程每個消耗512KB資源
* 程序設計更加復雜:比如線程之間的通信、多線程的數據共享
主線程
- 概念
* 一個iOS程序運行后,默認會開啟1條線程,稱為“主線程”或“UI線程”
- 作用
* 顯示\刷新UI界面
* 處理UI事件(比如點擊事件、滾動事件、拖拽事件等)
- 注意
* 別將比較耗時的操作放到主線程中
* 耗時操作會卡住主線程,嚴重影響UI的流暢度,給用戶一種“卡”的壞體驗
多線程實現
- NSThread
- GCD
* 任務:即操作,你想要干什么,說白了就是一段代碼,在 GCD 中就是一個 Block,所以添加任務十分方便。任務有兩種執行方式: 同步執行 和 異步執行,他們之間的區別是 是否會創建新的線程。
* 隊列:用于存放任務。一共有兩種隊列, 串行隊列 和 并行隊列。
- NSOperation
* 是蘋果公司對 GCD 的封裝,完全面向對象,所以使用起來更好理解。 大家可以看到 NSOperation 和 NSOperationQueue 分別對應 GCD 的 任務 和 隊列 。操作步驟也很好理解。
* NSOperation也有兩個概念,隊列和任務。
- NSOperation 對比 GCD
* GCD效率更高,使用起來也很方便
* NSOperation面向對象,可讀性更高,架構更清晰,對于復雜多線程場景,如并發中存在串行,和設置最大并發數,擁有現在的API,使用起來特別簡單
互斥鎖
@synchronized(self) {
//需要執行的代碼塊
}
談談category和extension區別,系統如何底層實現category
extension可以添加實例變量,而category是無法添加實例變量的(因為在運行期,對象的內存布局已經確定,如果添加實例變量就會破壞類的內部布局,這對編譯型語言來說是災難性的)
- extension 在編譯期決議,它是類的一部分,但是category則完全不一樣,它在運行期決議的。extension在編譯期和頭文件里的@interface以及實現文件@implement一起形成一個完整的類。extension伴隨類的產生而產生,亦隨之一起消亡。
- extension一般用來隱藏類的私有信息,你必須有一個類的源碼才能為一個類添加extension,所以你無法為系統的類比如NSString添加extension,除非創建子類再添加extension。而category不需要有類的源碼,我們可以給系統提供的類添加category。
- extension可以添加實例變量,而category不可以。
- extension和category都可以添加屬性,但是category的屬性不能生成成員變量和getter、setter方法的實現。
Category可以添加屬性,但是并不會自動生成成員變量及set/get方法。因為category_t結構體中并不存在成員變量。通過之前對對象的分析我們知道成員變量是存放在實例對象中的,并且編譯的那一刻就已經決定好了。而分類是在運行時才去加載的。那么我們就無法再程序運行時將分類的成員變量中添加到實例對象的結構體中。因此分類中不可以添加成員變量
objc中的類方法和實例方法有什么本質區別和聯系?
- 類方法:
* 類方法是屬于類對象的
* 類方法只能通過類對象調用
* 類方法中的self是類對象
* 類方法可以調用其他的類方法
* 類方法中不能訪問成員變量
* 類方法中不定直接調用對象方法
- 實例方法:
* 實例方法是屬于實例對象的
* 實例方法只能通過實例對象調用
* 實例方法中的self是實例對象
* 實例方法中可以訪問成員變量
* 實例方法中直接調用實例方法
* 實例方法中也可以調用類方法(通過類名)
能否向編譯后得到的類中增加實例變量?能否向運行時創建的類中添加實例變量?為什么?
- 不能向編譯后得到的類中增加實例變量
- 能向運行時創建的類中添加實例變量
- 因為編譯后的類已經注冊在 runtime 中,類結構體中的 objc_ivar_list 實例變量的鏈表 和 instance_size 實例變量的內存大小已經確定,同時runtime 會調用 class_setIvarLayout 或 class_setWeakIvarLayout 來處理 strong weak 引用。所以不能向存在的類中添加實例變量
- 運行時創建的類是可以添加實例變量,調用 class_addIvar 函數。但是得在調用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上
答案是通過 isa 混寫(isa-swizzling)
KVC的底層實現
- 檢查是否存在相應的key的set方法,如果存在,就調用set方法
- 如果set方法不存在,就會查找與key相同名稱并且帶下劃線的成員變量,如果有,則直接給成員變量屬性賦值
- 如果沒有找到_key,就會查找相同名稱的屬性key,如果有就直接賦值
- 如果還沒有找到,則調用valueForUndefinedKey:和setValue:forUndefinedKey:方法。這些方法的默認實現都是拋出異常,我們可以根據需要重寫它們
iOS遠程推送原理及詳細實現過程
- 1.裝有我們應用程序的 iOS 設備,需要向 APNs 服務器注冊
- 2.注冊成功后,APNs 服務器將會給我們返回一個 devicetoken,我們獲取到這個 token 后會將這個 token 發送給我們自己的應用服務器。
- 3.當我們需要推送消息時,我們的應用服務器將消息按照指定的格式進行打包,然后結合 iOS 設備的 devicetoken 一起發給 APNs 服務器
- 4.我們的應用會和 APNs 服務器維持一個基于 TCP 的長連接,APNs 服務器將新消息推送到iOS 設備上,然后在設備屏幕上顯示出推送的消息
KVO內部實現原理
KVO是基于runtime機制實現的
當某個類的對象第一次被觀察時,系統就會在運行期動態地創建該類的一個派生類,在這個派生類中重寫基類中任何被觀察屬性的setter 方法。派生類在被重寫的 setter 方法實現真正的通知機制(Person->NSKVONotifying_Person)
apple用什么方式實現對一個對象的KVO?
- 鍵值觀察通知依賴于 NSObject 的兩個方法: willChangeValueForKey: 和 didChangevlueForKey: 。在一個被觀察屬性發生改變之前, willChangeValueForKey: 一定會被調用,這就會記錄舊的值。而當改變發生后, observeValueForKey:ofObject:change:context: 會被調用,繼而 didChangeValueForKey: 也會被調用。可以手動實現這些調用,但很少有人這么做。一般我們只在希望能控制回調的調用時機時才會這么做。大部分情況下,改變通知會自動調用。
- 比如調用 setNow: 時,系統還會以某種方式在中間插入 wilChangeValueForKey: 、 didChangeValueForKey: 和 observeValueForKeyPath:ofObject:change:context: 的調用。大家可能以為這是因為 setNow: 是合成方法,有時候我們也能看到有人這么寫代碼:
- (void)setNow:(NSDate *)aDate {
[self willChangeValueForKey:@"now"]; // 沒有必要
_now = aDate;
[self didChangeValueForKey:@"now"];// 沒有必要
}
這完全沒有必要,不要這么做,這樣的話,KVO代碼會被調用兩次。KVO在調用存取方法之前總是調用 willChangeValueForKey: ,之后總是調用 didChangeValueForkey: 。怎么做到的呢?答案是通過 isa 混寫(isa-swizzling)。第一次對一個對象調用 addObserver:forKeyPath:options:context: 時,框架會創建這個類的新的 KVO 子類,并將被觀察對象轉換為新子類的對象。在這個 KVO 特殊子類中, Cocoa 創建觀察屬性的 setter ,大致工作原理如下:
- (void)setNow:(NSDate *)aDate {
[self willChangeValueForKey:@"now"];
[super setValue:aDate forKey:@"now"];
[self didChangeValueForKey:@"now"];
}
這種繼承和方法注入是在運行時而不是編譯時實現的。這就是正確命名如此重要的原因。只有在使用KVC命名約定時,KVO才能做到這一點。
KVO 在實現中通過 isa 混寫(isa-swizzling) 把這個對象的 isa 指針 ( isa 指針告訴 Runtime 系統這個對象的類是什么 ) 指向這個新創建的子類,對象就神奇的變成了新創建的子類的實例。
IBOutlet連出來的視圖屬性為什么可以被設置成weak?
使用storyboard(xib不行)創建的vc,會有一個叫_topLevelObjectsToKeepAliveFromStoryboard的私有數組強引用所有top level的對象,所以這時即便outlet聲明成weak也沒關系
dispatch_barrier_async的作用是什么?
在并行隊列中,為了保持某些任務的順序,需要等待一些任務完成后才能繼續進行,使用 barrier 來等待之前任務完成,避免數據競爭等問題。 dispatch_barrier_async 函數會等待追加到Concurrent Dispatch Queue并行隊列中的操作全部執行完之后,然后再執行 dispatch_barrier_async 函數追加的處理,等 dispatch_barrier_async 追加的處理執行結束之后,Concurrent Dispatch Queue才恢復之前的動作繼續執行。
如何手動觸發一個value的KVO
- 自動觸發是指類似這種場景:在注冊 KVO 之前設置一個初始值,注冊之后,設置一個不一樣的值,就可以觸發了。
- 鍵值觀察通知依賴于 NSObject 的兩個方法: willChangeValueForKey: 和 didChangevlueForKey: 。在一個被觀察屬性發生改變之前, willChangeValueForKey: 一定會被調用,這就 會記錄舊的值。而當改變發生后, observeValueForKey:ofObject:change:context: 會被調用,繼而 didChangeValueForKey: 也會被調用。如果可以手動實現這些調用,就可以實現“手動觸發”了。
RunLoop基本概念
為什么引入RunLoop機制
- 一個線程一次只能執行一個任務,執行完成后線程就會退出。RunLoop可以讓線程隨時處理事件但不退出。
- 引入RunLoop機制目的是利用RunLoop機制的特點實現整體省點的效果,并且讓系統和應用可以流暢的運行,提高響應速度,達到極致的用戶體驗
RunLoop的本質
- 進程是一家工廠,線程是一個流水線,RunLoop就是流水線上的主管;當工廠接到商家的訂單分配給這個流水線時,RunLoop就啟動這個流水線,讓流水線動起來,生產產品;當產品生產完畢時,RunLoop就會暫時停下流水線,節約資源。RunLoop管理流水線,流水線才不會因為無所事事被工廠銷毀;而不需要流水線時,就會辭退RunLoop這個主管,即退出線程,把所有資源釋放。
- RunLoop實質上是一個對象,這個對象管理了其需要處理的事情和消息,并提供了入口函數來執行Event Loop 的邏輯。線程執行這個函數后,會一直處于這個函數內部:接受消息 -> 等待 -> 執行 的循環中,知道這個循環結束。
RunLoop的特性
- 主線程的RunLoop在應用啟動的時候就會自動創建
- 其他線程則需要再該線程下自己啟動
- 不能直接創建RunLoop
- RunLoop并不是線程安全的,所以需要在其他線程上調用當前線程的RunLoop
- RunLoop負責管理autorelease pools
- RunLoop負責處理消息事件,即輸入源事件和計時器事件
RunLoop與線程的關系
線程和RunLoop之間是一一對應的,其關系是保存在一個全局的Dictionary里。線程剛創建時并沒有RunLoop,如果不主動獲取,一直不會有。RunLoop的創建時發生在第一次獲取時。
RunLoop的內部組件
一個RunLoop可以包含多個Mode,每個Mode包含多個Source、Timer、Observer。
image.png
RunLoop 實現邏輯分析
- 實際上 RunLoop 其內部是一個 do-while 循環。RunLoop的核心就是一個MachMessage的調用過程,RunLoop調用這個函數去接收消息,如果沒有別人發送port消息過來,RunLoop會進入休眠狀態。內核會將線程置于等待狀態,這個時候whild循環是停止的,相比于一直運行的while循環,會很節省CPU資源,進而達到省電的目的。
- 因為Source1注冊了MachPort端口,當有Source1事件進來的時候會通過這個port端口喚醒RunLoop繼續執行,當計時器到了時候也會喚醒RunLoop,RunLoop喚醒后會先通過Observer 當前已經處于喚醒狀態,之后會先執行Source1事件,執行完成后再執行timer、Source0。執行完所有的Source0事件后,如果有Source1事件則執行Source1,如果沒有則通知Observe 進入到休眠狀態。完成一個循環。
總結
RunLoop是一個do-while 循環,又不是一個do-while 循環。他的工作模式是一個循環,但是他基于mach_port和mach_msg的 休眠\喚醒 機制確保了他可以在無任務的時候休眠,有任務的時候及時喚醒,相比于一個普通循環,不會空轉,不會浪費系統資源。RunLoop又通過不同的工作mode隔離了不同的事件源,使他們的工作互不影響。這才是RunLoop實現省電,流暢,響應速度快,用戶體驗好的根本原因;進而基于RunLoop的組件如計時器、GCD、界面更新、自動釋放池能高效運轉的根本原因。
RunLoopModel
- kCFRunLoopDefaultMode: App的默認 Mode,通常主線程是在這個 Mode 下運行的
- UITrackingRunLoopMode: 界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
- UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用
- GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,通常用不到
- kCFRunLoopCommonModes: 這是一個占位的 Mode,沒有實際作用
為什么引入Runloop機制,有什么作用或者好處?
引入RunLoop機制的目的是利用RunLoop機制的特點實現整體省電的效果,并且讓系統和應用可以流暢的運行,提高響應速度,達到極致用戶體驗。
為什么省電
主要有兩點:1.因為不做任何操作的時候,主線程RunLoop會處于退出狀態,不會執行任何空轉邏輯,不執行代碼自然不消耗GPU資源,自然省電。2.RunLoop提供一種班車制,限制如頁面刷新等任務的頻率,一次RunLoop只執行一次,防止多次重復執行代碼帶來的性能損耗。
為什么可以流程運行
- 一個app流暢與否的決定性因素是主線程的阻塞率,在iOS系統中runloop每秒執行60次,理論上主線程runloop達到55幀以上的刷新頻率用戶就感覺不到卡頓。
- Mode機制,同一時間只執行一個Mode內的Source或者Timer,比如拖動的時候只指定拖動Mode,其他Mode 如Default Mode中的源不會被執行,確保了高速滑動的時候不會有其他邏輯阻礙主線程刷新。
- Runloop做的是管理視圖刷新頻率,防止重復運算。由于視圖更新必須在主線程,視圖的重布局和重繪都會占用主線程的執行時間,一次Runloop循環只執行一次可以最大限度的防止重復運算導致的計算浪費
- 管理核心動畫。核心動畫有三個樹,其中render tree 是私有的,應用開發無法訪問到。render tree在專用的render server 進程中執行,是真正用來渲染動畫的地方,線程優先級高于主線程。所以即使app主線程阻塞,也不會影響到動畫的繪制工作。既節省了主線程的計算資源,又使動畫可以流暢的執行
- 支持異步方法調用,將耗時操作分發到子線程中進行。RunLoop是performSelector的基礎設施。我們使用 performSelector:onThread: 或者 performSelecter:afterDelay: 時,實際上系統會創建一個Timer并添加到當前線程的RunLoop中。
如何提高響應速度
當發生系統事件時,如觸碰事件,系統通過Mach Port發送Mach消息主動喚醒RunLoop。Mach是搶占式操作系統內核,Mach系統IPC機制就是依靠消息機制實現的,所以效率非常高。
iOS中的事件的產生和傳遞
- 觸摸事件的傳遞是從父控件傳遞子控件
- 也就是UIApplication ->window ->尋找處理事件最合適的view
如何找到最合適的控件來處理事件
- 首先判斷主窗口(keyWindow)自己能否接受觸摸事件
- 判斷觸摸事件是否在自己身上
- 子控件數組中沖后往前遍歷子控件,重復前面的兩個步驟(所謂從后往前遍歷子控件,就是首先查找子控件數組中最后一個元素,然后執行1、2步驟)
- view,比如叫做fitView,那么會把這個事件交給這個fitView,再遍歷這個fitView的子控件,直至沒有更合適的view為止
- 如果沒有符合條件的子控件,那么就認為自己最合適處理這個事件,也就是自己的最合適的view
注意:之所以會采取從后往前遍歷子控件的方式尋找最合適的view只是為了做一些循環優化。因為相比較之下,后添加的view在上面,降低循環次數。
UIView不能接受觸摸事件的三種情況
- 不允許交互:userInteractionEnabled=NO
- 隱藏:如果把父控件隱藏,那么子控件也會隱藏,隱藏的控件不能接受事件
- 透明度:如果設置一個控件的透明度<0.01,會直接影響子控件的透明度。alpha:0.0~0.01為透明
總結
- 點擊一個UIView或產生一個觸摸事件,這個觸摸事件A會被添加到由UIApplication管理的事件隊列中
- UIApplication會從事件隊列中取出最前面的事件(此處假設為觸摸事件),把時間A傳遞給應用程序的主窗口
- 窗口會在視圖層次結構中找到一個最合適的視圖來處理觸摸事件
響應者鏈
響應者鏈條:在iOS程序中無論是最后面的UIWindow還是最前面的某個按鈕,它們的擺放是有前后關系的,一個控件可以放到另一個控件上面或下面,那么用戶點擊某個控件時是觸發上面的控件還是下面的控件呢,這種先后關系構成一個鏈條就叫“響應者鏈”。也可以說,響應者鏈是由多個響應者對象連接起來的鏈條。在iOS中響應者鏈的關系可以用下圖表示:
image.png
響應者鏈的事件傳遞過程
- 當一個事件發生后,事件會從父控件傳給子控件,也就是說由UIApplication -> UIWindow -> UIView -> initial view,以上就是事件傳遞,即尋找最合適的view過程
- 接下來是事件的響應。首先看initial view能否處理這個事件。如果不能則會將事件傳遞給其上級視圖;如果上級視圖仍然無法處理則繼續往上傳遞;一直傳遞到視圖控制器view controller,首先判斷視圖控制器的根視圖view是否能處理此事件;如果不能則接著判斷該視圖控制器能否處理此事件,如果還是不能,則繼續往上傳遞;一直到window,如果window還是不能處理此事件則繼續交給UIApplication處理,如果最后UIApplication還是不能處理此事件則將其丟棄
- 在事件的響應中,如果某個空間實現了touches...方法,則這個事件將由該空間來接受,如果調用了[super touches...];就會將事件順著響應者鏈條往上傳遞,傳遞給上一個響應者;接著就會調用上一個響應者的touches...方法
git add 和 git stage 有什么區別
在回答這個問題之前需要先了解 git 倉庫的三個組成部分:工作區(Working Directory)、暫存區(Stage)和歷史記錄區(History)
- 工作區:在 git 管理下的正常目錄都算是工作區,我們平時的編輯工作都是在工作區完成
- 暫存區:臨時區域。里面存放將要提交文件的快照
歷史記錄區:git commit 后的記錄區
然后是這三個區的轉換關系以及轉換所使用的命令:
image.png
然后我們就可以來說一下 git add 和 git stage 了。其實,他們兩是同義的,所以,驚不驚喜,意不意外?
iOS NSDictionary(字典)~實現原理
- NSDictionary(字典)是使用 hash表來實現key和value之間的映射和存儲的, hash函數設計的好壞影響著數據的查找訪問效率。
- Objective-C 中的字典 NSDictionary 底層其實是一個哈希表,實際上絕大多數語言中字典都通過哈希表實現,
哈希的原理
哈希概念:哈希表的本質是一個數組,數組中每一個元素稱為一個箱子(bin),箱子中存放的是鍵值對。
哈希表的存儲過程
- 根據 key 計算出它的哈希值 h
- 假設箱子的個數為 n,那么這個鍵值對應該放在第 (h % n) 個箱子中
- 如果該箱子中已經有了鍵值對,就使用開放尋址法或者拉鏈法解決沖突
iOS編譯過程的原理和應用
編譯器前端
編譯器前端的任務是進行:語法分析,語義分析,生成中間代碼(intermediate representation )。在這個過程中,會進行類型檢查,如果發現錯誤或者警告會標注出來在哪一行。
編譯器后端
編譯器后端會進行機器無關的代碼優化,生成機器語言,并且進行機器相關的代碼優化。
NSURLSession & NSURLConnection
- 使用現狀
NSURLSession是NSURLConnection 的替代者,在2013年蘋果全球開發者大會(WWDC2013)隨ios7一起發布,是對NSURLConnection進行了重構優化后的新的網絡訪問接口- 2.普通任務和上傳
NSURLSession針對下載/上傳等復雜的網絡操作提供了專門的解決方案,針對普通、上傳和下載分別對應三種不同的網絡請求任務:NSURLSessionDataTask, NSURLSessionUploadTask和NSURLSessionDownloadTask.。創建的task都是掛起狀態,需要resume才能執行。- 3.下載任務方式
NSURLConnection下載文件時,先將整個文件下載到內存,然后再寫入沙盒,如果文件比較大,就會出現內存暴漲的情況。而使用NSURLSessionUploadTask下載文件,會默認下載到沙盒中的tem文件夾中,不會出現內存暴漲的情況,但在下載完成后會將tem中的臨時文件刪除,需要在初始化任務方法時,在completionHandler回調中增加保存文件的代碼。- 4.請求方法的控制
NSURLConnection實例化對象,實例化開始,默認請求就發送(同步發送),不需要調用start方法。而cancel 可以停止請求的發送,停止后不能繼續訪問,需要創建新的請求。
NSURLSession有三個控制方法,取消(cancel),暫停(suspend),繼續(resume),暫停后可以通過繼續恢復當前的請求任務。- 5.斷點續傳的方式
NSURLConnection進行斷點下載,通過設置訪問請求的HTTPHeaderField的Range屬性,開啟運行循環,NSURLConnection的代理方法作為運行循環的事件源,接收到下載數據時代理方法就會持續調用,并使用NSOutputStream管道流進行數據保存。
NSURLSession進行斷點下載,當暫停下載任務后,如果 downloadTask (下載任務)為非空,調用 cancelByProducingResumeData:(void (^)(NSData *resumeData))completionHandler 這個方法,這個方法接收一個參數,完成處理代碼塊,這個代碼塊有一個 NSData 參數 resumeData,如果 resumeData 非空,我們就保存這個對象到視圖控制器的 resumeData 屬性中。在點擊再次下載時,通過調用 [ [self.session downloadTaskWithResumeData:self.resumeData]resume]方法進行繼續下載操作。
- 配置信息
NSURLSession的構造方法(sessionWithConfiguration:delegate:delegateQueue)中有一個 NSURLSessionConfiguration類的參數可以設置配置信息,其決定了cookie,安全和高速緩存策略,最大主機連接數,資源管理,網絡超時等配置。NSURLConnection不能進行這個配置,相比于 NSURLConnection 依賴于一個全局的配置對象,缺乏靈活性而言,NSURLSession 有很大的改進了。