1, 面試題
1, 使用NSDisplayLink仅淑、NSTimer有什么注意的地方?
2, 介紹下內(nèi)存的幾大區(qū)域线脚?
3, 你對(duì)iOS內(nèi)存管理是怎樣理解的?
4, ARC都幫我們做了什么?
5, weakSelf的原理是怎樣的?
6, autorelease對(duì)象會(huì)在什么時(shí)機(jī)調(diào)用release?
7, 方法里有局部變量, 會(huì)在方法執(zhí)行完之后馬上釋放嗎?
2, timer的探究
我們先從NSTimer開(kāi)始講起
2.1 NSTimer
使用NSTimer最常見(jiàn)的問(wèn)題有兩個(gè): 一個(gè)在后面說(shuō), 另一個(gè)就是循環(huán)引用, 引起循環(huán)引用的原因是: NSTimer中有一個(gè)強(qiáng)引用的target屬性, 持有VC本身, 所以會(huì)形成指針環(huán). 解決的方案有:
1,調(diào)用帶block的方法且配合weakSelf來(lái)達(dá)到弱引用的效果;
2, 使用一個(gè)第三方的中間體來(lái)達(dá)到弱引用的效果(proxy).
接下來(lái)我們先簡(jiǎn)單說(shuō)一下方法1, 然后重點(diǎn)說(shuō)方法2.
調(diào)用帶block的方法且配合weakSelf來(lái)達(dá)到弱引用的效果
使用一個(gè)第三方的中間體來(lái)達(dá)到弱引用的效果(proxy)
這個(gè)方法的原理是這樣的: 創(chuàng)建一個(gè)中間體來(lái)弱引用持有viewController, 從而達(dá)到破壞循環(huán)引用的目的
這個(gè)中間體我們一般稱為proxy, 實(shí)現(xiàn)的方法有兩種: 一種是繼承自NSObject, 另一種方法是繼承自NSProxy. 下面直接po出這兩種方法的實(shí)現(xiàn), 然后比對(duì)效果.
新建一個(gè)工程, 然后分別封裝兩個(gè)proxy類, 并實(shí)現(xiàn)調(diào)用, 代碼截圖如下
封裝的代碼如下, 注意截圖中的提示
上面的代碼就是實(shí)現(xiàn)的全部了, 可能也會(huì)有同學(xué)有疑問(wèn): 為什么不直接在GQProxy00或GQProxy01中實(shí)現(xiàn)test方法呢? 因?yàn)槲覀冊(cè)O(shè)計(jì)這個(gè)proxy中間體就是是為了考慮到以后別的類或者taget也會(huì)使用這個(gè)proxy做中間體, 所以proxy內(nèi)部不寫最終要調(diào)用的方法, 而是返回消息發(fā)送者本身.
接下來(lái)我們說(shuō)一下繼承自NSObject和NSProxy這兩種不同方案的區(qū)別:
1, 繼承自NSObject的中間體使用的是消息發(fā)送的整套機(jī)制, 也就是會(huì)遍歷本身和父類的消息列表, 最終找到方法本身來(lái)進(jìn)行調(diào)用;
2, 繼承自NSProxy的中間體則直接使用消息轉(zhuǎn)發(fā)機(jī)制, 也就是消息發(fā)送機(jī)制的最后一步, 直接將消息轉(zhuǎn)發(fā)出去, 這樣效率更高.
上面的第二點(diǎn)就是NSProxy類的最大用處. 關(guān)于NSProxy可能我們了解的很少, 下面展開(kāi)說(shuō)一下:
NSProxy是啥
NSProxy是一個(gè)跟NSObject同級(jí)的類, 遵守<NSProxy>協(xié)議但不繼承自NSObject; 其本身并沒(méi)有init方法; 當(dāng)使用NSProxy的對(duì)象進(jìn)行方法調(diào)用時(shí), 并不會(huì)去遍歷父類的方法列表, 而是只遍歷本身的方法列表, 如果沒(méi)找到方法實(shí)現(xiàn), 則會(huì)直接將消息轉(zhuǎn)發(fā); 是一個(gè)專門設(shè)計(jì)用來(lái)進(jìn)行消息轉(zhuǎn)發(fā)的類, 相比于一般繼承自NSObject的類, 其轉(zhuǎn)發(fā)消息非常的高效.
剛剛沒(méi)說(shuō)的關(guān)于NStimer的一個(gè)問(wèn)題就是: NStimer定時(shí)器并不能保證時(shí)間精準(zhǔn). 因?yàn)镹Stimer的底層是基于runloop來(lái)實(shí)現(xiàn)的. 實(shí)現(xiàn)的過(guò)程是這樣的: 假如設(shè)置了NStimer時(shí)間間隔為1.0秒, runloop底層有一段計(jì)算時(shí)間的代碼, 當(dāng)runloop每進(jìn)行一次循環(huán)都會(huì)判斷累計(jì)的時(shí)間, 如果累計(jì)時(shí)間>=1.0s, 就會(huì)調(diào)用NStimer的方法. 但其實(shí)runloop每次循環(huán)的時(shí)間都是根據(jù)任務(wù)的不同而有所不同的, 這就是導(dǎo)致不能保證每次累計(jì)的時(shí)間間隔都能精確的控制在1.0s. 所以如果需要精準(zhǔn)的時(shí)間間隔, 應(yīng)該使用GCD, 因?yàn)镚CD是不依賴runloop來(lái)實(shí)現(xiàn)的時(shí)間間隔, 而是依賴內(nèi)核來(lái)實(shí)現(xiàn)的.
2.2 NSDisplayLink
相比于NStimer, NSDisplayLink也可以實(shí)現(xiàn)定時(shí)器功能, 但其可以精準(zhǔn)的實(shí)現(xiàn)60fps幀率的水平, 也就是屏幕刷的幀率. 但其跟NSTimer一樣, 也會(huì)對(duì)taget形成強(qiáng)引用, 引起循環(huán)引用, 而解決的方法跟NStimer一樣, 所以這里就不展開(kāi)說(shuō)了.
接下來(lái)我們講內(nèi)存管理的另一塊知識(shí)點(diǎn): 內(nèi)存布局.
3. 內(nèi)存布局
4, tagged pointer
4.1 簡(jiǎn)介
看下面的介紹在使用tagged pointer之前, 對(duì)象的存儲(chǔ)是通過(guò)指針來(lái)指向的, 而在tagged pointer之后, 直接把對(duì)象存儲(chǔ)在指針中, 示意圖如下
下面我們來(lái)看一個(gè)面試題.
4.2面試題
下面我們直接說(shuō)結(jié)果吧:
第一個(gè)for循環(huán)會(huì)直接報(bào)錯(cuò)崩潰, 而且是地址錯(cuò)誤. 為什么呢? 主要原因有兩個(gè):
1,第一個(gè)跟tagged pointer相關(guān). 在這里, 由于字符串比較長(zhǎng), 所以第一個(gè)name會(huì)被runtime設(shè)置成非tagged pointer, 開(kāi)啟多線程頻繁訪問(wèn)name的時(shí)候, 在底層不停的[_name release]和_name=[name copy]的時(shí)候, 由于沒(méi)有加鎖, 所以可能會(huì)導(dǎo)致不同線程間銷毀原對(duì)象導(dǎo)致野指針錯(cuò)誤;
1, 第二個(gè)跟線程相關(guān), 就是上面提到的多線程讀寫操作.
如果要解決這個(gè)問(wèn)題, 就需要我們?cè)趎ame屬性前加atomic修飾, 而不是nonatomic. 或者對(duì)dispatch_queue進(jìn)行信號(hào)量限制為1.
第二個(gè)for循環(huán)不會(huì)報(bào)錯(cuò), 因?yàn)榈诙€(gè)name的字符串很短, runtime會(huì)將其設(shè)置成tagged pointer, 就不會(huì)存在上面的問(wèn)題, 讀寫值都非常高效.
5, 對(duì)象的內(nèi)存管理
5.1MRC
5.2 copy
一般調(diào)用copy的目的是為了獲得一個(gè)副本對(duì)象, 當(dāng)修改副本對(duì)象或者修改原對(duì)象的時(shí)候, 相互之間不影響. copy可分為深拷貝和淺拷貝, 深拷貝是產(chǎn)生了新的對(duì)象并分配了新的內(nèi)存地址, 淺拷貝則只是拷貝了指針指向原對(duì)象地址, 原對(duì)象和地址都沒(méi)有改變. 下面表格是拷貝的總結(jié):其規(guī)律就是對(duì)象和拷貝返回的對(duì)象不一致時(shí)候, 就需要深拷貝另外需要注意的兩點(diǎn)是:
1, 用copy修飾的屬性, 編譯器編譯后會(huì)在底層的setter方法內(nèi)部調(diào)用copy方法, 所以一般NSMutabelArray祝迂、NSDictionary等可變對(duì)象都不建議使用copy來(lái)修飾, 否則很容易在后續(xù)代碼中修改值的時(shí)候引起崩潰, 而且xcode也沒(méi)有提示警告;
2, 自定義的類如果想要調(diào)用copy方法, 則必須遵守<NSCopy>協(xié)議并且實(shí)現(xiàn)initWithZone:方法, 并且在方法中寫明需要copy的對(duì)象屬性等.
5.3weak指針的原理
直接總結(jié)如下:
1,當(dāng)一個(gè)對(duì)象被weak指針指向的時(shí)候, runtime會(huì)以這個(gè)對(duì)象的地址值作為key保存到sideTable類中的weak_table散列表中對(duì)應(yīng)的weak指針數(shù)組里;
2, 當(dāng)對(duì)象調(diào)用dealloc方法時(shí)候, 就會(huì)以對(duì)象地址作為key, 從sideTable的weak_table散列表中的weak數(shù)組遍歷逐個(gè)把weak設(shè)置為nil.