iOS 問題整理07----記憶體管理

根本停不下來發表於2018-08-05

1.對於block,理解,mrc和arc下有什麼區別,使用注意事項

  • mrc 下棧上的 block,在 arc 中並且符合一定條件的情況下會自動被拷貝到堆上
  • block 內部如果使用了 __block 修飾的區域性變數,在 mrc 下 block 不會對這個變數產生強引用,在 arc 下 block 會對這個變數產生強引用。block 從棧上拷貝到堆上的時候,如果是在 mrc 下,那麼 block 結構體中的 bref 結構部不會從棧上拷貝到堆上,所以 bref 不會持有變數;如果是在 arc 下,那麼 bref 也會跟著一起拷貝到堆上,所以 bref 會強引用變數。
  • arc 下,解決迴圈引用可以使用 __weak,__unsafe__unretained,或者使用__block 並且在塊內將變數置 nil,當程式碼塊內的程式碼執行完的時候,block 就不會再持有區域性變數了。如果是在 mrc 下,可以使用 __unsafe__unretained、__block 解決迴圈引用的問題。

2.使用CADisplayLink、NSTimer有什麼注意點?

  • 當定時器新增到 runloop 中的時候,runloop 會持有定時器,定時器會持有 target,就會導致 target 不釋放。
    • block,在內部使用 weakSelf
    • NSProxy,使用訊息轉發
  • NSTimer 與 CADisplayLink 依賴與 RunLoop,如果 RunLoop 的任務過於繁重,可能導致 NSTimer 不準時
    • 使用 GCD 的定時器

3. 介紹一下記憶體的幾大區域

從低地址到高地址依次是:

  • 程式碼段
  • 資料段(常量段):字串、全域性變數和靜態變數(未初始化的、已初始化的)
  • 堆區:自己 alloc、calloc、malloc 出來的物件
  • 棧區:函式的開銷,比如區域性變數。(棧區的地址是從高往低)
  • 核心區

4. 對mrc和arc的理解。ARC都幫我們做了什麼。講一下你對 iOS 記憶體管理的理解。ARC 的本質(阿里二面)。ARC 都幫我們做了什麼?

  • iOS 是使用引用計數來管理 OC 物件記憶體的,當引用計數為 0 的時候物件的記憶體就會被釋放。
  • 在 mrc 下我們需要手動地管理引用計數。使用 retain 讓物件的引用計數加一,使用 release 讓物件的引用計數減一。
  • 使用 arc 時,LLVM 編譯器會自動在合適的時候往我們的程式碼裡新增 release 或者 autorelease 和 retain 等,我們不需要再寫 retain 或者 release了。執行時會在 dealloc 的時候把 weak 修飾的指標自動置 nil。

5. 引用計數是怎麼儲存的?

  • 在 64bit 中,引用計數可以直接儲存在 isa 裡面,也可能儲存在 SideTable 類中
struct SideTable {
	sipinlock_t slock;
	RefcountMap refcnts; //存放著物件引用計數的雜湊表,以物件的地址為key
	weak_table_t weak_table; //雜湊表;key 是物件的地址,value 是所有弱引用了這個物件的指標的陣列
}
複製程式碼

6. @property 的本質是什麼?

  • 自動幫我們生成成員變數、setter 與 getter 方法的宣告和實現

7. property 的常用修飾詞有哪些?

  • 原子性:atomic/nonatomic
  • 讀寫許可權:readwrite/readonly
  • 記憶體管理:strong、copy、assign、unsafe_unretained、weak
  • 方法名:getter=<name> 、setter=<name>

8. 如果屬性完全不加修飾詞入weak,atomic,系統會怎麼處理

  • property 預設使用的是 atomic、readwrite、assign

9. weak 和 assign 的區別?

  • assign 可以修飾基本型別和物件,weak 只能修飾物件
  • 被 weak 修飾的指標會在物件被釋放的時候自動置 nil

10. 對於strong,weak,atomic等等理解

  • atomic 是屬性的原子性,給系統自動生成的 setter、getter 方法加鎖
  • 被 strong 修飾的屬性在指向物件的時候,物件的引用計數加一
  • 被 weak 修飾的屬性在指向物件的時候,不會持有物件,物件銷燬的時候屬性自動置nil

11. 使用 atomic 一定是執行緒安全的嗎?

  • 使用 atomic 就是給系統自動生成的 setter、getter 加鎖,它可以保證資料的完整性,但是並不能保證資料是準確的。比如 A 執行緒的寫操作結束後,B 執行緒又開始寫,這時候 A 執行緒讀操作就會等到 B 執行緒的寫操作結束後再做,那麼最後 A 執行緒的讀操作讀到的是 B 執行緒寫進去的資料。
  • atomic 也不能保證整個物件是執行緒安全的。比如 A 執行緒在進行讀操作之前,物件被 C 執行緒 release 了,那麼就會崩潰。再比如,一個可變的陣列在多個執行緒下使用 addObjectAtIndex:,這就是不安全的,這也和 setter 與 getter 沒什麼關係。

12. __block vs __weak

  • 使用 __block 的目的是在程式碼塊中修改區域性變數。使用 __block 後,變數會被包裝成 bref 結構體被捕獲進 Block 結構體,當 Blcok 拷貝到堆上的時候,在 arc 中 bref 也會被拷貝到堆上,在 mrc 中 bref 不會被拷貝到堆上,所以在 arc 中使用 __block 還要注意解決迴圈引用的問題,而在 mac 中使用 __block 不會引起迴圈引用的問題。
  • 使用 __weak 的目的是在 arc 中解決 Block 的迴圈引用的問題。Block 捕獲區域性變數時,如果變數是被 __weak 修飾的,捕獲進 Block 結構體的時候,也會被 weak 修飾,當 Block 拷貝到堆上的時候,捕獲進來的這個變數引用計數不會增加。

13. weak 屬性需要在 dealloc 中置 nil 嗎?

  • 不需要,weak 修飾的變數會在物件被釋放後自動置 nil

14. IBOutlet 連出來的屬兔屬性為什麼可以被設定成 weak?

  • xib 有一個陣列持有著這個物件

15. 怎麼使用 copy 關鍵字?

  • copy 的目的
    • 產生一個副本物件,跟源物件互不影響
    • 修改了源物件,不會影響副本物件
    • 修改了副本物件,不會影響源物件
  • 常用 copy 的物件: NSString、NSArray、NSDictionary
  • iOS 提供了兩個拷貝方法:copy、mutableCopy
    • copy:產生不可變副本
    • mutableCopy:產生可變副本
NSString *str1 = @"123"; //這是一個在常量區的不可變字串
NSString *str2 = [str1 copy];//還是這個字串。不採用taggedPointer技術
NSString *str3 = [str1 mutableCopy];//把它拷貝成可變的字串,並且是拷貝到堆上去了。
複製程式碼
NSMutableString *str1 = [NSMutableString stringWithFormat:@"123"];//在堆上的一個可變字串
NSString *str2 = [str1 copy];//拷貝成不可變字串。會採用taggedPointer技術,比較短的話就是在常量區的taggedPointer,長一點的話會變成在堆上的字串。
NSString * str3 - [str1 mutableCopy];//拷貝成一個新的、可變的字串。在堆上的,所以它會是不一樣的。
複製程式碼

16. 深複製和淺複製指的是什麼?對於深拷貝和淺拷貝的理解

  • 深拷貝
    • 內容拷貝,產生了新的物件
    • 比如拷貝堆上的物件
  • 淺拷貝
    • 指標拷貝,沒用產生新的物件
    • 比如 copy 常量區的字串。NSString *str = [@"123" copy];
  • mutableCopy 一定是深拷貝。 copy 不一定是淺拷貝。

17. 如何重寫帶 copy 關鍵字的 setter?

- (void)setKey:(id)key 
{
	if (key != _key) {
		//[_key release]; //MRC
		_key = [key copy];
	}
}
複製程式碼

18. 如何讓自己的的類用copy修飾符?

  • 自定義的類要想使用 copy 方法,必須得先遵守 NSCopying 協議
  • 實現 - (id)copyWithZone:(NSZone *)zone 方法
- (id)copyWithZone:(NSZone *)zone 
{
    Person *person = [[Person allocWithZone:zone] init];
	person.age = self.age;
	person.weight = self.weight;
	return person;
}
複製程式碼

19. 這種寫法會出現什麼問題:@property(copy)NSMutableArray *array;?

  • 使用 self.array 對 _array 進行賦值之後,會 copy 一個不可變陣列賦值給它,當呼叫 NSMutableArray 中的方法時,會報錯。

20. @property宣告的NSString(或NSArray,NSDictionary)經常使用copy關鍵字,為什麼?如果改用strong關鍵字,可能造成什麼問題?

  • 因為它們有對應子類,根據里氏替換原則,父類指標可以指向子類物件,所以可以給他們賦值一個可變的物件,通過改變這個物件就改了屬性的值。使用 copy 是為了保證屬性指向的物件的值是不能被修改的。

21. Objective-C 中的 copy 方法(阿里)

  • 拷貝的目的是產生一個副本,它們修改的時候互不影響。
  • NSString、NSArray、NSDictionary 等這些類有對應的可變型別,所以它們的拷貝有 copy 和 mutableCopy
    • copy 一定產生一個不可變物件
    • mutableCopy 一定產生一個不可變物件
    • 不可變物件使用 copy ,copy 出來的時候也是一個不可變的,拷貝的目的是拷貝之後它們修改的時候互不影響,既然它們都不能修改,那就沒有產生新物件的必要了。所以,只會拷貝一個新的指標指向這個不可變物件。這是淺拷貝。
    • 拷貝的時候產生了新的物件,這就是深拷貝
  • 自定義類要想使用 copy 方法,需要先遵守 NSCopying 協議,實現 copyWithZone:(NSZone *)zone 方法。

22. weak 的實現原理是什麼?

  • 物件的弱引用關係會存入 SideTable 裡面的弱引用雜湊表中,這個列表中的 key 是物件的地址,value 是所有弱引用了這個物件的的指標的陣列。當物件呼叫 release 的時候,會判斷引用計數是否為零,如果為零的話就會呼叫 dealloc 方法,然後就會通過物件的地址在 SideTable 的弱引用表中找到這個陣列,遍歷這個陣列對陣列裡邊的指標清空置 nil。

23. 談談對自動釋放池的理解。autoreleasepool 的使用場景和原理。Autoreleasepool 什麼時候釋放,在什麼場景下使用?(阿里)

  • AutoreleasePool 本質上是一個結構體,裡面有 push 和 pop 函式,這兩個函式時對 AutoreleasePoolPage 的封裝,所以 AutoreleasePool 沒有單獨的結構,是由若干個 AutoreleasePoolPage 以雙連結串列的形式組成的,自動釋放機制的核心在於 AutoreleasePoolPage 這個類。
  • 每一個 AutoreleasePoolPage 物件佔 4096 個位元組。出了自己結構體所佔的記憶體,都用來存放的是 autorelease 物件的地址。
  • 當建立 @autoreleasepool{} 時,會呼叫 push,會往裡面存入一個哨兵物件。
  • 在 autoreleasepool{} 中建立物件的時候就會向 next 指向的地址中寫入新建立的這個物件的地址。
  • 當 @autoreleasepool{} 結束的時候會呼叫 pop 的,這時從最新寫入的地址開始,向前找,依次向它們指向的物件傳送 release 訊息,直到找到前一個哨兵物件。(可能跨了好幾頁)
  • 使用場景:當有大量臨時變數的時候,避免記憶體峰值過高
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
    @autoreleasepool {
        NSError *error;
        NSString *fileContents = [NSString stringWithContentsOfURL:url
                                        encoding:NSUTF8StringEncoding
                                         error:&error];
   	}
}
複製程式碼

24. 自動釋放池在mrc和arc區別

25. 多層自動釋放池巢狀的物件在哪一層釋放

  • 最裡面的一層先 release。因為它先出大括號,先呼叫 AutoreleasePool 的 pop 方法。

25. 方法裡有區域性物件,出了方法後會立即釋放嗎?

  • 在 arc 下,如果編譯器幫我們新增的程式碼是在出方法前 [obj release],那麼就是出了方法會立即釋放;如果編譯器幫我們新增的程式碼是在建立的時候新增 autorelease,那麼就不是立即 release 的。它會在 runloop 休眠前進行 release。

26. 你在專案中是怎麼優化記憶體的?

  • 使用 instruments 的 LEAKS 或者 第三方工具比如 LeakFinder 來檢測記憶體洩漏,然後進行處理

相關文章