iOS開發(fā) 快速進(jìn)階與實(shí)戰(zhàn)
類蛛淋、類屬性卖鲤、類方法疗认、黑魔法
- 類屬性
NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END: 這是系統(tǒng)提供的兩個(gè)宏,用來將默認(rèn)未標(biāo)明為nullable還是nonnull的類的屬性都默認(rèn)設(shè)置為nonnull突倍;目的是為了兼容Swift的可選值類型(可橋接成更明確的方法)队伟,我們希望在對(duì)應(yīng)的Property中加上特定的修飾符穴吹;但是如果一個(gè)類的屬性很多,這樣會(huì)比較麻煩嗜侮,所以系統(tǒng)引入了NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END港令。 - 類的初始化
通過UNAVAILABLE_ATTRIBUTE可以修飾在頭文件中禁用的其他初始化方法,保證只有自己提供的初始化方法才是唯一途徑锈颗。image.png - 類方法的self
類方法的self指向的是當(dāng)前類顷霹,而不是固定的某個(gè)類,還可能是這個(gè)類的子類击吱。 - 類屬性
類可以看做是其原類的實(shí)例淋淀。在objc對(duì)象的結(jié)構(gòu)體,其中有一個(gè)methodLists覆醇,用于存儲(chǔ)對(duì)象方法的列表朵纷;根據(jù)OC的運(yùn)行時(shí)機(jī)制,一個(gè)類實(shí)例調(diào)用方法永脓,會(huì)在該類的方法表中檢索(methodLists)袍辞,如果未能找到則會(huì)沿著類關(guān)系繼承鏈路逐層向父類查找,也就是父類的methodLists常摧;如果都找不到搅吁,則會(huì)按照消息傳遞的邏輯進(jìn)行。同理,一個(gè)類的變量存在類的ivars中似芝,沒有則在父類中查找那婉。
XCode8的發(fā)布,表示Swift進(jìn)入了3.0的時(shí)代党瓮,同時(shí)Objective-C也引入了類屬性详炬,用法是在property的修飾符中加入class,表示這是一個(gè)類屬性寞奸,并在實(shí)現(xiàn)中手動(dòng)實(shí)現(xiàn)getter呛谜、setting方法。image.png
image.png
很多類的屬性中枪萄,并不需要設(shè)置為實(shí)例屬性隐岛,因?yàn)槊總€(gè)實(shí)例都會(huì)創(chuàng)建一遍,開辟一塊內(nèi)存瓷翻。類屬性的使用類似于自己實(shí)現(xiàn)property聚凹。類屬性本身不提供存儲(chǔ),類似于Swift的計(jì)算屬性齐帚。 - 黑魔法
黑魔法就是OC語言中的runtime給類的類方法或者實(shí)例方法做交換妒牙,達(dá)到不用修改原類就能在特定的方法做操作。黑魔法的使用時(shí)AOP編程思想的重要實(shí)現(xiàn)对妄。交換方法雖然會(huì)給特定情況下的開發(fā)帶來便利湘今,但是也會(huì)帶來一些侵入性,可能會(huì)影響其他類的代碼剪菱,所以要謹(jǐn)慎使用摩瞎。
底層實(shí)現(xiàn)分析
- 內(nèi)存分區(qū)
棧區(qū):由系統(tǒng)來自動(dòng)分配釋放,是一個(gè)棧的數(shù)據(jù)結(jié)構(gòu)孝常,存儲(chǔ)函數(shù)的參數(shù)旗们、局部變量、引用构灸;
堆區(qū):由開發(fā)者手動(dòng)管理或者程序結(jié)束后由系統(tǒng)全部回收蚪拦,是一種樹狀的數(shù)據(jù)結(jié)構(gòu),一般用于存儲(chǔ)由malloc冻押、new等方式創(chuàng)建的對(duì)象。開發(fā)中多數(shù)內(nèi)存管理問題多出于此盛嘿,未能及時(shí)回收內(nèi)存或者內(nèi)存溢出洛巢、內(nèi)存泄漏;
全局區(qū)(靜態(tài)存儲(chǔ)區(qū)):用于存放全局變量和靜態(tài)變量次兆,進(jìn)程結(jié)束后系統(tǒng)回收稿茉;
常量區(qū):主要存儲(chǔ)基本數(shù)據(jù)類型的值,以及常量,進(jìn)程結(jié)束后系統(tǒng)回收漓库;
代碼區(qū):存儲(chǔ)要執(zhí)行函數(shù)的二進(jìn)制代碼恃慧,如果需要執(zhí)行就加載到該區(qū)域中。
以下有幾個(gè)特例:
-
字符串類型:多個(gè)直接聲明的相同字符串在內(nèi)存中只占用一份內(nèi)存:image.png
因?yàn)橥粋€(gè)固定的字符串渺蒿,在編譯器就確定了痢士,不會(huì)更改。
- block類型
block聲明的時(shí)候是在棧中存儲(chǔ)的茂装,但是賦值給變量的時(shí)候會(huì)復(fù)制到堆中怠蹂。
初始化
創(chuàng)建一個(gè)對(duì)象通過alloc和init兩步實(shí)現(xiàn),alloc是為該對(duì)象分配內(nèi)存空間少态,init才是真正的初始化城侧。
在Objective-C中,初始化方法其實(shí)并不是安全的彼妻,因?yàn)閷?duì)初始化方法沒有限制嫌佑,開發(fā)者可以任意調(diào)用以及實(shí)現(xiàn)某個(gè)類的初始化方法。例如UITableView中 initWithFrame方法是初始化方法侨歉,但是沒有約束屋摇,會(huì)導(dǎo)致濫用,很可能直接使用init方法進(jìn)行初始化为肮。
initWithFrame方法是一個(gè)NS_DESIGNATED_INITIALIZER方法摊册,表明該方法是一個(gè)Designated方法。
Designated和Convenience初始化方法之間有什么關(guān)系呢颊艳?Designated方法是對(duì)外提供的標(biāo)準(zhǔn)的初始化方法茅特。如果該類還需要?jiǎng)?chuàng)建一些補(bǔ)充的初始化方法,需要在內(nèi)部調(diào)用當(dāng)前類的Designated方法棋枕。例如UITableView的init方法內(nèi)部調(diào)用了initWithFrame方法白修,這些補(bǔ)充的方法稱為Convenience初始化方法。拷貝
深拷貝:拷貝一個(gè)實(shí)例對(duì)象到一個(gè)新的內(nèi)存地址重斑;
淺拷貝:拷貝一個(gè)實(shí)例對(duì)象的指針兵睛;
完全深拷貝:對(duì)對(duì)象的每一層都是重新創(chuàng)建的實(shí)例變量,不存在指針拷貝窥浪;例如對(duì)數(shù)組的歸檔解檔就是完全深拷貝祖很;
數(shù)組與集合
不可變數(shù)組的存儲(chǔ)方式是一串連續(xù)的地址空間存儲(chǔ)的形式,對(duì)于可變數(shù)組是鏈表的形式漾脂;
數(shù)組與鏈表的區(qū)別:數(shù)組查找更快假颇、鏈表增刪更快晓折;字典(Map)與哈希表
字典中的鍵可以為任意實(shí)現(xiàn)NSCoping協(xié)議的對(duì)象倒淫,但是如果鍵不是NSString類型的話,不可以使用KVC進(jìn)行存取值缤弦。在OC中無論是鍵還是值都不能是nil,如果傳入一個(gè)nil形耗,會(huì)造成crash哥桥。
字典的結(jié)構(gòu)和工作原理:
在打印字典的時(shí)候會(huì)發(fā)現(xiàn)字典內(nèi)容的打印結(jié)果順序是不固定的,是因?yàn)樽值渲薪o一個(gè)很重要的概念:哈希表激涤。KVC(Key-Value-Coding)
KVC依賴于Runtime拟糕,在OC的動(dòng)態(tài)性方面發(fā)揮了重要作用。
主要功能是直接通過變量名來訪問變量成員昔期,不管是私有還是公有已卸。
開發(fā)原理相關(guān)
-
定時(shí)器的引用image.png
這時(shí)我們在dealloc中對(duì)timer執(zhí)行invalidate方法,在頁面銷毀時(shí)該方法并不會(huì)被調(diào)用硼一,這時(shí)為什么呢累澡?
事實(shí)上,timer會(huì)直接對(duì)傳遞過來的target強(qiáng)引用般贼,即使是weak修飾的愧哟。如果控制器本身對(duì)timer有強(qiáng)引用,那會(huì)造成循環(huán)運(yùn)用哼蛆。
定時(shí)器的啟用還涉及到一個(gè)領(lǐng)域就是Runloop蕊梧,timer必須加到Runloop中才有效。在repeats為YES的情況下是通過Runloop來控制timer的腮介,而Runloop可以理解為一個(gè)處理事務(wù)的死循環(huán)肥矢,有時(shí)候處理事務(wù)可能會(huì)消耗一些事件導(dǎo)致某次循環(huán)時(shí)間稍微長了一些,因此會(huì)出現(xiàn)平常所說的timer不準(zhǔn)確的現(xiàn)象叠洗。所以Runloop是對(duì)timer有強(qiáng)引用的甘改。
WeChat1558af0f86d29891e62a4d13eb4bbc6e.png
當(dāng)前Runloop是不會(huì)被銷毀的,強(qiáng)引用了timer灭抑,而timer通過參數(shù)強(qiáng)引用了target十艾,也就是控制器self,所以不管self對(duì)timer是強(qiáng)引用還是弱引用腾节,都是不能打破這種循環(huán)引用的忘嫉。
以上所說的timer問題是對(duì)于重復(fù)的定時(shí)器,不重復(fù)的定時(shí)器在僅執(zhí)行一次selector后就自動(dòng)invalidate了案腺。
解決方案一:創(chuàng)建中間對(duì)象:弱引用原本timer應(yīng)持有的target庆冕;也就是讓控制器弱引用timer,打破循環(huán)引用劈榨;
解決方案二:利用消息轉(zhuǎn)發(fā)機(jī)制來實(shí)現(xiàn):創(chuàng)建一個(gè)類愧杯,弱引用target,返回該類的實(shí)例鞋既,然后實(shí)現(xiàn)消息轉(zhuǎn)發(fā)機(jī)制力九,將傳遞給該實(shí)例的消息轉(zhuǎn)發(fā)給target。 動(dòng)畫事務(wù)
我們平時(shí)常用的動(dòng)畫有以下幾種:
- UIView的類方法:animateWithDuration
- CAAnimation
- POP:基于CADisplayLink
- 隱式動(dòng)畫:基于CALayer邑闺,CALayer是UIView的一個(gè)屬性跌前,負(fù)責(zé)顯示;而UIView主要負(fù)責(zé)用戶交互陡舅;上述三種直接對(duì)UIView進(jìn)行動(dòng)畫抵乓,實(shí)際也是通過CALayer來實(shí)現(xiàn)的;
- 響應(yīng)鏈
當(dāng)我們點(diǎn)擊屏幕時(shí)靶衍,系統(tǒng)會(huì)記錄該次的觸摸事件灾炭,添加到Application的事件隊(duì)列中,然后從keyWindow開始依次向上尋找颅眶,結(jié)合響應(yīng)者的pointInside方法和hitTest方法找到處理該觸摸時(shí)間的view蜈出,從而也形成了一條事件響應(yīng)鏈;