聊一聊京東七鮮一面總結(jié)
文末本文轉(zhuǎn)載自微信公眾號「Swift社區(qū)」,作者朱浦睿 。轉(zhuǎn)載本文請聯(lián)系Swift社區(qū)公眾號。
1. http 鏈接到斷開的過程?
第一步:TCP建立連接:三次握手
HTTP 是應(yīng)用層協(xié)議,他的工作還需要數(shù)據(jù)層協(xié)議的支持,最常與它搭配的就是 TCP 協(xié)議(應(yīng)用層、數(shù)據(jù)層是 OSI 七層模型中的,以后有機(jī)會(huì)會(huì)說到的)。TCP 協(xié)議稱為數(shù)據(jù)傳輸協(xié)議,是可靠傳輸,面向連接的,并且面向字節(jié)流的。
面向連接:通信之前先建立連接,確保雙方在線??煽總鬏敚涸诰W(wǎng)絡(luò)正常的情況下,數(shù)據(jù)不會(huì)丟失。面向字節(jié)流:傳輸靈活,但是 TCP 的傳輸存在粘包問題,沒有明顯的數(shù)據(jù)約定。
在正式發(fā)送請求之前,需要先建立 TCP 連接。建立 TCP 連接的過程簡單地來說就是客戶端和服務(wù)端之間發(fā)送三次消息來確保連接的建立,這個(gè)過程稱為三次握手。
第二步:瀏覽器發(fā)送請求命令
TCP 連接建立完成后,客戶端就可以向服務(wù)端發(fā)送請求報(bào)文來請求了
請求報(bào)文分為請求行、請求頭、空行、請求體,服務(wù)端通過請求行和請求頭中的內(nèi)容獲取客戶端的信息,通過請求體中的數(shù)據(jù)獲取客戶端的傳遞過來的數(shù)據(jù)。
第三步:應(yīng)答響應(yīng)
在接收到客戶端發(fā)來的請求報(bào)文并確認(rèn)完畢之后。服務(wù)端會(huì)向客戶端發(fā)送響應(yīng)報(bào)文
響應(yīng)報(bào)文是有狀態(tài)行、響應(yīng)頭、空行和響應(yīng)體組成,服務(wù)端通過狀態(tài)行和響應(yīng)頭告訴客戶端請求的狀態(tài)和如何對數(shù)據(jù)處理等信息,真正的數(shù)據(jù)則在響應(yīng)體中傳輸給客戶端。
第四步:斷開 TCP 連接
當(dāng)請求完成后,還需要斷開 tcp 連接,斷開的過程
斷開的過程簡單地說就算客戶端和服務(wù)端之間發(fā)送四次信息來確保連接的斷開,所以稱為四次揮手。
延伸:
一、單向請求 HTTP 請求是單向的,是只能由客戶端發(fā)起請求,由服務(wù)端響應(yīng)的請求-響應(yīng)模式。(如果你需要雙向請求,可以用 socket)
二、基于 TCP 協(xié)議 HTTP 是應(yīng)用層協(xié)議,所以其數(shù)據(jù)傳輸部分是基于 TCP 協(xié)議實(shí)現(xiàn)的。
三、無狀態(tài) HTTP 請求是無狀態(tài)的,即沒有記憶功能,不能獲取之前請求或響應(yīng)的內(nèi)容。起初這種簡單的模式,能夠加快處理速度,保證協(xié)議的穩(wěn)定,但是隨著應(yīng)用的發(fā)展,這種無狀態(tài)的模式會(huì)使我們的業(yè)務(wù)實(shí)現(xiàn)變得麻煩,比如說需要保存用戶的登錄狀態(tài),就得專門使用數(shù)據(jù)庫來實(shí)現(xiàn)。于是乎,為了實(shí)現(xiàn)狀態(tài)的保持,引入了 Cookie 技術(shù)來管理狀態(tài)。
四、無連接 HTTP 協(xié)議不能保存連接狀態(tài),每次連接只處理一個(gè)請求,用完即斷,從而達(dá)到節(jié)約傳輸時(shí)間、提高并發(fā)性。在 TCP 連接斷開之后,客戶端和服務(wù)端就像陌生人一樣,下次再發(fā)送請求,就得重新建立連接了。有時(shí)候,當(dāng)我們需要發(fā)送一段頻繁的請求時(shí),這種無連接的狀態(tài)反而會(huì)耗費(fèi)更多的請求時(shí)間(因?yàn)榻⒑蛿嚅_連接本身也需要時(shí)間),于是乎,HTTP1.1 中提出了持久連接的概念,可以在請求頭中設(shè)置 Connection: keep-alive 來實(shí)現(xiàn)。
2. 深拷貝、淺拷貝
深拷貝、淺拷貝實(shí)例說明?
深拷貝:是對對象本身的拷貝;淺拷貝:是對指針的拷貝;
在 oc 中父類的指針可以指向子類的對象,這是多態(tài)的一個(gè)特性 聲明一個(gè) NSString 對象,讓它指向一個(gè) NSMutableString 對象,這一點(diǎn)是完全可以的,因?yàn)?NSMutableString 的父類就是 NSString。NSMutableString 是一個(gè)可以改變的對象,如果我們用 strong 修飾,NSString 對象強(qiáng)引用了 NSMutableString 對象。假如我們在其他的地方修改了這個(gè) NSMutableString 對象,那么 NSString 的值會(huì)隨之改變。
關(guān)于copy修飾相關(guān)
1、對 NSString 進(jìn)行 copy -> 這是一個(gè)淺拷貝,但是因?yàn)槭遣豢勺儗ο?,后期值也不?huì)改變;
2、對 NSString 進(jìn)行 mutableCopy -> 這是一個(gè)深拷貝,但是拷貝出來的是一個(gè)可變的對象 NSMutableString;
3、對 NSMutableString 進(jìn)行 copy -> 這是一個(gè)深拷貝,拷貝出來一個(gè)不可變的對象;
4、對 NSmutableString 進(jìn)行 mutableCopy -> 這是一個(gè)深拷貝,拷貝出來一個(gè)可變的對象;
總結(jié):
對對象進(jìn)行 mutableCopy,不管對象是可變的還是不可變的都是深拷貝,并且拷貝出來的對象都是可變的;
對對象進(jìn)行 copy,copy 出來的都是不可變的。
對于系統(tǒng)的非容器類對象,我們可以認(rèn)為,如果對一不可變對象復(fù)制,copy 是指針復(fù)制(淺拷貝)和 mutableCopy 就是對象復(fù)制(深拷貝)。如果是對可變對象復(fù)制,都是深拷貝,但是 copy 返回的對象是不可變的。
指 NSArray,NSDictionary 等。對于容器類本身,上面討論的結(jié)論也是適用的,需要探討的是復(fù)制后容器內(nèi)對象的變化。
- //copy返回不可變對象,mutablecopy返回可變對象
- NSArray *array1 = [NSArray arrayWithObjects:@"a",@"b",@"c",nil];
- NSArray *arrayCopy1 = [array1 copy];
- //arrayCopy1是和array同一個(gè)NSArray對象(指向相同的對象),包括array里面的元素也是指向相同的指針
- NSLog(@"array1 retain count: %d",[array1 retainCount]);
- NSLog(@"array1 retain count: %d",[arrayCopy1 retainCount]);
- NSMutableArray *mArrayCopy1 = [array1 mutableCopy];
mArrayCopy1 是 array1 的可變副本,指向的對象和 array1 不同,但是其中的元素和 array1 中的元素指向的是同一個(gè)對象。
mArrayCopy1 還可以修改自己的對象 [mArrayCopy1 addObject:@"de"];
[mArrayCopy1 removeObjectAtIndex:0]; array1 和 arrayCopy1 是指針復(fù)制,而 mArrayCopy1 是對象復(fù)制,mArrayCopy1 還可以改變期內(nèi)的元素:刪除或添加。但是注意的是,容器內(nèi)的元素內(nèi)容都是指針復(fù)制。
- NSArray *mArray1 = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
- NSArray *mArrayCopy2 = [mArray1 copy];
- NSMutableArray *mArrayMCopy1 = [mArray1 mutableCopy];
- NSMutableString *testString = [mArray1 objectAtIndex:0];
- [testString appendString:@" tail"];
- NSLog(@"%@-%@-%@",mArray1,mArrayMCopy1,mArrayCopy2);
- 結(jié)果:mArray1,mArrayMCopy1,mArrayCopy2三個(gè)數(shù)組的首元素都發(fā)生了變化!
3. load 和 initialize 區(qū)別
load 方法和 initialize 方法區(qū)別,以及在子類、父類、分類中調(diào)用順序?
+(void)load
1、+load 方法加載順序:父類> 子類> 分類 (load 方法都會(huì)加載)注意:(如果分類中有 A, B,順序要看 A, B 加入工程中順序) ,可能結(jié)果:( 父類> 子類> 分類A> 分類B ) 或者( 父類> 子類> 分類B> 分類A )
2、+load 方法不會(huì)被覆蓋(比如有父類,子類,分類A,分類B,這四個(gè) load 方法都會(huì)加載)。
3、+load 方法調(diào)用在 main函數(shù)前
+(void)initialize
1、分類 (子類沒有 initialize 方法,父類存在或者沒有 1initialize 方法)
2、分類> 子類 (多個(gè)分類就看編譯順序,只有存在一個(gè))
3、父類> 子類 (分類沒有 initialize 方法)
4、父類 (子類,分類都沒有 initialize 方法)
總結(jié) +initialize:
1、當(dāng)調(diào)用子類的 + initialize 方法時(shí)候,先調(diào)用父類的,如果父類有分類, 那么分類的 + initialize 會(huì)覆蓋掉父類的
2、分類的 + initialize 會(huì)覆蓋掉父類的
3、子類的 + initialize 不會(huì)覆蓋分類的
4、父類的 + initialize 不一定會(huì)調(diào)用, 因?yàn)橛锌赡芨割惖姆诸愔貙懥怂?/p>
5、發(fā)生在main函數(shù)后。
4. 同名方法調(diào)用順序
同名方法在子類、父類、分類的調(diào)用順序?
load,initialize方法調(diào)用源碼分析[1]
注意:+load 方法是根據(jù)方法地址直接調(diào)用,并不是經(jīng)過 objc_msgSend 函數(shù)調(diào)用(通過 isa 和 superclass 找方法),所以不會(huì)存在方法覆蓋的問題。
5. 事件響應(yīng)鏈
事件響應(yīng)鏈(同一個(gè)控制器有三個(gè)view,如何判斷是否擁有相同的父視圖)
iOS 系統(tǒng)檢測到手指觸摸( Touch )操作時(shí)會(huì)將其打包成一個(gè) UIEvent 對象,并放入當(dāng)前活動(dòng) Application 的事件隊(duì)列,單例的 UIApplication 會(huì)從事件隊(duì)列中取出觸摸事件并傳遞給單例的 UIWindow 來處理,UIWindow 對象首先會(huì)使用 hitTest:withEvent: 方法尋找此次 Touch 操作初始點(diǎn)所在的視圖(View),即需要將觸摸事件傳遞給其處理的視圖,這個(gè)過程稱之為 hit-test view。
UIAppliction --> UIWiondw -->遞歸找到最適合處理事件的控件-->控件調(diào)用 touches 方法-->判斷是否實(shí)現(xiàn) touches 方法-->沒有實(shí)現(xiàn)默認(rèn)會(huì)將事件傳遞給上一個(gè)響應(yīng)者-->找到上一個(gè)響應(yīng)者。
UIResponder 是所有響應(yīng)對象的基類,在 UIResponder 類中定義了處理上述各種事件的接口。我們熟悉的 UIApplication、 UIViewController、 UIWindow 和所有繼承自 UIView 的 UIKit 類都直接或間接的繼承自 UIResponder,所以它們的實(shí)例都是可以構(gòu)成響應(yīng)者鏈的響應(yīng)者對象。
- UIResponder *nextResponder = gView.nextResponder;
- NSMutableString *p = [NSMutableString stringWithString:@"--"];
- while (nextResponder) {
- NSLog(@"%@%@", p, NSStringFromClass([nextResponder class]));
- [p appendString:@"--"];
- nextResponder = nextResponder.nextResponder;
- }
如果有父視圖則 nextResponder 指向父視圖如果是控制器根視圖則指向控制器;
控制器如果在導(dǎo)航控制器中則指向?qū)Ш娇刂破鞯南嚓P(guān)顯示視圖最后指向?qū)Ш娇刂破?
如果是根控制器則指向 UIWindow;
UIWindow 的 nexResponder 指向 UIApplication 最后指向 AppDelegate。
6.TCP丟包
TCP 會(huì)不會(huì)丟包?該怎么處理?網(wǎng)絡(luò)斷開會(huì)斷開鏈接還是一直等待,如果一直網(wǎng)絡(luò)斷開呢?
TCP 在不可靠的網(wǎng)絡(luò)上實(shí)現(xiàn)可靠的傳輸,必然會(huì)有丟包。TCP 是一個(gè)“流”協(xié)議,一個(gè)詳細(xì)的包將會(huì)被 TCP 拆分為好幾個(gè)包上傳,也是將會(huì)把小的封裝成大的上傳,這就是說 TCP 粘包和拆包難題。
TCP丟包總結(jié)[2]
7.自動(dòng)釋放池
自動(dòng)釋放池創(chuàng)建和釋放的時(shí)機(jī),在子線程是什么時(shí)候創(chuàng)建釋放的?
默認(rèn)主線程的運(yùn)行循環(huán)(runloop)是開啟的,子線程的運(yùn)行循環(huán)(runloop)默認(rèn)是不開啟的,也就意味著子線程中不會(huì)創(chuàng)建 autoreleasepool,所以需要我們自己在子線程中創(chuàng)建一個(gè)自動(dòng)釋放池。(子線程里面使用的類方法都是 autorelease,就會(huì)沒有池子可釋放,也就意味著后面沒有辦法進(jìn)行釋放,造成內(nèi)存泄漏。)
在主線程中如果產(chǎn)生事件那么 runloop 才回去創(chuàng)建 autoreleasepool,通過這個(gè)道理我們就知道為什么子線程中不會(huì)創(chuàng)建自動(dòng)釋放池了,因?yàn)樽泳€程的 runloop 默認(rèn)是關(guān)閉的,所以他不會(huì)自動(dòng)創(chuàng)建 autoreleasepool,需要我們手動(dòng)添加。
如果你生成一個(gè)子線程的時(shí)候,要在線程開始執(zhí)行的時(shí)候,盡快創(chuàng)建一個(gè)自動(dòng)釋放池,否則會(huì)內(nèi)存泄露。因?yàn)樽泳€程無法訪問主線程的自動(dòng)釋放池。
8.計(jì)算機(jī)編譯流程
源文件: 載入.h、.m、.cpp 等文件
預(yù)處理: 替換宏,刪除注釋,展開頭文件,產(chǎn)生 .i 文件
編譯: 將 .i 文件轉(zhuǎn)換為匯編語言,產(chǎn)生 .s 文件
匯編: 將匯編文件轉(zhuǎn)換為機(jī)器碼文件,產(chǎn)生 .o 文件
鏈接: 對 .o 文件中引用其他庫的地方進(jìn)行引用,生成最后的可執(zhí)行文件
dyld加載流程[3]
作者:朱浦睿、林一、怪物先生
參考資料
[1]load,initialize方法調(diào)用源碼分析: https://www.jianshu.com/p/e5c89e9045cf
[2]TCP丟包總結(jié): https://blog.csdn.net/weixin_41563161/article/details/105310459
[3]dyld加載流程: https://www.jianshu.com/p/db765ff4e36a