iOS進階面試題(二)

前10題面試題見iOS進階面試題(一)

面試題.png

11.用過 Swift 嗎?如何評價 String index 的設(shè)計?

解答:Q1略過
Q2:如何評價 String index 的設(shè)計 ?
字符串的索引:

  • 可擴展的字符群集可以組成一個或者多個 Unicode 標量。這意味著不同的字符以及相同字符的不同表示方式可能需要不同數(shù)量的內(nèi)存空間來存儲。所以 Swift 中的字符在一個字符串中并不一定占用相同的內(nèi)存空間數(shù)量。因此在沒有獲取字符串的可擴展的字符群的范圍時候,就不能計算出字符串的字符數(shù)量。如果您正在處理一個長字符串,需要注意characters屬性必須遍歷全部的 Unicode 標量,來確定字符串的字符數(shù)量。

  • 另外需要注意的是通過characters屬性返回的字符數(shù)量并不總是與包含相同字符的NSString的length屬性相同。NSString的length屬性是利用 UTF-16 表示的十六位代碼單元數(shù)字,而不是 Unicode 可擴展的字符群集。

  • 前面提到,不同的字符可能會占用不同數(shù)量的內(nèi)存空間,所以要知道Character的確定位置,就必須從String開頭遍歷每一個 Unicode 標量直到結(jié)尾。因此,Swift 的字符串不能用整數(shù)(integer)做索引。

  • 使用startIndex屬性可以獲取一個String的第一個Character的索引。使用endIndex屬性可以獲取最后一個Character的后一個位置的索引。因此,endIndex屬性不能作為一個字符串的有效下標。如果String是空串,startIndex和endIndex是相等的。

  • 通過調(diào)用 String 的 index(before:) 或 index(after:) 方法,可以立即得到前面或后面的一個索引。您還可以通過調(diào)用 index(_:offsetBy:) 方法來獲取對應偏移量的索引,這種方式可以避免多次調(diào)用 index(before:) 或 index(after:) 方法。

        let stringIndexStr = "hello word!"
        let starIndexC = stringIndexStr[stringIndexStr.startIndex]//獲取字符串第一個索引的 字符
        print("startIndexValue:\(stringIndexStr.startIndex)")//打印 startIndexValue:Index(_base: Swift.String.UnicodeScalarView.Index(_position: 0), _countUTF16: 1)
        
        print("starIndexCharacter:\(starIndexC)")//打印 starIndexCharacter:h
        
        //endIndex屬性不能作為一個字符串的有效下標,運行時會崩潰
        //let endIndexC = stringIndexStr[stringIndexStr.endIndex]//試圖獲取越界索引對應的 Character,將引發(fā)一個運行時錯誤。
        //print("endIndexCharacter:\(endIndexC)")
 

12.假設(shè) iPhone 上有一個與服務(wù)器的 TCP 連接,此時 iPhone 忽然斷網(wǎng),服務(wù)器能在短時間內(nèi)知會 iPhone 的離線嗎?

解答

  • 一般來說不能
  1. 中斷連接可以是客戶端,也可以是服務(wù)端。
    假設(shè)由客戶端中斷連接,即客戶端發(fā)起FIN報文中斷連接請求,告訴服務(wù)器“我沒有數(shù)據(jù)發(fā)給你了”。這時,客戶端進入FIN_WAIT_1狀態(tài)。服務(wù)器收到FIN報文后,如果還有數(shù)據(jù)沒有發(fā)送完成,則不會急著關(guān)閉SOCKET,會繼續(xù)發(fā)送數(shù)據(jù)。這時,服務(wù)器會發(fā)送ACK告訴客戶端“我收到你的斷開請求了,但是我還沒有準備好,請等我消息”。這時,客戶端就進入FIN_WAIT_2狀態(tài),繼續(xù)等待服務(wù)器的FIN報文。當服務(wù)器確認數(shù)據(jù)已經(jīng)發(fā)送完成,則向客戶端發(fā)送FIN報文,告訴客戶端“好了,我這邊的數(shù)據(jù)發(fā)完了,準備斷開連接”。客戶端收到FIN報文后就知道斷開連接,但是它不相信網(wǎng)絡(luò),怕服務(wù)器不知道斷開,所以發(fā)送ACK后進入TIME_WAIT狀態(tài)。如果服務(wù)器沒有收到ACK報文,則客戶端會繼續(xù)重傳ACK,服務(wù)器收到ACK報文后就知道可以斷開連接了。客戶端等待2MSL后依然沒有收到回復,則證明服務(wù)器已正常關(guān)閉。那好,我客戶端也可以關(guān)閉連接了。OK,TCP連接就這樣關(guān)閉了。

  2. 在服務(wù)器的TCP連接沒有完全斷開之前不允許重新監(jiān)聽,某些情況下可能是不合理的。
    【例】
    服務(wù)器需要處理大量的客戶端的連接,每個連接的生存時間可能很短,但是每秒都有很多客戶端來請求。這個時候,如果由服務(wù)器主動關(guān)閉連接,比如某些客戶端不活躍,就需要被服務(wù)器主動清理掉,這時會產(chǎn)生大量的TIME_WAIT連接。由于我們的請求量很大,就可能導致TIME_WAIT的連接數(shù)很多而導致服務(wù)器的端口不夠用,無法處理新的連接。

3.如何解決TIME_WAIT狀態(tài)引起的bind失敗?
答:使用setsockopt()設(shè)置socket描述符的選項SO_REUSEADDR為1,表示允許創(chuàng)建端口號相同,但是IP地址不同的多個socket描述符。即,在server代碼的socket函數(shù)和bind函數(shù)調(diào)用之間插入如下代碼:
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

最后, 我們來簡要說說另外一種網(wǎng)絡(luò)結(jié)構(gòu), 假設(shè)把pc1和pc2直接用網(wǎng)線相連, 建立起世界最小局域網(wǎng), 并形成tcp連接。如果在客戶端和服務(wù)端都沒有心跳機制,那么實驗結(jié)果如下
1. 如果斷掉其中的網(wǎng)線, 客戶端和服務(wù)端都沒有感知。
2. 客戶端突然斷電, 則服務(wù)端沒有感知。
3.服務(wù)端突然斷電, 則客戶端沒有感知。

13.為什么 Wireshark 不能直接抓取 SSL 的原始數(shù)據(jù)?

  1. 必要的加密解密基礎(chǔ)知識

1)對稱加密算法:就是加密和解密使用同一個密鑰的加密算法。因為加密方和解密方使用的密鑰相同,所以稱為稱為對稱加密,也稱為單鑰加密方法。

優(yōu)點是:加密和解密運算速度快,所以對稱加密算法通常在消息發(fā)送方需要加密大量數(shù)據(jù)時使用;

缺點是:安全性差,如果一方的密鑰遭泄露,那么整個通信就會被破解。另外加密之前雙方需要同步密鑰;

常用對稱加密算法有:DES、3DES、TDEA、Blowfish、RC2、RC4、RC5、IDEA、SKIPJACK、AES等;

2)非對稱加密算法:而非對稱加密算法需要兩個密鑰來進行加密和解密,這兩個秘鑰是公開密鑰(public key,簡稱公鑰)和私有密鑰(private key,簡稱私鑰)。

公鑰和私鑰是一對:公鑰用來加密,私鑰解密,而且公鑰是公開的,私鑰是自己保存的,不需要像對稱加密那樣在通信之前要先同步秘鑰。

有點是:安全性更好,私鑰是自己保存的,不需要像對稱加密那樣在通信之前要先同步秘鑰。

缺點是:加密和解密花費時間長、速度慢,只適合對少量數(shù)據(jù)進行加密。

常用的非對稱加密算法有:RSA、Elgamal、Rabin、D-H、ECC等;

3)HASH算法:也稱為消息摘要算法。將任意長度的二進制值映射為較短的固定長度的二進制值,該二進制值稱為哈希值。

常用于檢驗數(shù)據(jù)的完整性,檢驗數(shù)據(jù)沒有被篡改過。常見的又 MD5(MD系列),SHA-1(SHA系列)

HTTPS 使用到了上面全部三種加密算法。

  1. HTTPS 的作用

HTTPS簡單而言,即使建立在SSL/TLS協(xié)議之上的HTTP。不使用SSL/TLS的HTTP通信,就是不加密的通信。所有信息明文傳播,帶來了三大風險。
(1) 竊聽風險(eavesdropping):第三方可以獲知通信內(nèi)容。
(2) 篡改風險(tampering):第三方可以修改通信內(nèi)容。
(3) 冒充風險(pretending):第三方可以冒充他人身份參與通信。

SSL/TLS協(xié)議是為了解決這三大風險而設(shè)計的,希望達到:
(1) 所有信息都是加密傳播,第三方無法竊聽。
(2) 具有校驗機制,一旦被篡改,通信雙方會立刻發(fā)現(xiàn)。
(3) 配備身份證書,防止身份被冒充。

互聯(lián)網(wǎng)是開放環(huán)境,通信雙方都是未知身份,這為協(xié)議的設(shè)計帶來了很大的難度。而且,協(xié)議還必須能夠經(jīng)受所有匪夷所思的攻擊,這使得SSL/TLS協(xié)議變得異常復雜。

4. 基本的運行過程
HTTPS 的基本運行過程:
1)利用對稱加密算法來加密網(wǎng)頁內(nèi)容,那么如何保證對稱加密算法的秘鑰的安全呢?
2)使用非對稱加密算法來獲得對稱加密算法的秘鑰,從而保證了對稱加密算法的秘鑰的安全,也就保證了對稱加密算法的安全
這里這樣安排使用的原理是,利用了對稱加密算法和非對稱加密算法優(yōu)點,而避免了它們的缺點。利用了對稱加密算法速度快,而非對稱加密算法安全的優(yōu)點;同時巧妙的避免了對稱加密算法的不安全性,以及需要同步密鑰的缺點,也避免了非對稱加密算法的速度慢的缺點。實在是巧妙了。

這里有兩個問題:
(1)如何保證非對稱加密算法公鑰不被篡改?
解決方法:將公鑰放在數(shù)字證書中。只要證書是可信的,公鑰就是可信的。
(2)公鑰加密計算量太大,如何減少耗用的時間?
解決方法:每一次對話(session),客戶端和服務(wù)器端都生成一個"對話密鑰"(session key),用它來加密信息。由于"對話密鑰"是對稱加密算法,所以運算速度非常快,而服務(wù)器公鑰只用于加密"對話密鑰"本身,這樣就減少了加密運算的消耗時間。(也就是網(wǎng)頁內(nèi)容的加密使用的是對稱加密算法)

因此,SSL/TLS協(xié)議的基本過程是這樣的:

(1) 客戶端向服務(wù)器端索要并驗證非對稱加密算法的公鑰。
(2) 雙方協(xié)商生成對稱加密算法的"對話密鑰"。
(3) 雙方采用對稱加密算法和它的"對話密鑰"進行加密通信。

上面過程的前兩步,又稱為"握手階段"(handshake)。

5. 握手階段的詳細過程
"握手階段"涉及四次通信,我們一個個來看。需要注意的是,"握手階段"的所有通信都是明文的。

1) 客戶端發(fā)出請求(ClientHello)
首先,客戶端(通常是瀏覽器)先向服務(wù)器發(fā)出加密通信的請求,這被叫做ClientHello請求。
在這一步,客戶端主要向服務(wù)器提供以下信息。

(1) 瀏覽器支持的SSL/TLS協(xié)議版本,比如TLS 1.0版。
(2) 一個瀏覽器客戶端生成的隨機數(shù),稍后用于生成對稱加密算法的"對話密鑰"。
(3) 瀏覽器支持的各種加密方法,對稱的,非對稱的,HASH算法。比如RSA非對稱加密算法,DES對稱加密算法,SHA-1 hash算法。
(4) 瀏覽器支持的壓縮方法。

這里需要注意的是,客戶端發(fā)送的信息之中不包括服務(wù)器的域名。也就是說,理論上服務(wù)器只能包含一個網(wǎng)站,否則會分不清應該向客戶端提供哪一個網(wǎng)站的數(shù)字證書。這就是為什么通常一臺服務(wù)器只能有一張數(shù)字證書的原因。

對于虛擬主機的用戶來說,這當然很不方便。2006年,TLS協(xié)議加入了一個Server Name Indication擴展,允許客戶端向服務(wù)器提供它所請求的域名。

2) 服務(wù)器回應(SeverHello)
服務(wù)器收到客戶端請求后,向客戶端發(fā)出回應,這叫做SeverHello。服務(wù)器的回應包含以下內(nèi)容。

(1) 確認使用的加密通信協(xié)議版本,比如TLS 1.0版本。如果瀏覽器與服務(wù)器支持的版本不一致,服務(wù)器關(guān)閉加密通信。
(2) 一個服務(wù)器生成的隨機數(shù),稍后用于生成對稱加密算法的"對話密鑰"。
(3) 確認使用的各種加密方法,比如確認算法使用:RSA非對稱加密算法,DES對稱加密算法,SHA-1 hash算法
(4) 服務(wù)器證書。

除了上面這些信息,如果服務(wù)器需要確認客戶端的身份,就會再包含一項請求,要求客戶端提供"客戶端證書"。比如,金融機構(gòu)往往只允許認證客戶連入自己的網(wǎng)絡(luò),就會向正式客戶提供USB密鑰,里面就包含了一張客戶端證書。

3) 客戶端回應

客戶端收到服務(wù)器回應以后,首先驗證服務(wù)器證書。如果證書不是可信機構(gòu)頒布、或者證書中的域名與實際域名不一致、或者證書已經(jīng)過期,就會向訪問者顯示一個警告,由其選擇是否還要繼續(xù)通信。

如果證書沒有問題,客戶端就會從證書中取出服務(wù)器的非對稱加密算法的公鑰。然后,向服務(wù)器發(fā)送下面三項信息。

(1) 一個隨機數(shù)。該隨機數(shù)用服務(wù)器發(fā)來的公鑰進行的使用非對稱加密算法加密,防止被竊聽。
(2) 編碼改變通知,表示隨后的信息都將用雙方商定的加密方法和密鑰發(fā)送(比如確認使用:RSA非對稱,DES對稱,SHA-1 hash算法)。
(3) 客戶端握手結(jié)束通知,表示客戶端的握手階段已經(jīng)結(jié)束。這一項同時也是前面發(fā)送的所有內(nèi)容的hash值,用來供服務(wù)器校驗。

上面第一項的隨機數(shù),是整個握手階段出現(xiàn)的第三個隨機數(shù),又稱"pre-master key"。有了它以后,客戶端和服務(wù)器就同時有了三個隨機數(shù),接著雙方就用事先商定的對稱加密算法,各自生成本次會話所用的同一把"會話密鑰"。也就是說瀏覽器和服務(wù)器各自使用同一個對稱加密算法,對三個相同的隨機數(shù)進行加密,獲得了,用來加密網(wǎng)頁內(nèi)容的 對稱加密算法的秘鑰。(注意:這里瀏覽器的三個隨機數(shù)都是明文的,但是服務(wù)端獲得的"pre-master key"是密文的,所以服務(wù)器需要使用非對稱加密算法的私鑰,來先解密獲得"pre-master key"的明文,在來生成對稱加密算法的秘鑰。這樣的目的是為了防止:"pre-master key"被竊聽,因為發(fā)送明文會被竊聽,但是發(fā)生的是非對稱加密算法的加密過后的密文,因為竊聽者不知道私鑰,所以即使竊聽了,也無法解密出其對應的明文。從而保證了最后生成的:用于加密網(wǎng)頁內(nèi)容的對稱加密算法的秘鑰的安全性!!!)

至于為什么一定要用三個隨機數(shù),來生成"會話密鑰"?

"不管是客戶端還是服務(wù)器,都需要隨機數(shù),這樣生成的密鑰才不會每次都一樣。由于SSL協(xié)議中證書是靜態(tài)的,因此十分有必要引入一種隨機因素來保證協(xié)商出來的密鑰的隨機性。

對于RSA密鑰交換算法來說,pre-master-key本身就是一個隨機數(shù),再加上hello消息中的隨機,三個隨機數(shù)通過一個密鑰導出器最終導出一個對稱密鑰。

pre master的存在在于SSL協(xié)議不信任每個主機都能產(chǎn)生完全隨機的隨機數(shù),如果隨機數(shù)不隨機,那么pre master secret就有可能被猜出來,那么僅適用pre master secret作為密鑰就不合適了,因此必須引入新的隨機因素,那么客戶端和服務(wù)器加上pre master secret三個隨機數(shù)一同生成的密鑰就不容易被猜出了,一個偽隨機可能完全不隨機,可是是三個偽隨機就十分接近隨機了,每增加一個自由度,隨機性增加的 可不是一。"

這里:其實如果 pre-master-key 和 瀏覽器生成的隨機數(shù)都可能被猜出來,那么最后生成的對稱加密算法的秘鑰就是不安全的。因為三個隨機數(shù)都可能被竊聽到了。

此外,如果前一步,服務(wù)器要求客戶端證書,客戶端會在這一步發(fā)送證書及相關(guān)信息。

4) 服務(wù)器的最后回應

服務(wù)器收到客戶端的第三個隨機數(shù)pre-master key之后,計算生成本次會話所用的對稱加密算法的"會話密鑰"。然后,向客戶端最后發(fā)送下面信息。

(1)編碼改變通知,表示隨后的信息都將用雙方商定的對稱加密算法和密鑰進行加密。

(2)服務(wù)器握手結(jié)束通知,表示服務(wù)器的握手階段已經(jīng)結(jié)束。這一項同時也是前面發(fā)送的所有內(nèi)容的hash值,用來供客戶端校驗。

至此,整個握手階段全部結(jié)束。接下來,客戶端與服務(wù)器進入加密通信,就完全是使用普通的HTTP協(xié)議,只不過用"會話密鑰"加密內(nèi)容。

總結(jié)

1)HTTPS 結(jié)合使用了 非對稱加密算法,對稱加密算法,hash算法,分別利用他們的優(yōu)勢,避免他們的缺點。利用非對稱加密算法獲得對稱加密算法的秘鑰,保證他的安全性;然后實際的網(wǎng)頁內(nèi)容的加密使用的是對稱加密算法,利用了對稱加密算法速度快的優(yōu)勢,hash算法主要是防止篡改的發(fā)生,是一種校驗機制,最后數(shù)字證書,保證了服務(wù)器在將非對稱加密算法的公鑰傳給瀏覽器時的安全性(不會被中間人篡改),同時也標志了服務(wù)器的身份

2)HTTPS的四大金剛:

非對稱加密算法(對稱加密算法的秘鑰) + 對稱加密算法(加密內(nèi)容) + 數(shù)字證書(防止篡改非對稱加密算法的公鑰) + HASH算法(防止篡改消息)== HTTPS

3)HTTPS的本質(zhì)是什么?

HTTPS的本質(zhì)就是在HTTP連接發(fā)起之前,先使用SSL/TLS協(xié)議,協(xié)調(diào)客戶端和服務(wù)端,在兩端各自生產(chǎn)一個對稱加密算法的秘鑰,

然后使用普通的HTTP協(xié)議傳輸 經(jīng)過對稱加密算法加密的網(wǎng)頁內(nèi)容。因為對稱加密算法的秘鑰是安全的,所以對稱加密算法加密的網(wǎng)頁內(nèi)容也是安全的。

14.backtrace 是在用戶態(tài)實現(xiàn)的嗎?能否講講實現(xiàn)它的大致思路?

顯示函數(shù)調(diào)用關(guān)系(backtrace/callstack)是調(diào)試器必備的功能之一,比如在gdb里,用bt命令就可以查看backtrace。在程序崩潰的時候,函數(shù)調(diào)用關(guān)系有助于快速定位問題的根源,了解它的實現(xiàn)原理,可以擴充自己的知識面,在沒有調(diào)試器的情況下,也能實現(xiàn)自己backtrace。更重要的是,分析backtrace的實現(xiàn)原理很有意思。現(xiàn)在我們一起來研究一下:

glibc提供了一個backtrace函數(shù),這個函數(shù)可以幫助我們獲取當前函數(shù)的backtrace,先看看它的使用方法,后面我們再仿照它寫一個。

#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h> 

#define MAX_LEVEL 4 

static void test2()
{
    int i = 0;
    void* buffer[MAX_LEVEL] = {0}; 

    int size = backtrace(buffer, MAX_LEVEL); 

    for(i = 0; i < size; i++)
    {
        printf("called by %p/n",    buffer[i]);
    } 

    return;
} 

static void test1()
{
    int a=0x11111111;
    int b=0x11111112; 

    test2();
    a = b; 

    return;
} 

static void test()
{
    int a=0x10000000;
    int b=0x10000002; 

    test1();
    a = b; 

    return;
} 

int main(int argc, char* argv[])
{
    test(); 

    return 0;
}

編譯運行它:
gcc -g -Wall bt_std.c -o bt_std
./bt_std

屏幕打印:
called by 0×8048440
called by 0×804848a
called by 0×80484ab
called by 0×80484c9

上面打印的是調(diào)用者的地址,對程序員來說不太直觀,glibc還提供了另外一個函數(shù)backtrace_symbols,它可以把這些地址轉(zhuǎn)換成源代碼的位置(通常是函數(shù)名)。不過這個函數(shù)并不怎么好用,特別是在沒有調(diào)試信息的情況下,幾乎得不什么有用的信息。這里我們使用另外一個工具addr2line來實現(xiàn)地址到源代碼位置的轉(zhuǎn)換:

運行:
./bt_std |awk ‘{print “addr2line “$3″ -e bt_std”}’>t.sh;. t.sh;rm -f t.sh

屏幕打印:
/home/work/mine/sysprog/think-in-compway/backtrace/bt_std.c:12
/home/work/mine/sysprog/think-in-compway/backtrace/bt_std.c:28
/home/work/mine/sysprog/think-in-compway/backtrace/bt_std.c:39
/home/work/mine/sysprog/think-in-compway/backtrace/bt_std.c:48

backtrace是如何實現(xiàn)的呢? 在x86的機器上,函數(shù)調(diào)用時,棧中數(shù)據(jù)的結(jié)構(gòu)如下:


參數(shù)N
參數(shù)… 函數(shù)參數(shù)入棧的順序與具體的調(diào)用方式有關(guān)
參數(shù) 3
參數(shù) 2
參數(shù) 1


EIP 完成本次調(diào)用后,下一條指令的地址
EBP 保存調(diào)用者的EBP,然后EBP指向此時的棧頂。
----------------新的EBP指向這里---------------
臨時變量1
臨時變量2
臨時變量3
臨時變量…
臨時變量5


(說明:下面低是地址,上面是高地址,棧向下增長的)

調(diào)用時,先把被調(diào)函數(shù)的參數(shù)壓入棧中,C語言的壓棧方式是:先壓入最后一個參數(shù),再壓入倒數(shù)第二參數(shù),按此順序入棧,最后才壓入第一個參數(shù)。

然后壓入EIP和EBP,此時EIP指向完成本次調(diào)用后下一條指令的地址 ,這個地址可以近似的認為是函數(shù)調(diào)用者的地址。EBP是調(diào)用者和被調(diào)函數(shù)之間的分界線,分界線之上是調(diào)用者的臨時變量、被調(diào)函數(shù)的參數(shù)、函數(shù)返回地址(EIP),和上一層函數(shù)的EBP,分界線之下是被調(diào)函數(shù)的臨時變量。

最后進入被調(diào)函數(shù),并為它分配臨時變量的空間。gcc不同版本的處理是不一樣的,對于老版本的gcc(如gcc3.4),第一個臨時變量放在最高的地址,第二個其次,依次順序分布。而對于新版本的gcc(如gcc4.3),臨時變量的位置是反的,即最后一個臨時變量在最高的地址,倒數(shù)第二個其次,依次順序分布。

為了實現(xiàn)backtrace,我們需要:

1.獲取當前函數(shù)的EBP。
2.通過EBP獲得調(diào)用者的EIP。
3.通過EBP獲得上一級的EBP。
4.重復這個過程,直到結(jié)束。

通過嵌入?yún)R編代碼,我們可以獲得當前函數(shù)的EBP,不過這里我們不用匯編,而且通過臨時變量的地址來獲得當前函數(shù)的EBP。我們知道,對于gcc3.4生成的代碼,當前函數(shù)的第一個臨時變量的下一個位置就是EBP。而對于gcc4.3生成的代碼,當前函數(shù)的最后一個臨時變量的下一個位置就是EBP。

有了這些背景知識,我們來實現(xiàn)自己的backtrace:

#ifdef NEW_GCC
#define OFFSET 4
#else
#define OFFSET 0
#endif/*NEW_GCC*/ 

int backtrace(void** buffer, int size)
{
    int  n = 0xfefefefe;
    int* p = &n;
    int  i = 0; 

    int ebp = p[1 + OFFSET];
    int eip = p[2 + OFFSET]; 

    for(i = 0; i < size; i++)
    {
        buffer[i] = (void*)eip;
        p = (int*)ebp;
        ebp = p[0];
        eip = p[1];
    } 

    return size;
}

對于老版本的gcc,OFFSET定義為0,此時p+1就是EBP,而p[1]就是上一級的EBP,p[2]是調(diào)用者的EIP。本函數(shù)總共有5個int的臨時變量,所以對于新版本gcc, OFFSET定義為5,此時p+5就是EBP。在一個循環(huán)中,重復取上一層的EBP和EIP,最終得到所有調(diào)用者的EIP,從而實現(xiàn)了backtrace。

現(xiàn)在我們用完整的程序來測試一下(bt.c):

寫個簡單的Makefile:

#include <stdio.h> 

#define MAX_LEVEL 4
#ifdef NEW_GCC
#define OFFSET 4
#else
#define OFFSET 0
#endif/*NEW_GCC*/ 

int backtrace(void** buffer, int size)
{
    int     n = 0xfefefefe;
    int* p = &n;
    int     i = 0; 

    int ebp = p[1 + OFFSET];
    int eip = p[2 + OFFSET]; 

    for(i = 0; i < size; i++)
    {
        buffer[i] = (void*)eip;
        p = (int*)ebp;
        ebp = p[0];
        eip = p[1];
    } 

    return size;
} 

static void test2()
{
    int i = 0;
    void* buffer[MAX_LEVEL] = {0}; 

    backtrace(buffer, MAX_LEVEL); 

    for(i = 0; i < MAX_LEVEL; i++)
    {
        printf("called by %p/n",    buffer[i]);
    } 

    return;
} 

static void test1()
{
    int a=0x11111111;
    int b=0x11111112; 

    test2();
    a = b; 

    return;
} 

static void test()
{
    int a=0x10000000;
    int b=0x10000002; 

    test1();
    a = b; 

    return;
} 

int main(int argc, char* argv[])
{
    test(); 

    return 0;
}

CFLAGS=-g -Wall
all:
    gcc34 $(CFLAGS) bt.c -o bt34
    gcc $(CFLAGS) -DNEW_GCC  bt.c -o bt
    gcc $(CFLAGS) bt_std.c -o bt_std 

clean:
    rm -f bt bt34 bt_std

編譯然后運行:
make
./bt|awk ‘{print “addr2line “$3″ -e bt”}’>t.sh;. t.sh;

屏幕打印:
/home/work/mine/sysprog/think-in-compway/backtrace/bt.c:37
/home/work/mine/sysprog/think-in-compway/backtrace/bt.c:51
/home/work/mine/sysprog/think-in-compway/backtrace/bt.c:62
/home/work/mine/sysprog/think-in-compway/backtrace/bt.c:71

對于可執(zhí)行文件,這種方法工作正常。對于共享庫,addr2line無法根據(jù)這個地址找到對應的源代碼位置了。原因是:addr2line只能通過地址偏移量來查找,而打印出的地址是絕對地址。由于共享庫加載到內(nèi)存的位置是不確定的,為了計算地址偏移量,我們還需要進程maps文件的幫助:

通過進程的maps文件(/proc/進程號/maps),我們可以找到共享庫的加載位置,如:

00c5d000-00c5e000 r-xp 00000000 08:05 2129013 /home/work/mine/sysprog/think-in-compway/backtrace/libbt_so.so
00c5e000-00c5f000 rw-p 00000000 08:05 2129013 /home/work/mine/sysprog/think-in-compway/backtrace/libbt_so.so

libbt_so.so的代碼段加載到0×00c5d000-0×00c5e000,而backtrace打印出的地址是:
called by 0xc5d4eb
called by 0xc5d535
called by 0xc5d556
called by 0×80484ca

這里可以用打印出的地址減去加載的地址來計算偏移量。如,用 0xc5d4eb減去加載地址0×00c5d000,得到偏移量0×4eb,然后把0×4eb傳給addr2line:
addr2line 0×4eb -f -s -e ./libbt_so.so

15.malloc 的指針 double free 產(chǎn)生的異常與訪問 freed 指針有可能產(chǎn)生的異常有什么區(qū)別?為什么訪問 freed 指針不一定產(chǎn)生異常?

Double Free其實就是同一個指針free兩次。雖然一般把它叫做double free。其實只要是free一個指向堆內(nèi)存的指針都有可能產(chǎn)生可以利用的漏洞。
double free的原理其實和堆溢出的原理差不多,都是通過unlink這個雙向鏈表刪除的宏來利用的。只是double free需要由自己來偽造整個chunk并且欺騙操作系統(tǒng)。實現(xiàn)對漏洞的有效利用,攻擊者利用成功可導致權(quán)限提升。

一、內(nèi)存問題歸類
1、野指針。 指針對象指向了無效的地址,這個地址被其它對象持有了,已經(jīng)屬于其它對象; 或者還有一種可能, 這塊內(nèi)存已經(jīng)被 系統(tǒng)回收了。 總而言之, 指針指向的內(nèi)存塊不能用了,要么是被別人用了,要么是被系統(tǒng)回收了

2、Method cache corrupted. This may be a message to an invalid object, or a memory error somewhere else. 這其實也是內(nèi)存問題的一種。 這個對象你明明申請了空間,現(xiàn)在卻說你這個對象是個無效的對象,這說明你這個對象在創(chuàng)建的時候,占用的內(nèi)存塊有問題,在內(nèi)存上發(fā)生了沖突。 舉例: 你調(diào)用一個系統(tǒng)api,將一個結(jié)構(gòu)體里的buffer數(shù)據(jù),轉(zhuǎn)為另一個結(jié)構(gòu)體。 你在初始化目標結(jié)構(gòu)體的時候,給結(jié)構(gòu)體里的data申請了1024個字節(jié),可是系統(tǒng)api中,有參數(shù)指定了要拷貝4000個字節(jié)的源結(jié)構(gòu)體的data到目標結(jié)構(gòu)體。 這個時候, 目標結(jié)構(gòu)體就會多去占用2976個字節(jié)的空間,可是你卻只申請了1024個字節(jié),這樣,當你程序在運行的時候,創(chuàng)建對象的時候,如果又去指向那多占的2976個內(nèi)存空間, 就會出現(xiàn)對象無效。

3、 incorrect checksum for freed object - object was probably modified after being freed. 對象被釋放后,又進行了修改。

4、重復釋放。 一個對象的內(nèi)存已經(jīng)被釋放了一次,又再一次去釋放。 總而言之;都是內(nèi)存管理不當。

16.RunLoop 是一個不停歇在運行的死循環(huán)嗎?為什么?

1.RunLoop的基本作用

1.保持程序的持續(xù)運行
2.處理app中的各種事件(比如觸摸事件、定時器事件、selector事件
3.節(jié)省CPU資源,提高程序性能,有事情就做事情,沒事情就休息

2.RunLoop與線程

1.關(guān)系:一個Runloop對應著一條唯一的線程
2.創(chuàng)建:主線程Runloop已經(jīng)創(chuàng)建好了,子線程的runloop需要手動創(chuàng)建 創(chuàng)建方式:

NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];

子線程創(chuàng)建的runloop還需要手動的開啟:

[currentRunloop run];

3.生命周期:Runloop在第一次獲取時創(chuàng)建,在線程結(jié)束時銷毀

3.RunLoop運行模式對NSTimer定時器的影響

1.首先創(chuàng)建一個定時器

 NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(task) userInfo:nil repeats:YES];

這里的task為一個普通的打印操作,這時候往控制器的view里拖一個Text View,

2.把定時器對象添加到runloop中,并且制定runloop的運行模式為默認:只有當runloop的運行模式為NSDefaultRunLoopMode的時候定時器才工作,也就是說這時候如果滑動Text View,定時器就不工作了

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

3.如果想在滾動Text View的時候,定時器也工作,可以:

[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

但是如果這樣做的話,當我們停止?jié)L動的時候定時器又不工作了

4.有時候我們需要在默認情況下以及在滾動的時候都讓定時器工作,這時候我們就可以:

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

由此可見:NSRunLoopCommonModes = NSDefaultRunLoopMode & UITrackingRunLoopMode

拓展:
①scheduledTimerWithTimeInterval方法:創(chuàng)建定時器并默認添加到當前線程的Runloop中指定默認運行模式

②timerWithTimeInterval:創(chuàng)建定時器,如果該定時器要工作還需要添加到runloop中并指定相應的運行模式

通過以上代碼我們不難看出,NSTimer的定時器是受運行模式影響的,,而開發(fā)中我們有時候徹底去除這種影響,很顯然,NSTimer定時器不能做到這點,這時,我們可以使用GCD的定時器。

5.GCD定時器

GCD定時器是不受運行模式的影響的,因此,以后盡量使用此定時器,,該定時器的具體參數(shù)如下所示:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //01 創(chuàng)建定時器對象
    /*
     第一個參數(shù):是一個宏,表示要創(chuàng)建的是一個定時器
     第二個參數(shù)和第三個參數(shù):默認總是傳0 描述信息
     第四個參數(shù):隊列  決定代碼塊(dispatch_source_set_event_handler)在哪個線程中調(diào)用(主隊列-主線程)
     */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));

    //02 設(shè)置定時器
    /*
     第一個參數(shù):定時器對象
     第二個參數(shù):定時器開始計時的時間(開始時間)
     第三個參數(shù):設(shè)置間隔時間 GCD的時間單位:納秒
     第四個參數(shù):精準度
     */
    //這句代碼是設(shè)置開始兩秒之后再調(diào)用
    dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC);
    dispatch_source_set_timer(timer, t, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);

    //03 GCD定時器時間到了之后要執(zhí)行的任務(wù)
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"GCD----%@",[NSThread currentThread]);
    });

    //04 默認是在掛起狀態(tài),需要手動恢復執(zhí)行
    dispatch_resume(timer);

    //如果沒有一個強指針指向,一創(chuàng)建就回被釋放。
    self.timer = timer;

}
6. CFRunLoopModeRef
  • a.基本說明:
    CFRunloopModeRef代表著Runloop的運行模式
    一個Runloop中可以有多個mode,一個mode里面又可以有多個source\observer\timer等等
    每次runloop啟動的時候,只能指定一個mode,這個mode被稱為該Runloop的當前mode
    如果需要切換mode,只能先退出當前Runloop,再重新指定一個mode進入
    這樣做主要是為了分割不同組的定時器等,讓他們相互之間不受影響
  • b.Model的分類:
    kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
    UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動
    UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用
    GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到
    kCFRunLoopCommonModes: 這是一個占位用的Mode,不是一種真正的Mode
7.CFRunloopSourceRef

分類(根據(jù)函數(shù)調(diào)用棧來區(qū)分):
1.Source0:非基于Port的 :凡是用戶主動觸發(fā)事件都是Source0事件
2.Source1:基于Port的:凡是系統(tǒng)事件都是Source1事件

8. CFRunLoopObserverRef

作用:監(jiān)聽運行循環(huán)的狀態(tài)

9. selecter事件與RunLoop之間的關(guān)系

默認情況下,selecter事件是被添加到當前的runloop中執(zhí)行的,并且指定了運行模式為默認,由此可見,performSelecter事件是受運行模式的影響的,仔細查看以下代碼,看看有什么問題:

    [NSThread detachNewThreadSelector:@selector(task) toTarget:self withObject:nil];

    -(void)task{
    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"Snip20160713_9"] afterDelay:3.0];
    NSLog(@"+++++");
}

一個顯而易見的問題就是我們在子線程中來設(shè)置顯示圖片,然而拋開這個問題不管,圖片依舊不會被設(shè)置上去,因為在task中缺少一個運行循環(huán),我們需要手動開啟一個子運行循環(huán)才可以。

繼續(xù)查看一下代碼,圖片會被設(shè)置到imageView上面嗎?

    [NSThread detachNewThreadSelector:@selector(task) toTarget:self withObject:nil];

    -(void)task{
    NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    [runloop run];

    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"Snip20160713_9"] afterDelay:3.0];
    NSLog(@"+++++");
}

答案是否定的,因為我們雖然開啟了子運行循環(huán),但是當我們開啟這個循環(huán)的時候,當前循環(huán)里既沒有source事件(包括timer事件),也沒有selecter事件,于是循環(huán)立刻就退出了。正確的書寫方式如下:

    [NSThread detachNewThreadSelector:@selector(task) toTarget:self withObject:nil];

    -(void)task{
    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"Snip20160713_9"] afterDelay:3.0];
      NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    [runloop run];
    NSLog(@"+++++");
}

注意:runLoop是一個死循環(huán),因此++++是不會打印的

10.使用RunLoop實現(xiàn)常駐線程

眾所周知,我們手動開啟的子線程在執(zhí)行完任務(wù)之后就會銷毀,而有時候我們需要一個子線程在執(zhí)行完當前任務(wù)后,不要銷毀,等我們需要的時候再來執(zhí)行其它任務(wù),這就用到了常駐線程。

  • 假設(shè)我們又這樣一個需求,但我們點擊按鈕1的時候會開啟一條子線程來執(zhí)行run1任務(wù),當我們點擊按鈕2的時候,再讓剛才的線程來執(zhí)行run2任務(wù),具體實現(xiàn)代碼如下:
- (IBAction)btn1:(id)sender {
    //01 創(chuàng)建線程,執(zhí)行任務(wù)
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run1) object:nil];

    //02 執(zhí)行任務(wù)
    [thread start];

    self.thread = thread;
}

為了不讓剛開啟的線程銷毀,我們需要給它添加一個運行循環(huán),保證它不釋放:

-(void)run1
{
    NSLog(@"run1---%@",[NSThread currentThread]);
    //001 獲得當前線程對應的runloop對象
    NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];

    //002 為runloop添加input soucre或者是timer souce或selecter事件(最好就是一個基于端口的事件,這樣就不會去執(zhí)行不必要的方法)
    [currentRunloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];

//以下為其它保證程序運行的方案,不推薦使用。
    //[self performSelector:@selector(run3) withObject:nil afterDelay:3.0];
    //[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];

    //003 啟動runloop
    //runUntilDate |run 內(nèi)部都指定了運行模式為默認
    [currentRunloop run];

}

按鈕2:

- (IBAction)goOnBtnClick:(id)sender {
    //讓之前創(chuàng)建的線程繼續(xù)執(zhí)行
    [self performSelector:@selector(run2) onThread:self.thread withObject:nil waitUntilDone:YES];
}

-(void)run2
{
    NSLog(@"run2---%@",[NSThread currentThread]);
}
11.RunLoop的自動釋放池

runloop的自動釋放池什么時候創(chuàng)建釋放?

  1. 當runloop進入的時候會創(chuàng)建一個自動釋放池。
    2)當runloop退出的時候會把之前的自動釋放池銷毀。
    3)當runloop即將進入休眠的時候會把之前的自動釋放池先銷毀,然后創(chuàng)建一個新的自動釋放池。


    image.png

17.看過 runtime 的源碼嗎?源碼中常有的 fastpath、slowpath 是什么?

fastpath和slowpath的區(qū)別在于,fastpath要求zone的unmapped file page必須大于zone規(guī)定的min_unmapped_pages,slab reclaimable大于min_slab_pages,回收內(nèi)存的的頁數(shù)為2^order個頁數(shù)和是32個之間的最大值,而kswapd回收內(nèi)存需要回收到所有的zone都滿足free page大于high watermark值或者zone的high watermark值個頁面。同時fastpath不會進行回寫,也不會回收mapped的page。fastpath主要靠zone_reclaim來完成快速的內(nèi)存回收。kswap則主要從balance_pgdat()來完成

direct reclaim和kswapd的差別在于只能回收32個頁面,同時kswapd進行回寫頁面需要滿足特定條件(當前有很多的頁面需要等待writeback),而direct reclaim判斷當回收過程中掃描的總頁數(shù)大于48個時就會啟動flush線程來進行臟頁的回寫操作。

我們在調(diào)性能時候都會盡量的提高extra_free_kbytes的值來防止過多的出現(xiàn)direct reclaim,因為direct reclaim會進行臟頁的回寫,這里再IO性能不是很好的時候會造成系統(tǒng)嚴重卡頓。

18.runtime 中 SideTables(不是 SideTable)存在的意義是什么?

1.數(shù)據(jù)結(jié)構(gòu)分析(* SideTables、RefcountMap、weak_table_t*)

為了管理所有對象的引用計數(shù)和weak指針,蘋果創(chuàng)建了一個全局的SideTables,雖然名字后面有個"s"不過他其實是一個全局的Hash表,里面的內(nèi)容裝的都是SideTable結(jié)構(gòu)體而已。它使用對象的內(nèi)存地址當它的key。管理引用計數(shù)和weak指針就靠它了。
????因為對象引用計數(shù)相關(guān)操作應該是原子性的。不然如果多個線程同時去寫一個對象的引用計數(shù),那就會造成數(shù)據(jù)錯亂,失去了內(nèi)存管理的意義。同時又因為內(nèi)存中對象的數(shù)量是非常非常龐大的需要非常頻繁的操作SideTables,所以能對整個Hash表加鎖。蘋果采用了分離鎖技術(shù)。

image.png

舉例分析
假設(shè)有80個學生需要咱們安排住宿,同時還要保證學生們的財產(chǎn)安全。應該怎么安排?
顯然不會給80個學生分別安排80間宿舍,然后給每個宿舍的大門上加一把鎖。那樣太浪費資源了鎖也挺貴的,太多的宿舍維護起來也很費力氣。
我們一般的做法是把80個學生分配到10間宿舍里,每個宿舍住8個人。假設(shè)宿舍號分別是101、102 、... 110。然后再給他們分配床位,01號床、02號床等。然后給每個宿舍配一把鎖來保護宿舍內(nèi)同學的財產(chǎn)安全。為什么不只給整個宿舍樓上一把鎖,每次有人進出的時候都把整個宿舍樓鎖上?顯然這樣會造成宿舍樓大門口阻塞。
OK假如現(xiàn)在有人要找102號宿舍的2號床的人聊天。這個人會怎么做?

1、找到宿舍樓(SideTables)的宿管,跟他說自己要找10202(內(nèi)存地址當做key)。
2、宿管帶著他SideTables[10202]找到了102宿舍SideTable,然后把102的門一鎖lock,在他訪問102期間不再允許其他訪客訪問102了。(這樣只是阻塞了102的8個兄弟的訪問,而不會影響整棟宿舍樓的訪問)
3、然后在宿舍里大喊一聲:"2號床的兄弟在哪里?"table.refcnts.find(02)你就可以找到2號床的兄弟了。
4、等這個訪客離開的時候會把房門的鎖打開unlock,這樣其他需要訪問102的人就可以繼續(xù)進來訪問了。

SideTables == 宿舍樓
SideTable == 宿舍
RefcountMap里存放著具體的床位

蘋果之所以需要創(chuàng)造SideTables的Hash沖突是為了把對象放到宿舍里管理,把鎖的粒度縮小到一個宿舍SideTable。RefcountMap的工作是在找到宿舍以后幫助大家找到正確的床位的兄弟。

2. SideTable

當我們通過SideTables[key]來得到SideTable的時候
1.一把自旋鎖。spinlock_t??slock;

自旋鎖比較適用于鎖使用者保持鎖時間比較短的情況。正是由于自旋鎖使用者一般保持鎖時間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠高于互斥鎖。信號量和讀寫信號量適合于保持時間較長的情況,它們會導致調(diào)用者睡眠,因此只能在進程上下文使用,而自旋鎖適合于保持時間非常短的情況,它可以在任何上下文使用。

它的作用是在操作引用技術(shù)的時候?qū)ideTable加鎖,避免數(shù)據(jù)錯誤。

2.引用計數(shù)器 RefcountMap??refcnts;
對象具體的引用計數(shù)數(shù)量是記錄在這里的。
????這里注意RefcountMap其實是個C++的Map。為什么Hash以后還需要個Map?其實蘋果采用的是分塊化的方法。
????舉個例子
????假設(shè)現(xiàn)在內(nèi)存中有16個對象。
0x0000、0x0001、...... 0x000e、0x000f
????咱們創(chuàng)建一個SideTables[8]來存放這16個對象,那么查找的時候發(fā)生Hash沖突的概率就是八分之一。
????假設(shè)SideTables[0x0000]和SideTables[0x0x000f]沖突,映射到相同的結(jié)果。

SideTables[0x0000] == SideTables[0x0x000f] ==> 都指向同一個SideTable

  • RefcountMap的工作是在找到宿舍以后幫助大家找到正確的床位的兄弟。
  1. 維護weak指針的結(jié)構(gòu)體 weak_table_t ??weak_table;
    這里是一個兩層結(jié)構(gòu)。
  • 第一層結(jié)構(gòu)體中包含兩個元素。
    ????第一個元素weak_entry_t *weak_entries;是一個數(shù)組,上面的RefcountMap是要通過find(key)來找到精確的元素的。weak_entries則是通過循環(huán)遍歷來找到對應的entry。
    ????(上面管理引用計數(shù)器蘋果使用的是Map,這里管理weak指針蘋果使用的是數(shù)組,有興趣的朋友可以思考一下為什么蘋果會分別采用這兩種不同的結(jié)構(gòu))
    ????第二個元素num_entries是用來維護保證數(shù)組始終有一個合適的size。比如數(shù)組中元素的數(shù)量超過3/4的時候?qū)?shù)組的大小乘以2。

  • 第二層weak_entry_t的結(jié)構(gòu)包含3個部分
    1.referent:
    被指對象的地址。前面循環(huán)遍歷查找的時候就是判斷目標地址是否和他相等。
    2.referrers
    可變數(shù)組,里面保存著所有指向這個對象的弱引用的地址。當這個對象被釋放的時候,referrers里的所有指針都會被設(shè)置成nil。
    3.inline_referrers
    只有4個元素的數(shù)組,默認情況下用它來存儲弱引用的指針。當大于4個的時候使用referrers來存儲指針。

19.為什么 ARC 環(huán)境下不允許我們調(diào)用 [super dealloc]?

1.dealloc 的使用

a. 什么情況下會調(diào)用呢?
當對象的引用計數(shù)為0,系統(tǒng)會自動調(diào)用dealloc方法,回收內(nèi)存。

//調(diào)用方法
-(void)dealloc{
   // [super dealloc];    //ARC環(huán)境下不需要調(diào)用。因為系統(tǒng)會 自動調(diào)用該方法幫助釋放父類對象。
}

b.調(diào)用的順序
一般說調(diào)用的順序是,當子類的對象釋放完時,然后再釋放父類的所擁有的實例。這一點與調(diào)用初始化方法,正好相反

2.dealloc 誤區(qū)

我們在開發(fā)過程中,用到dealloc,卻因不會意識得到對象的引用計數(shù)是不是為0,dealloc到底走了沒走,因而導致內(nèi)存暴增,還會遇到很多奇怪的問題。我們需要知道dealloc不被調(diào)用的幾種情況?
1.controller中使用了計時器 NSTimer 使用后沒有銷毀 導致循環(huán)引用

self.playerTimer = [NSTimerscheduledTimerWithTimeInterval:1target:selfselector:@selector(playProgressAction)userInfo:nilrepeats:YES];

使用后記得銷毀
   [_playerTimerinvalidate]
    _playerTimer =nil;

2.協(xié)議delegate 應該使用weak修飾,否則會引起循環(huán)引用 不能釋放內(nèi)存
@property (nonatomic,weak)id<checkDelegate>delegate;

3.使用到block的地方,block回調(diào)中不能直接使用self 否則可能引起循環(huán)引用。

__weaktypeof(self) weakSelf =self;
    _audioStream.onCompletion=^(){
        [weakSelf nextButtonAction];
    };

  1. NSNotificationCenter 記得注銷
-(void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"delectOrGoDownProject" object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"changerInfoItem" object:nil];
}

20.Objective-C 是如何保證系統(tǒng)升級后的 ABI 穩(wěn)定性的?

自由發(fā)揮

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,135評論 1 32
  • 原文地址 http://blog.csdn.net/u012409247/article/details/4985...
    0fbf551ff6fb閱讀 3,566評論 0 13
  • 文中首先解釋了加密解密的一些基礎(chǔ)知識和概念,然后通過一個加密通信過程的例子說明了加密算法的作用,以及數(shù)字證書的出現(xiàn)...
    納蘭三少閱讀 1,952評論 1 6
  • 1.請簡單說明多線程技術(shù)的優(yōu)點和缺點? 優(yōu)點:能夠適當提高程序的執(zhí)行效率;能夠適當?shù)奶岣哔Y源的利用率,比如CPU、...
    deeper_iOS閱讀 1,462評論 1 12
  • 字符串作為雙向集合 有時你想要反轉(zhuǎn)一個字符串。通常會倒著迭代。幸運的是,Swift有一種相當簡單的方法,通過一種名...
    小橘子成長記閱讀 244評論 0 0