內(nèi)存管理
1.什么情況使用weak關鍵字,相比assign有什么不同滔吠?
-
什么情況使用 weak 關鍵字纲菌?
在 ARC 中,在有可能出現(xiàn)循環(huán)引用的時候,往往要通過讓其中一端使用 weak 來解決,比如: delegate 代理屬性
自身已經(jīng)對它進行一次強引用,沒有必要再強引用一次,此時也會使用 weak,自定義 IBOutlet 控件屬性一般也使用 weak;當然,也可以使用strong。在下文也有論述:《IBOutlet連出來的視圖屬性為什么可以被設置成weak?》
-
不同點:
weak 此特質(zhì)表明該屬性定義了一種“非擁有關系” (nonowning relationship)搓逾。為這種屬性設置新值時,設置方法既不保留新值矾踱,也不釋放舊值。此特質(zhì)同assign類似, 然而在屬性所指的對象遭到摧毀時,屬性值也會清空(nil out)庇麦。 而 assign 的“設置方法”只會執(zhí)行針對“純量類型” (scalar type,例如 CGFloat 或 NSlnteger 等)的簡單賦值操作喜德。
assign 可以用非 OC 對象,而 weak 必須用于 OC 對象
2.如何讓自己的類用copy修飾符山橄?如何重寫帶copy關鍵字的setter?
-
若想令自己所寫的對象具有拷貝功能住诸,則需實現(xiàn) NSCopying 協(xié)議驾胆。如果自定義的對象分為可變版本與不可變版本,那么就要同時實現(xiàn) NSCopying 與 NSMutableCopying 協(xié)議贱呐。
具體步驟:
需聲明該類遵從 NSCopying 協(xié)議
實現(xiàn) NSCopying 協(xié)議丧诺。該協(xié)議只有一個方法:
- (id)copyWithZone:(NSZone *)zone;
注意:一提到讓自己的類用 copy 修飾符,我們總是想覆寫copy方法奄薇,其實真正需要實現(xiàn)的卻是 “copyWithZone” 方法驳阎。
-
重寫帶 copy 關鍵字的 setter,例如:
- (void)setName:(NSString *)name { //[_name release]; _name = [name copy]; }
3.深拷貝與淺拷貝
淺拷貝只是對指針的拷貝馁蒂,拷貝后兩個指針指向同一個內(nèi)存空間呵晚,深拷貝不但對指針進行拷貝,而且對指針指向的內(nèi)容進行拷貝沫屡,經(jīng)深拷貝后的指針是指向兩個不同地址的指針饵隙。
當對象中存在指針成員時,除了在復制對象時需要考慮自定義拷貝構造函數(shù)沮脖,還應該考慮以下兩種情形:
當函數(shù)的參數(shù)為對象時金矛,實參傳遞給形參的實際上是實參的一個拷貝對象,系統(tǒng)自動通過拷貝構造函數(shù)實現(xiàn)勺届;
當函數(shù)的返回值為一個對象時驶俊,該對象實際上是函數(shù)內(nèi)對象的一個拷貝,用于返回函數(shù)調(diào)用處免姿。
copy方法:如果是非可擴展類對象饼酿,則是淺拷貝。如果是可擴展類對象胚膊,則是深拷貝故俐。
mutableCopy方法:無論是可擴展類對象還是不可擴展類對象,都是深拷貝紊婉。
4.@property的本質(zhì)是什么购披?ivar、getter肩榕、setter是如何生成并添加到這個類中的
-
@property 的本質(zhì)是實例變量(ivar)+存取方法(access method = getter + setter),即 @property = ivar + getter + setter;
“屬性” (property)作為 Objective-C 的一項特性刚陡,主要的作用就在于封裝對象中的數(shù)據(jù)。 Objective-C 對象通常會把其所需要的數(shù)據(jù)保存為各種實例變量株汉。實例變量一般通過“存取方法”(access method)來訪問筐乳。其中,“獲取方法” (getter)用于讀取變量值乔妈,而“設置方法” (setter)用于寫入變量值蝙云。
-
ivar、getter路召、setter 是自動合成這個類中的
完成屬性定義后勃刨,編譯器會自動編寫訪問這些屬性所需的方法波材,此過程叫做“自動合成”(autosynthesis)。需要強調(diào)的是身隐,這個過程由編譯 器在編譯期執(zhí)行廷区,所以編輯器里看不到這些“合成方法”(synthesized method)的源代碼。除了生成方法代碼 getter贾铝、setter 之外隙轻,編譯器還要自動向類中添加適當類型的實例變量,并且在屬性名前面加下劃線垢揩,以此作為實例變量的名字玖绿。在前例中,會生成兩個實例變量叁巨,其名稱分別為 _firstName 與 _lastName斑匪。也可以在類的實現(xiàn)代碼里通過 @synthesize 語法來指定實例變量的名字.
5.@protocol和category中如何使用@property
在 protocol 中使用 property 只會生成 setter 和 getter 方法聲明,我們使用屬性的目的,是希望遵守我協(xié)議的對象能實現(xiàn)該屬性
category 使用 @property 也是只會生成 setter 和 getter 方法的聲明,如果我們真的需要給 category 增加屬性的實現(xiàn),需要借助于運行時的兩個函數(shù):objc_setAssociatedObject和objc_getAssociatedObject
6.簡要說一下@autoreleasePool的數(shù)據(jù)結構?锋勺?
簡單說是雙向鏈表秤标,每張鏈表頭尾相接,有 parent宙刘、child指針
每創(chuàng)建一個池子苍姜,會在首部創(chuàng)建一個 哨兵 對象,作為標記
最外層池子的頂端會有一個next指針。當鏈表容量滿了悬包,就會在鏈表的頂端衙猪,并指向下一張表。
7.BAD_ACCESS在什么情況下出現(xiàn)布近?
訪問了懸垂指針垫释,比如對一個已經(jīng)釋放的對象執(zhí)行了release、訪問已經(jīng)釋放對象的成員變量或者發(fā)消息撑瞧。 死循環(huán)
8.使用CADisplayLink棵譬、NSTimer有什么注意點?
CADisplayLink预伺、NSTimer會造成循環(huán)引用订咸,可以使用YYWeakProxy或者為CADisplayLink、NSTimer添加block方法解決循環(huán)引用
9.iOS內(nèi)存分區(qū)情況
-
棧區(qū)(Stack)
由編譯器自動分配釋放酬诀,存放函數(shù)的參數(shù)脏嚷,局部變量的值等
棧是向低地址擴展的數(shù)據(jù)結構,是一塊連續(xù)的內(nèi)存區(qū)域
-
堆區(qū)(Heap)
由程序員分配釋放
是向高地址擴展的數(shù)據(jù)結構瞒御,是不連續(xù)的內(nèi)存區(qū)域
-
全局區(qū)
全局變量和靜態(tài)變量的存儲是放在一塊的父叙,初始化的全局變量和靜態(tài)變量在一塊區(qū)域,未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域
程序結束后由系統(tǒng)釋放
-
常量區(qū)
常量字符串就是放在這里的
程序結束后由系統(tǒng)釋放
-
代碼區(qū)
存放函數(shù)體的二進制代碼
-
注:
在 iOS 中,堆區(qū)的內(nèi)存是應用程序共享的趾唱,堆中的內(nèi)存分配是系統(tǒng)負責的
系統(tǒng)使用一個鏈表來維護所有已經(jīng)分配的內(nèi)存空間(系統(tǒng)僅僅記錄涌乳,并不管理具體的內(nèi)容)
變量使用結束后,需要釋放內(nèi)存甜癞,OC 中是判斷引用計數(shù)是否為 0夕晓,如果是就說明沒有任何變量使用該空間,那么系統(tǒng)將其回收
當一個 app 啟動后带欢,代碼區(qū)运授、常量區(qū)烤惊、全局區(qū)大小就已經(jīng)固定乔煞,因此指向這些區(qū)的指針不會產(chǎn)生崩潰性的錯誤。而堆區(qū)和棧區(qū)是時時刻刻變化的(堆的創(chuàng)建銷毀柒室,棧的彈入彈出)渡贾,所以當使用一個指針指向這個區(qū)里面的內(nèi)存時,一定要注意內(nèi)存是否已經(jīng)被釋放雄右,否則會產(chǎn)生程序崩潰(也即是野指針報錯)
10.iOS內(nèi)存管理方式
-
Tagged Pointer(小對象)
Tagged Pointer 專門用來存儲小的對象空骚,例如 NSNumber 和 NSDate
Tagged Pointer 指針的值不再是地址了,而是真正的值擂仍。所以囤屹,實際上它不再是一個對象了,它只是一個披著對象皮的普通變量而已逢渔。所以肋坚,它的內(nèi)存并不存儲在堆中,也不需要 malloc 和 free
在內(nèi)存讀取上有著 3 倍的效率肃廓,創(chuàng)建時比以前快 106 倍
objc_msgSend 能識別 Tagged Pointer智厌,比如 NSNumber 的 intValue 方法,直接從指針提取數(shù)據(jù)
使用 Tagged Pointer 后盲赊,指針內(nèi)存儲的數(shù)據(jù)變成了 Tag + Data铣鹏,也就是將數(shù)據(jù)直接存儲在了指針中
-
NONPOINTER_ISA (指針中存放與該對象內(nèi)存相關的信息)
蘋果將 isa 設計成了聯(lián)合體,在 isa 中存儲了與該對象相關的一些內(nèi)存的信息哀蘑,原因也如上面所說诚卸,并不需要 64 個二進制位全部都用來存儲指針。isa 的結構:
// x86_64 架構 struct { uintptr_t nonpointer : 1; // 0:普通指針绘迁,1:優(yōu)化過惨险,使用位域存儲更多信息 uintptr_t has_assoc : 1; // 對象是否含有或曾經(jīng)含有關聯(lián)引用 uintptr_t has_cxx_dtor : 1; // 表示是否有C++析構函數(shù)或OC的dealloc uintptr_t shiftcls : 44; // 存放著 Class、Meta-Class 對象的內(nèi)存地址信息 uintptr_t magic : 6; // 用于在調(diào)試時分辨對象是否未完成初始化 uintptr_t weakly_referenced : 1; // 是否被弱引用指向 uintptr_t deallocating : 1; // 對象是否正在釋放 uintptr_t has_sidetable_rc : 1; // 是否需要使用 sidetable 來存儲引用計數(shù) uintptr_t extra_rc : 8; // 引用計數(shù)能夠用 8 個二進制位存儲時脊髓,直接存儲在這里 }; // arm64 架構 struct { uintptr_t nonpointer : 1; // 0:普通指針辫愉,1:優(yōu)化過,使用位域存儲更多信息 uintptr_t has_assoc : 1; // 對象是否含有或曾經(jīng)含有關聯(lián)引用 uintptr_t has_cxx_dtor : 1; // 表示是否有C++析構函數(shù)或OC的dealloc uintptr_t shiftcls : 33; // 存放著 Class将硝、Meta-Class 對象的內(nèi)存地址信息 uintptr_t magic : 6; // 用于在調(diào)試時分辨對象是否未完成初始化 uintptr_t weakly_referenced : 1; // 是否被弱引用指向 uintptr_t deallocating : 1; // 對象是否正在釋放 uintptr_t has_sidetable_rc : 1; // 是否需要使用 sidetable 來存儲引用計數(shù) uintptr_t extra_rc : 19; // 引用計數(shù)能夠用 19 個二進制位存儲時恭朗,直接存儲在這里 };
這里的 has_sidetable_rc 和 extra_rc屏镊,has_sidetable_rc 表明該指針是否引用了 sidetable 散列表,之所以有這個選項痰腮,是因為少量的引用計數(shù)是不會直接存放在 SideTables 表中的而芥,對象的引用計數(shù)會先存放在 extra_rc 中,當其被存滿時膀值,才會存入相應的 SideTables 散列表中棍丐,SideTables 中有很多張 SideTable,每個 SideTable 也都是一個散列表沧踏,而引用計數(shù)表就包含在 SideTable 之中歌逢。
-
散列表(引用計數(shù)表、弱引用表)
引用計數(shù)要么存放在 isa 的 extra_rc 中翘狱,要么存放在引用計數(shù)表中秘案,而引用計數(shù)表包含在一個叫 SideTable 的結構中,它是一個散列表潦匈,也就是哈希表阱高。而 SideTable 又包含在一個全局的 StripeMap 的哈希映射表中,這個表的名字叫 SideTables茬缩。
當一個對象訪問 SideTables 時:
首先會取得對象的地址赤惊,將地址進行哈希運算,與 SideTables 中 SideTable 的個數(shù)取余凰锡,最后得到的結果就是該對象所要訪問的 SideTable
在取得的 SideTable 中的 RefcountMap 表中再進行一次哈希查找未舟,找到該對象在引用計數(shù)表中對應的位置
如果該位置存在對應的引用計數(shù),則對其進行操作,如果沒有對應的引用計數(shù),則創(chuàng)建一個對應的 size_t 對象漾根,其實就是一個 uint 類型的無符號整型
弱引用表也是一張哈希表的結構,其內(nèi)部包含了每個對象對應的弱引用表 weak_entry_t魂角,而 weak_entry_t 是一個結構體數(shù)組,其中包含的則是每一個對象弱引用的對象所對應的弱引用指針智绸。
11.循環(huán)引用
循環(huán)引用的實質(zhì):多個對象相互之間有強引用野揪,不能釋放讓系統(tǒng)回收。
如何解決循環(huán)引用瞧栗?
1斯稳、避免產(chǎn)生循環(huán)引用,通常是將 strong 引用改為 weak 引用迹恐。 比如在修飾屬性時用weak 在block內(nèi)調(diào)用對象方法時挣惰,使用其弱引用,這里可以使用兩個宏
define WS(weakSelf) __weak __typeof(&*self)weakSelf = self; // 弱引用
define ST(strongSelf) __strong __typeof(&*self)strongSelf = weakSelf; //使用這個要先聲明weakSelf
還可以使用__block來修飾變量 在MRC下,__block不會增加其引用計數(shù)憎茂,避免了循環(huán)引用 在ARC下珍语,__block修飾對象會被強引用,無法避免循環(huán)引用竖幔,需要手動解除板乙。
2、在合適時機去手動斷開循環(huán)引用拳氢。 通常我們使用第一種募逞。
-
代理(delegate)循環(huán)引用屬于相互循環(huán)引用
delegate 是iOS中開發(fā)中比較常遇到的循環(huán)引用,一般在聲明delegate的時候都要使用弱引用 weak,或者assign,當然怎么選擇使用assign還是weak馋评,MRC的話只能用assign放接,在ARC的情況下最好使用weak,因為weak修飾的變量在釋放后自動指向nil栗恩,防止野指針存在
-
NSTimer循環(huán)引用屬于相互循環(huán)使用
在控制器內(nèi)透乾,創(chuàng)建NSTimer作為其屬性洪燥,由于定時器創(chuàng)建后也會強引用該控制器對象磕秤,那么該對象和定時器就相互循環(huán)引用了。 如何解決呢捧韵? 這里我們可以使用手動斷開循環(huán)引用: 如果是不重復定時器市咆,在回調(diào)方法里將定時器invalidate并置為nil即可。 如果是重復定時器再来,在合適的位置將其invalidate并置為nil即可
3蒙兰、block循環(huán)引用
一個簡單的例子:
@property (copy, nonatomic) dispatch_block_t myBlock;
@property (copy, nonatomic) NSString *blockString;
- (void)testBlock {
self.myBlock = ^() {
NSLog(@"%@",self.blockString);
};
}
由于block會對block中的對象進行持有操作,就相當于持有了其中的對象,而如果此時block中的對象又持有了該block芒篷,則會造成循環(huán)引用搜变。 解決方案就是使用__weak修飾self即可
__weak typeof(self) weakSelf = self;
self.myBlock = ^() {
NSLog(@"%@",weakSelf.blockString);
};
并不是所有block都會造成循環(huán)引用。 只有被強引用了的block才會產(chǎn)生循環(huán)引用 而比如dispatch_async(dispatch_get_main_queue(), ^{}),[UIView animateWithDuration:1 animations:^{}]這些系統(tǒng)方法等 或者block并不是其屬性而是臨時變量,即棧block
[self testWithBlock:^{
NSLog(@"%@",self);
}];
- (void)testWithBlock:(dispatch_block_t)block {
block();
}
還有一種場景针炉,在block執(zhí)行開始時self對象還未被釋放挠他,而執(zhí)行過程中,self被釋放了篡帕,由于是用weak修飾的殖侵,那么weakSelf也被釋放了,此時在block里訪問weakSelf時镰烧,就可能會發(fā)生錯誤(向nil對象發(fā)消息并不會崩潰拢军,但也沒任何效果)。 對于這種場景怔鳖,應該在block中對 對象使用__strong修飾茉唉,使得在block期間對 對象持有,block執(zhí)行結束后,解除其持有度陆。
__weak typeof(self) weakSelf = self;
self.myBlock = ^() {
__strong __typeof(self) strongSelf = weakSelf;
[strongSelf test];
};
消息傳遞的方式
1.KVC實現(xiàn)原理
KVC魏铅,鍵-值編碼,使用字符串直接訪問對象的屬性坚芜。
-
底層實現(xiàn)览芳,當一個對象調(diào)用setValue方法時,方法內(nèi)部會做以下操作:
1.檢查是否存在相應key的set方法鸿竖,如果存在沧竟,就調(diào)用set方法
2.如果set方法不存在,就會查找與key相同名稱并且?guī)聞澗€的成員屬性缚忧,如果有悟泵,則直接給成員屬性賦值
3.如果沒有找到_key,就會查找相同名稱的屬性key闪水,如果有就直接賦值
4.如果還沒找到糕非,則調(diào)用valueForUndefinedKey:和setValue:forUndefinedKey:方法
2.KVO的實現(xiàn)原理
KVO-鍵值觀察機制,原理如下:
1.當給A類添加KVO的時候球榆,runtime動態(tài)的生成了一個子類NSKVONotifying_A朽肥,讓A類的isa指針指向NSKVONotifying_A類,重寫class方法持钉,隱藏對象真實類信息
2.重寫監(jiān)聽屬性的setter方法衡招,在setter方法內(nèi)部調(diào)用了Foundation 的 _NSSetObjectValueAndNotify 函數(shù)
-
3._NSSetObjectValueAndNotify函數(shù)內(nèi)部
a) 首先會調(diào)用 willChangeValueForKey
b) 然后給屬性賦值
c) 最后調(diào)用 didChangeValueForKey
d) 最后調(diào)用 observer 的 observeValueForKeyPath 去告訴監(jiān)聽器屬性值發(fā)生了改變 .
4.重寫了dealloc做一些 KVO 內(nèi)存釋放
3.如何手動觸發(fā)KVO方法
手動調(diào)用willChangeValueForKey和didChangeValueForKey方法
鍵值觀察通知依賴于 NSObject 的兩個方法: willChangeValueForKey: 和 didChangeValueForKey。在一個被觀察屬性發(fā)生改變之前每强, willChangeValueForKey: 一定會被調(diào)用始腾,這就 會記錄舊的值。而當改變發(fā)生后空执, didChangeValueForKey 會被調(diào)用浪箭,繼而 observeValueForKey:ofObject:change:context: 也會被調(diào)用。如果可以手動實現(xiàn)這些調(diào)用辨绊,就可以實現(xiàn)“手動觸發(fā)”了
有人可能會問只調(diào)用didChangeValueForKey方法可以觸發(fā)KVO方法奶栖,其實是不能的,因為willChangeValueForKey: 記錄舊的值邢羔,如果不記錄舊的值驼抹,那就沒有改變一說了
4.通知和代理有什么區(qū)別
通知是觀察者模式,適合一對多的場景
代理模式適合一對一的反向傳值
通知耦合度低拜鹤,代理的耦合度高
5.block和delegate的區(qū)別
-
delegate運行成本低框冀,block的運行成本高
block出棧需要將使用的數(shù)據(jù)從棧內(nèi)存拷貝到堆內(nèi)存,當然對象的話就是加計數(shù)敏簿,使用完或者block置nil后才消除明也。delegate只是保存了一個對象指針宣虾,直接回調(diào),沒有額外消耗温数。就像C的函數(shù)指針绣硝,只多做了一個查表動作。
delegate更適用于多個回調(diào)方法(3個以上)撑刺,block則適用于1鹉胖,2個回調(diào)時。
6.為什么Block用copy關鍵字
Block在沒有使用外部變量時够傍,內(nèi)存存在全局區(qū)甫菠,然而,當Block在使用外部變量的時候冕屯,內(nèi)存是存在于棧區(qū)寂诱,當Block copy之后,是存在堆區(qū)的安聘。存在于棧區(qū)的特點是對象隨時有可能被銷毀痰洒,一旦銷毀在調(diào)用的時候,就會造成系統(tǒng)的崩潰浴韭。所以Block要用copy關鍵字丘喻。
多線程
1.進程與線程
-
進程:
1.進程是一個具有一定獨立功能的程序關于某次數(shù)據(jù)集合的一次運行活動,它是操作系統(tǒng)分配資源的基本單元.
2.進程是指在系統(tǒng)中正在運行的一個應用程序囱桨,就是一段程序的執(zhí)行過程,我們可以理解為手機上的一個app.
3.每個進程之間是獨立的仓犬,每個進程均運行在其專用且受保護的內(nèi)存空間內(nèi)嗅绰,擁有獨立運行所需的全部資源
-
線程
1.程序執(zhí)行流的最小單元舍肠,線程是進程中的一個實體.
2.一個進程要想執(zhí)行任務,必須至少有一條線程.應用程序啟動的時候,系統(tǒng)會默認開啟一條線程,也就是主線程
-
進程和線程的關系
1.線程是進程的執(zhí)行單元窘面,進程的所有任務都在線程中執(zhí)行
2.線程是 CPU 分配資源和調(diào)度的最小單位
3.一個程序可以對應多個進程(多進程),一個進程中可有多個線程,但至少要有一條線程
4.同一個進程內(nèi)的線程共享進程資源
2.什么是多線程翠语?
多線程的實現(xiàn)原理:事實上,同一時間內(nèi)單核的CPU只能執(zhí)行一個線程财边,多線程是CPU快速的在多個線程之間進行切換(調(diào)度)肌括,造成了多個線程同時執(zhí)行的假象。
如果是多核CPU就真的可以同時處理多個線程了酣难。
多線程的目的是為了同步完成多項任務谍夭,通過提高系統(tǒng)的資源利用率來提高系統(tǒng)的效率。
3.多線程的優(yōu)點和缺點
-
優(yōu)點:
能適當提高程序的執(zhí)行效率
能適當提高資源利用率(CPU憨募、內(nèi)存利用率)
-
缺點:
開啟線程需要占用一定的內(nèi)存空間(默認情況下紧索,主線程占用1M,子線程占用512KB)菜谣,如果開啟大量的線程珠漂,會占用大量的內(nèi)存空間晚缩,降低程序的性能
線程越多,CPU在調(diào)度線程上的開銷就越大
程序設計更加復雜:比如線程之間的通信媳危、多線程的數(shù)據(jù)共享
4.多線程的 并行 和 并發(fā) 有什么區(qū)別荞彼?
并行:充分利用計算機的多核,在多個線程上同步進行
并發(fā):在一條線程上通過快速切換待笑,讓人感覺在同步進行
5.iOS中實現(xiàn)多線程的幾種方案鸣皂,各自有什么特點?
NSThread 面向?qū)ο蟮哪乎澹枰绦騿T手動創(chuàng)建線程签夭,但不需要手動銷毀。子線程間通信很難椎侠。
GCD c語言第租,充分利用了設備的多核,自動管理線程生命周期我纪。比NSOperation效率更高慎宾。
NSOperation 基于gcd封裝,更加面向?qū)ο笄诚ぃ萭cd多了一些功能趟据。
6.多個網(wǎng)絡請求完成后執(zhí)行下一步
-
使用GCD的dispatch_group_t
創(chuàng)建一個dispatch_group_t
每次網(wǎng)絡請求前先dispatch_group_enter,請求回調(diào)后再dispatch_group_leave,enter和leave必須配合使用术健,有幾次enter就要有幾次leave汹碱,否則group會一直存在。
當所有enter的block都leave后荞估,會執(zhí)行dispatch_group_notify的block咳促。
NSString *str = @"http://xxxx.com/"; NSURL *url = [NSURL URLWithString:str]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSURLSession *session = [NSURLSession sharedSession]; dispatch_group_t downloadGroup = dispatch_group_create(); for (int i=0; i<10; i++) { dispatch_group_enter(downloadGroup); NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSLog(@"%d---%d",i,i); dispatch_group_leave(downloadGroup); }]; [task resume]; } dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{ NSLog(@"end"); });
-
使用GCD的信號量dispatch_semaphore_t
dispatch_semaphore信號量為基于計數(shù)器的一種多線程同步機制。如果semaphore計數(shù)大于等于1勘伺,計數(shù)-1跪腹,返回,程序繼續(xù)運行飞醉。如果計數(shù)為0冲茸,則等待。dispatch_semaphore_signal(semaphore)為計數(shù)+1操作,dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)為設置等待時間缅帘,這里設置的等待時間是一直等待轴术。
創(chuàng)建semaphore為0,等待钦无,等10個網(wǎng)絡請求都完成了逗栽,dispatch_semaphore_signal(semaphore)為計數(shù)+1,然后計數(shù)-1返回
NSString *str = @"http://xxxx.com/"; NSURL *url = [NSURL URLWithString:str]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSURLSession *session = [NSURLSession sharedSession]; dispatch_semaphore_t sem = dispatch_semaphore_create(0); for (int i=0; i<10; i++) { NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSLog(@"%d---%d",i,i); count++; if (count==10) { dispatch_semaphore_signal(sem); count = 0; } }]; [task resume]; } dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"end"); });
7.多個網(wǎng)絡請求順序執(zhí)行后執(zhí)行下一步
-
使用信號量semaphore
每一次遍歷铃诬,都讓其dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER)祭陷,這個時候線程會等待苍凛,阻塞當前線程,直到dispatch_semaphore_signal(sem)調(diào)用之后
NSString *str = @"http://www.reibang.com/p/6930f335adba"; NSURL *url = [NSURL URLWithString:str]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSURLSession *session = [NSURLSession sharedSession]; dispatch_semaphore_t sem = dispatch_semaphore_create(0); for (int i=0; i<10; i++) { NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSLog(@"%d---%d",i,i); dispatch_semaphore_signal(sem); }]; [task resume]; dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); } dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"end"); });
8.異步操作兩組數(shù)據(jù)時, 執(zhí)行完第一組之后, 才能執(zhí)行第二組
-
這里使用dispatch_barrier_async柵欄方法即可實現(xiàn)
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ NSLog(@"第一次任務的主線程為: %@", [NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"第二次任務的主線程為: %@", [NSThread currentThread]); }); dispatch_barrier_async(queue, ^{ NSLog(@"第一次任務, 第二次任務執(zhí)行完畢, 繼續(xù)執(zhí)行"); }); dispatch_async(queue, ^{ NSLog(@"第三次任務的主線程為: %@", [NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"第四次任務的主線程為: %@", [NSThread currentThread]); });
9.多線程中的死鎖兵志?
死鎖是由于多個線程(進程)在執(zhí)行過程中醇蝴,因為爭奪資源而造成的互相等待現(xiàn)象,你可以理解為卡主了想罕。產(chǎn)生死鎖的必要條件有四個:
互斥條件 : 指進程對所分配到的資源進行排它性使用悠栓,即在一段時間內(nèi)某資源只由一個進程占用。如果此時還有其它進程請求資源按价,則請求者只能等待惭适,直至占有資源的進程用畢釋放。
請求和保持條件 : 指進程已經(jīng)保持至少一個資源楼镐,但又提出了新的資源請求癞志,而該資源已被其它進程占有,此時請求進程阻塞框产,但又對自己已獲得的其它資源保持不放凄杯。
不可剝奪條件 : 指進程已獲得的資源,在未使用完之前秉宿,不能被剝奪戒突,只能在使用完時由自己釋放。
-
環(huán)路等待條件 : 指在發(fā)生死鎖時描睦,必然存在一個進程——資源的環(huán)形鏈膊存,即進程集合{P0,P1忱叭,P2隔崎,···,Pn}中的P0正在等待一個P1占用的資源窑多;P1正在等待P2占用的資源仍稀,……,Pn正在等待已被P0占用的資源埂息。
最常見的就是 同步函數(shù) + 主隊列 的組合,本質(zhì)是隊列阻塞遥巴。
dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"2"); }); NSLog(@"1"); // 什么也不會打印千康,直接報錯
10.GCD執(zhí)行原理?
GCD有一個底層線程池铲掐,這個池中存放的是一個個的線程拾弃。之所以稱為“池”,很容易理解出這個“池”中的線程是可以重用的摆霉,當一段時間后這個線程沒有被調(diào)用胡話豪椿,這個線程就會被銷毀奔坟。注意:開多少條線程是由底層線程池決定的(線程建議控制再3~5條),池是系統(tǒng)自動來維護搭盾,不需要我們程序員來維護(看到這句話是不是很開心咳秉?)
而我們程序員需要關心的是什么呢?我們只關心的是向隊列中添加任務鸯隅,隊列調(diào)度即可澜建。如果隊列中存放的是同步任務,則任務出隊后蝌以,底層線程池中會提供一條線程供這個任務執(zhí)行炕舵,任務執(zhí)行完畢后這條線程再回到線程池。這樣隊列中的任務反復調(diào)度跟畅,因為是同步的咽筋,所以當我們用currentThread打印的時候,就是同一條線程徊件。
如果隊列中存放的是異步的任務晤硕,(注意異步可以開線程),當任務出隊后庇忌,底層線程池會提供一個線程供任務執(zhí)行舞箍,因為是異步執(zhí)行,隊列中的任務不需等待當前任務執(zhí)行完畢就可以調(diào)度下一個任務皆疹,這時底層線程池中會再次提供一個線程供第二個任務執(zhí)行疏橄,執(zhí)行完畢后再回到底層線程池中。
這樣就對線程完成一個復用略就,而不需要每一個任務執(zhí)行都開啟新的線程捎迫,也就從而節(jié)約的系統(tǒng)的開銷,提高了效率表牢。在iOS7.0的時候窄绒,使用GCD系統(tǒng)通常只能開58條線程,iOS8.0以后崔兴,系統(tǒng)可以開啟很多條線程彰导,但是實在開發(fā)應用中,建議開啟線程條數(shù):35條最為合理敲茄。
Runloop
1.Runloop 和線程的關系位谋?
一個線程對應一個 Runloop。
主線程的默認就有了 Runloop堰燎。
子線程的 Runloop 以懶加載的形式創(chuàng)建掏父。
Runloop 存儲在一個全局的可變字典里,線程是 key 秆剪,Runloop 是 value赊淑。
2.RunLoop的運行模式
-
RunLoop的運行模式共有5種爵政,RunLoop只會運行在一個模式下,要切換模式陶缺,就要暫停當前模式钾挟,重寫啟動一個運行模式
- kCFRunLoopDefaultMode, App的默認運行模式,通常主線程是在這個運行模式下運行 - UITrackingRunLoopMode, 跟蹤用戶交互事件(用于 ScrollView 追蹤觸摸滑動组哩,保證界面滑動時不受其他Mode影響) - kCFRunLoopCommonModes, 偽模式等龙,不是一種真正的運行模式 - UIInitializationRunLoopMode:在剛啟動App時第進入的第一個Mode,啟動完成后就不再使用 - GSEventReceiveRunLoopMode:接受系統(tǒng)內(nèi)部事件伶贰,通常用不到
3.runloop內(nèi)部邏輯蛛砰?
-
實際上 RunLoop 就是這樣一個函數(shù),其內(nèi)部是一個 do-while 循環(huán)黍衙。當你調(diào)用 CFRunLoopRun() 時泥畅,線程就會一直停留在這個循環(huán)里;直到超時或被手動停止琅翻,該函數(shù)才會返回位仁。
RunLoop -
內(nèi)部邏輯:
通知 Observer 已經(jīng)進入了 RunLoop
通知 Observer 即將處理 Timer
通知 Observer 即將處理非基于端口的輸入源(即將處理 Source0)
處理那些準備好的非基于端口的輸入源(處理 Source0)
如果基于端口的輸入源準備就緒并等待處理,請立刻處理該事件。轉到第 9 步(處理 Source1)
通知 Observer 線程即將休眠
-
將線程置于休眠狀態(tài),直到發(fā)生以下事件之一
事件到達基于端口的輸入源(port-based input sources)(也就是 Source0)
Timer 到時間執(zhí)行
外部手動喚醒
為 RunLoop 設定的時間超時
通知 Observer 線程剛被喚醒(還沒處理事件)
-
處理待處理事件
如果是 Timer 事件寇蚊,處理 Timer 并重新啟動循環(huán)帮碰,跳到第 2 步
如果輸入源被觸發(fā)划乖,處理該事件(文檔上是 deliver the event)
如果 RunLoop 被手動喚醒但尚未超時,重新啟動循環(huán),跳到第 2 步
4.autoreleasePool 在何時被釋放?
App啟動后空盼,蘋果在主線程 RunLoop 里注冊了兩個 Observer,其回調(diào)都是 _wrapRunLoopWithAutoreleasePoolHandler()新荤。
第一個 Observer 監(jiān)視的事件是 Entry(即將進入Loop)揽趾,其回調(diào)內(nèi)會調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動釋放池。其 order 是 -2147483647苛骨,優(yōu)先級最高篱瞎,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前。
第二個 Observer 監(jiān)視了兩個事件: BeforeWaiting(準備進入休眠) 時調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池智袭;Exit(即將退出Loop) 時調(diào)用 _objc_autoreleasePoolPop() 來釋放自動釋放池奔缠。這個 Observer 的 order 是 2147483647,優(yōu)先級最低吼野,保證其釋放池子發(fā)生在其他所有回調(diào)之后。
在主線程執(zhí)行的代碼两波,通常是寫在諸如事件回調(diào)瞳步、Timer回調(diào)內(nèi)的闷哆。這些回調(diào)會被 RunLoop 創(chuàng)建好的 AutoreleasePool 環(huán)繞著,所以不會出現(xiàn)內(nèi)存泄漏单起,開發(fā)者也不必顯示創(chuàng)建 Pool 了抱怔。
5.GCD 在Runloop中的使用?
- GCD由 子線程 返回到 主線程,只有在這種情況下才會觸發(fā) RunLoop嘀倒。會觸發(fā) RunLoop 的 Source 1 事件屈留。
6.AFNetworking 中如何運用 Runloop?
-
AFURLConnectionOperation 這個類是基于 NSURLConnection 構建的,其希望能在后臺線程接收 Delegate 回調(diào)测蘑。為此 AFNetworking 單獨創(chuàng)建了一個線程灌危,并在這個線程中啟動了一個 RunLoop:
+ (void)networkRequestThreadEntryPoint:(id)__unused object { @autoreleasepool { [[NSThread currentThread] setName:@"AFNetworking"]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [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; }
-
RunLoop 啟動前內(nèi)部必須要有至少一個 Timer/Observer/Source,所以 AFNetworking 在 [runLoop run] 之前先創(chuàng)建了一個新的 NSMachPort 添加進去了碳胳。通常情況下勇蝙,調(diào)用者需要持有這個 NSMachPort (mach_port) 并在外部線程通過這個 port 發(fā)送消息到 loop 內(nèi);但此處添加 port 只是為了讓 RunLoop 不至于退出挨约,并沒有用于實際的發(fā)送消息味混。
- (void)start { [self.lock lock]; if ([self isCancelled]) { [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } else if ([self isReady]) { self.state = AFOperationExecutingState; [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } [self.lock unlock]; }
當需要這個后臺線程執(zhí)行任務時,AFNetworking 通過調(diào)用 [NSObject performSelector:onThread:..] 將這個任務扔到了后臺線程的 RunLoop 中诫惭。
7.PerformSelector 的實現(xiàn)原理翁锡?
當調(diào)用 NSObject 的 performSelecter:afterDelay: 后,實際上其內(nèi)部會創(chuàng)建一個 Timer 并添加到當前線程的 RunLoop 中夕土。所以如果當前線程沒有 RunLoop馆衔,則這個方法會失效。
當調(diào)用 performSelector:onThread: 時隘弊,實際上其會創(chuàng)建一個 Timer 加到對應的線程去哈踱,同樣的,如果對應線程沒有 RunLoop 該方法也會失效梨熙。
8.PerformSelector:afterDelay:這個方法在子線程中是否起作用开镣?
- 不起作用,子線程默認沒有 Runloop咽扇,也就沒有 Timer邪财。可以使用 GCD的dispatch_after來實現(xiàn)
9.事件響應的過程质欲?
蘋果注冊了一個 Source1 (基于 mach port 的) 用來接收系統(tǒng)事件树埠,其回調(diào)函數(shù)為 __IOHIDEventSystemClientQueueCallback()。
當一個硬件事件(觸摸/鎖屏/搖晃等)發(fā)生后嘶伟,首先由 IOKit.framework 生成一個 IOHIDEvent 事件并由 SpringBoard 接收怎憋。這個過程的詳細情況可以參考這里。SpringBoard 只接收按鍵(鎖屏/靜音等),觸摸绊袋,加速毕匀,接近傳感器等幾種 Event,隨后用 mach port 轉發(fā)給需要的 App 進程癌别。隨后蘋果注冊的那個 Source1 就會觸發(fā)回調(diào)皂岔,并調(diào)用 _UIApplicationHandleEventQueue() 進行應用內(nèi)部的分發(fā)。
_UIApplicationHandleEventQueue() 會把 IOHIDEvent 處理并包裝成 UIEvent 進行處理或分發(fā)展姐,其中包括識別 UIGesture/處理屏幕旋轉/發(fā)送給 UIWindow 等躁垛。通常事件比如 UIButton 點擊、touchesBegin/Move/End/Cancel 事件都是在這個回調(diào)中完成的圾笨。
10.手勢識別的過程教馆?
當 _UIApplicationHandleEventQueue() 識別了一個手勢時,其首先會調(diào)用 Cancel 將當前的 touchesBegin/Move/End 系列回調(diào)打斷墅拭。隨后系統(tǒng)將對應的 UIGestureRecognizer 標記為待處理活玲。
蘋果注冊了一個 Observer 監(jiān)測 BeforeWaiting (Loop即將進入休眠) 事件,這個 Observer 的回調(diào)函數(shù)是 _UIGestureRecognizerUpdateObserver()谍婉,其內(nèi)部會獲取所有剛被標記為待處理的 GestureRecognizer舒憾,并執(zhí)行GestureRecognizer 的回調(diào)。
當有 UIGestureRecognizer 的變化(創(chuàng)建/銷毀/狀態(tài)改變)時穗熬,這個回調(diào)都會進行相應處理镀迂。
11.CADispalyTimer和Timer哪個更精確
CADisplayLink 更精確
iOS設備的屏幕刷新頻率是固定的,CADisplayLink在正常情況下會在每次刷新結束都被調(diào)用唤蔗,精確度相當高探遵。
NSTimer的精確度就顯得低了點,比如NSTimer的觸發(fā)時間到的時候妓柜,runloop如果在阻塞狀態(tài)箱季,觸發(fā)時間就會推遲到下一個runloop周期。并且 NSTimer新增了tolerance屬性棍掐,讓用戶可以設置可以容忍的觸發(fā)的時間的延遲范圍藏雏。
CADisplayLink使用場合相對專一,適合做UI的不停重繪作煌,比如自定義動畫引擎或者視頻播放的渲染掘殴。NSTimer的使用范圍要廣泛的多,各種需要單次或者循環(huán)定時處理的任務都可以使用粟誓。在UI相關的動畫或者顯示內(nèi)容使用 CADisplayLink比起用NSTimer的好處就是我們不需要在格外關心屏幕的刷新頻率了奏寨,因為它本身就是跟屏幕刷新同步的。
Runtime
1.Category 的實現(xiàn)原理鹰服?
Category 實際上是 Category_t的結構體病瞳,在運行時,新添加的方法,都被以倒序插入到原有方法列表的最前面仍源,所以不同的Category心褐,添加了同一個方法舔涎,執(zhí)行的實際上是最后一個笼踩。
Category 在剛剛編譯完的時候,和原來的類是分開的亡嫌,只有在程序運行起來后嚎于,通過 Runtime ,Category 和原來的類才會合并到一起挟冠。
2.isa指針的理解于购,對象的isa指針指向哪里?isa指針有哪兩種類型知染?
-
isa 等價于 is kind of
實例對象的 isa 指向類對象
類對象的 isa 指向元類對象
元類對象的 isa 指向元類的基類
-
isa 有兩種類型
純指針肋僧,指向內(nèi)存地址
NON_POINTER_ISA,除了內(nèi)存地址控淡,還存有一些其他信息
3.Objective-C 如何實現(xiàn)多重繼承嫌吠?
Object-c的類沒有多繼承,只支持單繼承,如果要實現(xiàn)多繼承的話,可使用如下幾種方式間接實現(xiàn)
-
通過組合實現(xiàn)
A和B組合掺炭,作為C類的組件
-
通過協(xié)議實現(xiàn)
C類實現(xiàn)A和B類的協(xié)議方法
-
消息轉發(fā)實現(xiàn)
forwardInvocation:方法
4.runtime 如何實現(xiàn) weak 屬性?
weak 此特質(zhì)表明該屬性定義了一種「非擁有關系」(nonowning relationship)炕矮。為這種屬性設置新值時肤视,設置方法既不持有新值(新指向的對象),也不釋放舊值(原來指向的對象)涉枫。
runtime 對注冊的類邢滑,會進行內(nèi)存布局,從一個粗粒度的概念上來講拜银,這時候會有一個 hash 表殊鞭,這是一個全局表,表中是用 weak 指向的對象內(nèi)存地址作為 key尼桶,用所有指向該對象的 weak 指針表作為 value操灿。當此對象的引用計數(shù)為 0 的時候會 dealloc,假如該對象內(nèi)存地址是 a泵督,那么就會以 a 為 key趾盐,在這個 weak 表中搜索,找到所有以 a 為鍵的 weak 對象,從而設置為 nil救鲤。
runtime 如何實現(xiàn) weak 屬性具體流程大致分為 3 步:
1久窟、初始化時:runtime 會調(diào)用 objc_initWeak 函數(shù),初始化一個新的 weak 指針指向?qū)ο蟮牡刂贰?/p>
2本缠、添加引用時:objc_initWeak 函數(shù)會調(diào)用 objc_storeWeak() 函數(shù)斥扛,objc_storeWeak() 的作用是更新指針指向(指針可能原來指向著其他對象,這時候需要將該 weak 指針與舊對象解除綁定丹锹,會調(diào)用到 weak_unregister_no_lock)稀颁,如果指針指向的新對象非空,則創(chuàng)建對應的弱引用表楣黍,將 weak 指針與新對象進行綁定阶女,會調(diào)用到 weak_register_no_lock秃踩。在這個過程中,為了防止多線程中競爭沖突芍秆,會有一些鎖的操作。
3荆虱、釋放時:調(diào)用 clearDeallocating 函數(shù),clearDeallocating 函數(shù)首先根據(jù)對象地址獲取所有 weak 指針地址的數(shù)組菜枷,然后遍歷這個數(shù)組把其中的數(shù)據(jù)設為 nil啤誊,最后把這個 entry 從 weak 表中刪除,最后清理對象的記錄姚炕。
5.講一下 OC 的消息機制
OC中的方法調(diào)用其實都是轉成了objc_msgSend函數(shù)的調(diào)用钻心,給receiver(方法調(diào)用者)發(fā)送了一條消息(selector方法名)
objc_msgSend底層有3大階段狐史,消息發(fā)送(當前類、父類中查找)姜贡、動態(tài)方法解析、消息轉發(fā)
6.runtime具體應用
利用關聯(lián)對象(AssociatedObject)給分類添加屬性
遍歷類的所有成員變量(修改textfield的占位文字顏色母怜、字典轉模型、自動歸檔解檔)
交換方法實現(xiàn)(交換系統(tǒng)的方法)
利用消息轉發(fā)機制解決方法找不到的異常問題
KVC 字典轉模型
7.runtime如何通過selector找到對應的IMP地址轨域?
每一個類對象中都一個對象方法列表(對象方法緩存)
類方法列表是存放在類對象中isa指針指向的元類對象中(類方法緩存)。
方法列表中每個方法結構體中記錄著方法的名稱,方法實現(xiàn),以及參數(shù)類型铐然,其實selector本質(zhì)就是方法名稱,通過這個方法名稱就可以在方法列表中找到對應的方法實現(xiàn)沥阳。
當我們發(fā)送一個消息給一個NSObject對象時桐罕,這條消息會在對象的類對象方法列表里查找。
當我們發(fā)送一個消息給一個類時,這條消息會在類的Meta Class對象的方法列表里查找嫁怀。
8.簡述下Objective-C中調(diào)用方法的過程
Objective-C是動態(tài)語言,每個方法在運行時會被動態(tài)轉為消息發(fā)送存捺,即:objc_msgSend(receiver, selector),整個過程介紹如下:
objc在向一個對象發(fā)送消息時,runtime庫會根據(jù)對象的isa指針找到該對象實際所屬的類
然后在該類中的方法列表以及其父類方法列表中尋找方法運行
如果构韵,在最頂層的父類(一般也就NSObject)中依然找不到相應的方法時,程序在運行時會掛掉并拋出異常unrecognized selector sent to XXX
但是在這之前,objc的運行時會給出三次拯救程序崩潰的機會杂数,這三次拯救程序奔潰的說明見問題《什么時候會報unrecognized selector的異炒魏停》中的說明。
9.load和initialize的區(qū)別
兩者都會自動調(diào)用父類的,不需要super操作日熬,且僅會調(diào)用一次(不包括外部顯示調(diào)用).
load和initialize方法都會在實例化對象之前調(diào)用,以main函數(shù)為分水嶺揣炕,前者在main函數(shù)之前調(diào)用,后者在之后調(diào)用曹动。這兩個方法會被自動調(diào)用第献,不能手動調(diào)用它們仔拟。
load和initialize方法都不用顯示的調(diào)用父類的方法而是自動調(diào)用,即使子類沒有initialize方法也會調(diào)用父類的方法,而load方法則不會調(diào)用父類。
load方法通常用來進行Method Swizzle崭闲,initialize方法一般用于初始化全局變量或靜態(tài)變量韧涨。
load和initialize方法內(nèi)部使用了鎖如孝,因此它們是線程安全的彬祖。實現(xiàn)時要盡可能保持簡單茁瘦,避免阻塞線程,不要再使用鎖储笑。
10.怎么理解Objective-C是動態(tài)運行時語言甜熔。
主要是將數(shù)據(jù)類型的確定由編譯時,推遲到了運行時。這個問題其實淺涉及到兩個概念,運行時和多態(tài)突倍。
簡單來說, 運行時機制使我們直到運行時才去決定一個對象的類別,以及調(diào)用該類別對象指定方法腔稀。
多態(tài):不同對象以自己的方式響應相同的消息的能力叫做多態(tài)炕淮。
意思就是假設生物類(life)都擁有一個相同的方法-eat;那人類屬于生物,豬也屬于生物,都繼承了life后,實現(xiàn)各自的eat,但是調(diào)用是我們只需調(diào)用各自的eat方法踩衩。也就是不同的對象以自己的方式響應了相同的消 息(響應了eat這個選擇器)叫榕。因此也可以說,運行時機制是多態(tài)的基礎.
網(wǎng)絡
1.網(wǎng)絡七層協(xié)議
-
應用層:
1.用戶接口、應用程序掸鹅;
2.Application典型設備:網(wǎng)關矮嫉;
3.典型協(xié)議、標準和應用:TELNET蹂匹、FTP、HTTP
-
表示層:
1.數(shù)據(jù)表示、壓縮和加密presentation
2.典型設備:網(wǎng)關
3.典型協(xié)議、標準和應用:ASCLL钞螟、PICT甫匹、TIFF、JPEG|MPEG
4.表示層相當于一個東西的表示梳码,表示的一些協(xié)議暖混,比如圖片收擦、聲音和視頻MPEG辽剧。
-
會話層:
1.會話的建立和結束碌宴;
2.典型設備:網(wǎng)關;
3.典型協(xié)議上煤、標準和應用:RPC休玩、SQL、NFS楼入、X WINDOWS哥捕、ASP
-
傳輸層:
1.主要功能:端到端控制Transport;
2.典型設備:網(wǎng)關嘉熊;
3.典型協(xié)議遥赚、標準和應用:TCP、UDP阐肤、SPX
-
網(wǎng)絡層:
1.主要功能:路由凫佛、尋址Network;
2.典型設備:路由器孕惜;
3.典型協(xié)議愧薛、標準和應用:IP、IPX衫画、APPLETALK毫炉、ICMP;
-
數(shù)據(jù)鏈路層:
1.主要功能:保證無差錯的疏忽鏈路的data link削罩;
2.典型設備:交換機瞄勾、網(wǎng)橋、網(wǎng)卡弥激;
3.典型協(xié)議进陡、標準和應用:802.2、802.3ATM微服、HDLC趾疚、FRAME RELAY;
-
物理層:
1.主要功能:傳輸比特流Physical以蕴;
2.典型設備:集線器糙麦、中繼器
3.典型協(xié)議、標準和應用:V.35丛肮、EIA/TIA-232.
2.Http 和 Https 的區(qū)別喳资?Https為什么更加安全?
-
區(qū)別
1.HTTPS 需要向機構申請 CA 證書腾供,極少免費。
2.HTTP 屬于明文傳輸,HTTPS基于 SSL 進行加密傳輸伴鳖。
3.HTTP 端口號為 80节值,HTTPS 端口號為 443 。
4.HTTPS 是加密傳輸榜聂,有身份驗證的環(huán)節(jié)搞疗,更加安全。
-
安全
SSL(安全套接層) TLS(傳輸層安全)
以上兩者在傳輸層之上须肆,對網(wǎng)絡連接進行加密處理匿乃,保障數(shù)據(jù)的完整性,更加的安全豌汇。
3.HTTPS的連接建立流程
HTTPS為了兼顧安全與效率幢炸,同時使用了對稱加密和非對稱加密。在傳輸?shù)倪^程中會涉及到三個密鑰:
服務器端的公鑰和私鑰拒贱,用來進行非對稱加密
-
客戶端生成的隨機密鑰宛徊,用來進行對稱加密
https
如上圖,HTTPS連接過程大致可分為八步:
-
1逻澳、客戶端訪問HTTPS連接闸天。
客戶端會把安全協(xié)議版本號、客戶端支持的加密算法列表斜做、隨機數(shù)C發(fā)給服務端苞氮。
-
2、服務端發(fā)送證書給客戶端
服務端接收密鑰算法配件后瓤逼,會和自己支持的加密算法列表進行比對笼吟,如果不符合,則斷開連接抛姑。否則赞厕,服務端會在該算法列表中,選擇一種對稱算法(如AES)定硝、一種公鑰算法(如具有特定秘鑰長度的RSA)和一種MAC算法發(fā)給客戶端皿桑。
服務器端有一個密鑰對,即公鑰和私鑰蔬啡,是用來進行非對稱加密使用的诲侮,服務器端保存著私鑰,不能將其泄露箱蟆,公鑰可以發(fā)送給任何人沟绪。
在發(fā)送加密算法的同時還會把數(shù)字證書和隨機數(shù)S發(fā)送給客戶端
-
3、客戶端驗證server證書
會對server公鑰進行檢查空猜,驗證其合法性绽慈,如果發(fā)現(xiàn)發(fā)現(xiàn)公鑰有問題恨旱,那么HTTPS傳輸就無法繼續(xù)。
-
4坝疼、客戶端組裝會話秘鑰
如果公鑰合格搜贤,那么客戶端會用服務器公鑰來生成一個前主秘鑰(Pre-Master Secret,PMS)钝凶,并通過該前主秘鑰和隨機數(shù)C仪芒、S來組裝成會話秘鑰
-
5、客戶端將前主秘鑰加密發(fā)送給服務端
是通過服務端的公鑰來對前主秘鑰進行非對稱加密耕陷,發(fā)送給服務端
-
6掂名、服務端通過私鑰解密得到前主秘鑰
服務端接收到加密信息后镶奉,用私鑰解密得到主秘鑰梯嗽。
-
7、服務端組裝會話秘鑰
服務端通過前主秘鑰和隨機數(shù)C秋麸、S來組裝會話秘鑰南用。
至此膀钠,服務端和客戶端都已經(jīng)知道了用于此次會話的主秘鑰。
-
8裹虫、數(shù)據(jù)傳輸
客戶端收到服務器發(fā)送來的密文肿嘲,用客戶端密鑰對其進行對稱解密,得到服務器發(fā)送的數(shù)據(jù)筑公。
同理雳窟,服務端收到客戶端發(fā)送來的密文,用服務端密鑰對其進行對稱解密匣屡,得到客戶端發(fā)送的數(shù)據(jù)封救。
4.解釋一下 三次握手 和 四次揮手
-
三次握手
1.由客戶端向服務端發(fā)送 SYN 同步報文。
2.當服務端收到 SYN 同步報文之后捣作,會返回給客戶端 SYN 同步報文和 ACK 確認報文誉结。
3.客戶端會向服務端發(fā)送 ACK 確認報文,此時客戶端和服務端的連接正式建立券躁。
-
建立連接
1.這個時候客戶端就可以通過 Http 請求報文惩坑,向服務端發(fā)送請求
2.服務端接收到客戶端的請求之后,向客戶端回復 Http 響應報文也拜。
-
四次揮手
當客戶端和服務端的連接想要斷開的時候以舒,要經(jīng)歷四次揮手的過程,步驟如下:
1.先由客戶端向服務端發(fā)送 FIN 結束報文慢哈。
2.服務端會返回給客戶端 ACK 確認報文 蔓钟。此時,由客戶端發(fā)起的斷開連接已經(jīng)完成卵贱。
3.服務端會發(fā)送給客戶端 FIN 結束報文 和 ACK 確認報文滥沫。
4.客戶端會返回 ACK 確認報文到服務端侣集,至此,由服務端方向的斷開連接已經(jīng)完成兰绣。
5.TCP 和 UDP的區(qū)別
TCP:面向連接肚吏、傳輸可靠(保證數(shù)據(jù)正確性,保證數(shù)據(jù)順序)、用于傳輸大量數(shù)據(jù)(流模式)狭魂、速度慢,建立連接需要開銷較多(時間党觅,系統(tǒng)資源)雌澄。
UDP:面向非連接、傳輸不可靠杯瞻、用于傳輸少量數(shù)據(jù)(數(shù)據(jù)包模式)镐牺、速度快。
6.Cookie和Session
cookie
-
1.用戶與服務器的交互
cookie主要是用來記錄用戶狀態(tài)魁莉,區(qū)分用戶睬涧,狀態(tài)保存在客戶端。cookie功能需要瀏覽器的支持旗唁。如果瀏覽器不支持cookie(如大部分手機中的瀏覽器)或者把cookie禁用了畦浓,cookie功能就會失效。
cookiea).首次訪問amazon時检疫,客戶端發(fā)送一個HTTP請求到服務器端 讶请。服務器端發(fā)送一個HTTP響應到客戶端,其中包含Set-Cookie頭部
b).客戶端發(fā)送一個HTTP請求到服務器端屎媳,其中包含Cookie頭部夺溢。服務器端發(fā)送一個HTTP響應到客戶端
c).隔段時間再去訪問時,客戶端會直接發(fā)包含Cookie頭部的HTTP請求烛谊。服務器端發(fā)送一個HTTP響應到客戶端
-
2.cookie的修改和刪除
在修改cookie的時候风响,只需要新cookie覆蓋舊cookie即可,在覆蓋的時候丹禀,由于Cookie具有不可跨域名性状勤,注意name、path湃崩、domain需與原cookie一致
刪除cookie也一樣荧降,設置cookie的過期時間expires為過去的一個時間點,或者maxAge = 0(Cookie的有效期,單位為秒)即可
-
3攒读、cookie的安全
事實上朵诫,cookie的使用存在爭議,因為它被認為是對用戶隱私的一種侵害薄扁,而且cookie并不安全
HTTP協(xié)議不僅是無狀態(tài)的剪返,而且是不安全的废累。使用HTTP協(xié)議的數(shù)據(jù)不經(jīng)過任何加密就直接在網(wǎng)絡上傳播,有被截獲的可能脱盲。使用HTTP協(xié)議傳輸很機密的內(nèi)容是一種隱患邑滨。
a).如果不希望Cookie在HTTP等非安全協(xié)議中傳輸,可以設置Cookie的secure屬性為true钱反。瀏覽器只會在HTTPS和SSL等安全協(xié)議中傳輸此類Cookie掖看。
b).此外,secure屬性并不能對Cookie內(nèi)容加密面哥,因而不能保證絕對的安全性哎壳。如果需要高安全性,需要在程序中對Cookie內(nèi)容加密尚卫、解密归榕,以防泄密。
c).也可以設置cookie為HttpOnly吱涉,如果在cookie中設置了HttpOnly屬性刹泄,那么通過js腳本將無法讀取到cookie信息,這樣能有效的防止XSS(跨站腳本攻擊)攻擊
Session
Session是服務器端使用的一種記錄客戶端狀態(tài)的機制怎爵,使用上比Cookie簡單一些特石,相應的也增加了服務器的存儲壓力。
-
Session是另一種記錄客戶狀態(tài)的機制疙咸,不同的是Cookie保存在客戶端瀏覽器中县匠,而Session保存在服務器上。
客戶端瀏覽器訪問服務器的時候撒轮,服務器把客戶端信息以某種形式記錄在服務器上乞旦。這就是Session√馍剑客戶端瀏覽器再次訪問時只需要從該Session中查找該客戶的狀態(tài)就可以了兰粉。session -
如圖:
當程序需要為某個客戶端的請求創(chuàng)建一個session時,服務器首先檢查這個客戶端的請求里是否已包含了一個session標識(稱為SessionId)
如果已包含則說明以前已經(jīng)為此客戶端創(chuàng)建過session顶瞳,服務器就按照SessionId把這個session檢索出來玖姑,使用(檢索不到,會新建一個)
如果客戶端請求不包含SessionId慨菱,則為此客戶端創(chuàng)建一個session并且生成一個與此session相關聯(lián)的SessionId焰络,SessionId的值應該是一個既不會重復,又不容易被找到規(guī)律以仿造的字符串符喝,這個SessionId將被在本次響應中返回給客戶端保存闪彼。
保存這個SessionId的方式可以采用cookie,這樣在交互過程中瀏覽器可以自動的按照規(guī)則把這個標識發(fā)送給服務器。但cookie可以被人為的禁止畏腕,則必須有其他機制以便在cookie被禁止時仍然能夠把SessionId傳遞回服務器缴川。
Cookie 和Session 的區(qū)別:
1、cookie數(shù)據(jù)存放在客戶的瀏覽器上描馅,session數(shù)據(jù)放在服務器上把夸。
2、cookie相比session不是很安全铭污,別人可以分析存放在本地的cookie并進行cookie欺騙,考慮到安全應當使用session恋日。
3、session會在一定時間內(nèi)保存在服務器上嘹狞。當訪問增多谚鄙,會比較占用你服務器的性能,考慮到減輕服務器性能方面,應當使用cookie刁绒。
4、單個cookie保存的數(shù)據(jù)不能超過4K烤黍,很多瀏覽器都限制一個站點最多保存20個cookie知市。而session存儲在服務端,可以無限量存儲
5速蕊、所以:將登錄信息等重要信息存放為session;其他信息如果需要保留嫂丙,可以放在cookie中
7.DNS是什么
因特網(wǎng)上的主機,可以使用多種方式標識规哲,比如主機名或IP地址跟啤。一種標識方法就是用它的主機名(hostname),比如·www.baidu.com唉锌、www.google.com隅肥、gaia.cs.umass.edu等。這方式方便人們記憶和接受袄简,但是這種長度不一腥放、沒有規(guī)律的字符串路由器并不方便處理。還有一種方式绿语,就是直接使用定長的秃症、有著清晰層次結構的IP地址,路由器比較熱衷于這種方式吕粹。為了折衷這兩種方式种柑,我們需要一種能進行主機名到IP地址轉換的目錄服務。這就是域名系統(tǒng)(Domain Name System匹耕,DNS)的主要任務聚请。
-
DNS是:
1、一個由分層的DNS服務器實現(xiàn)的分布式數(shù)據(jù)庫
2泌神、一個使得主機能夠查詢分布式數(shù)據(jù)庫的應用層協(xié)議
DNS服務器通常是運行BIND軟件的UNIX機器良漱,DNS協(xié)議運行在UDP上舞虱,使用53號端口
DNS通常是由其他應用層協(xié)議所使用的,包括HTTP母市、SMTP等矾兜。其作用則是:將用戶提供的主機名解析為IP地址
-
DNS的一種簡單設計就是在因特網(wǎng)上只使用一個DNS服務器,該服務器包含所有的映射患久。很明顯這種設計是有很大的問題的:
單點故障:如果該DNS服務器崩潰椅寺,全世界的網(wǎng)絡隨之癱瘓
通信容量:單個DNS服務器必須處理所有DNS查詢
遠距離的集中式數(shù)據(jù)庫:單個DNS服務器必須面對所有用戶,距離過遠會有嚴重的時延蒋失。
維護:該數(shù)據(jù)庫過于龐大返帕,還需要對新添加的主機頻繁更新。
所以篙挽,DNS被設計成了一個分布式荆萤、層次數(shù)據(jù)庫
8.DNS解析過程
以www.163.com為例:
客戶端打開瀏覽器,輸入一個域名铣卡。比如輸入www.163.com链韭,這時,客戶端會發(fā)出一個DNS請求到本地DNS服務器煮落。本地DNS服務器一般都是你的網(wǎng)絡接入服務器商提供敞峭,比如中國電信,中國移動蝉仇。
查詢www.163.com的DNS請求到達本地DNS服務器之后旋讹,本地DNS服務器會首先查詢它的緩存記錄,如果緩存中有此條記錄轿衔,就可以直接返回結果沉迹。如果沒有,本地DNS服務器還要向DNS根服務器進行查詢害驹。
根DNS服務器沒有記錄具體的域名和IP地址的對應關系胚股,而是告訴本地DNS服務器,你可以到域服務器上去繼續(xù)查詢裙秋,并給出域服務器的地址琅拌。
本地DNS服務器繼續(xù)向域服務器發(fā)出請求,在這個例子中摘刑,請求的對象是.com域服務器进宝。.com域服務器收到請求之后,也不會直接返回域名和IP地址的對應關系枷恕,而是告訴本地DNS服務器党晋,你的域名的解析服務器的地址。
最后,本地DNS服務器向域名的解析服務器發(fā)出請求未玻,這時就能收到一個域名和IP地址對應關系灾而,本地DNS服務器不僅要把IP地址返回給用戶電腦,還要把這個對應關系保存在緩存中扳剿,以備下次別的用戶查詢時旁趟,可以直接返回結果,加快網(wǎng)絡訪問庇绽。