最新整理:iOS面試題-常問內(nèi)存管理問題(五)

前言:

最近把 iOS 面試中可能會遇到的問題整理了一番, 題目大部分是網(wǎng)上收錄的, 方便自己鞏固復(fù)習(xí), 也分享給大家; 希望對大家有所幫助!

  • 對于答案,不一定都合適,歡迎大家積極討論;整理不易,如果您覺得還不錯(cuò),麻煩在文末 “點(diǎn)個(gè)贊” ,或者留下您的評論“Mark” 一下,謝謝您的支持

目錄合集

iOS面試題-內(nèi)存管理問題(五)

1.什么是內(nèi)存泄漏?

  • 內(nèi)存泄漏指動態(tài)分配內(nèi)存的對象在使用完后沒有被系統(tǒng)回收內(nèi)存,導(dǎo)致對象始終占有著內(nèi)存,屬于內(nèi)存管理出錯(cuò), (例如一個(gè)對象或者變量使用完成后沒有釋放,這個(gè)對象一直占用著內(nèi)存),一次內(nèi)存泄露危害可以忽略,但內(nèi)存泄露堆積后果很嚴(yán)重,無論多少內(nèi)存,遲早會被占光。

2. 什么是僵尸對象?

  • 已經(jīng)被銷毀的對象(不能再使用的對象),內(nèi)存已經(jīng)被回收的對象。一個(gè)引用計(jì)數(shù)器為0對象被釋放后就變?yōu)榱私┦瑢ο?

3. 野指針

  • 野指針又叫做'懸掛指針', 野指針出現(xiàn)的原因是因?yàn)橹羔槢]有賦值,或者指針指向的對象已經(jīng)釋放了, 比如指向僵尸對象;野指針可能會指向一塊垃圾內(nèi)存,給野指針發(fā)送消息會導(dǎo)致程序崩潰
    比如:

    NSObject *obj = [NSObject new];
    [obj release]; // obj 指向的內(nèi)存地址已經(jīng)釋放了,
    obj 如果再去訪問的話就是野指針錯(cuò)誤了.
    野指針錯(cuò)誤形式在Xcode中通常表現(xiàn)為:Thread 1:EXC_BAD_ACCESS,因?yàn)槟阍L問了一塊已經(jīng)不屬于你的內(nèi)存。
    
    

4. 什么是空指針?

  • 空指針不同于野指針,他是一個(gè)沒有指向任何內(nèi)存的指針,空指針是有效指針,值為nil,NULL,Nil,0等,給空指針發(fā)送消息不會報(bào)錯(cuò),不會響應(yīng)消息;

5. OC對象的內(nèi)存管理機(jī)制?

在iOS中,使用引用計(jì)數(shù)來管理OC對象的內(nèi)存

  • 一個(gè)新創(chuàng)建的OC對象引用計(jì)數(shù)默認(rèn)是1,當(dāng)引用計(jì)數(shù)減為0,OC對象就會銷毀,釋放其占用的內(nèi)存空間
  • 調(diào)用retain會讓OC對象的引用計(jì)數(shù)+1,調(diào)用release會讓OC對象的引用計(jì)數(shù)-1

內(nèi)存管理的經(jīng)驗(yàn)總結(jié)

  • 當(dāng)調(diào)用alloc、new、copy、mutableCopy方法返回了一個(gè)對象,在不需要這個(gè)對象時(shí),要調(diào)用release或者autorelease來釋放它
  • 想擁有某個(gè)對象,就讓它的引用計(jì)數(shù)+1;不想再擁有某個(gè)對象,就讓它的引用計(jì)數(shù)-1

可以通過以下私有函數(shù)來查看自動釋放池的情況

  • extern void _objc_autoreleasePoolPrint(void);

6. OC中有GC垃圾回收機(jī)制嗎?,iPhone上GC嗎?

  • 垃圾回收(GC),就是程序中用于處理廢棄不用的內(nèi)存對象的機(jī)制,防止內(nèi)存泄露
  • OC本身是支持垃圾回頭得,不過只支持MAC OSX平臺, iOS 平臺不支持

7.在OC中與 Alloc 語義相反的是 release 還是 dealloc?

  • alloc 與 dealloc 語義相反,alloc 是創(chuàng)建變量,dealloc是釋放變量
  • retain 與 release 語義相反, retain 保留一個(gè)對象,引用計(jì)數(shù)器+1, release 使引用計(jì)數(shù)器 -1;

8.什么是內(nèi)存溢出?

  • 當(dāng)程序在申請內(nèi)存時(shí),沒有足夠的內(nèi)存空間供其使用,出現(xiàn)out of memory;比如申請了一個(gè)int,但給它存了long才能存下的數(shù),那就是內(nèi)存溢出。

9.內(nèi)存區(qū)域分布

在iOS開發(fā)過程中,為了合理的分配有限的內(nèi)存空間,將內(nèi)存區(qū)域分為五個(gè)區(qū),由低地址向高地址分類分別是:代碼區(qū)、常量區(qū)、全局靜態(tài)區(qū)、堆、棧。

  • 代碼段 -- 程序編譯產(chǎn)生的二進(jìn)制的數(shù)據(jù)
  • 常量區(qū) -- 存儲常量數(shù)據(jù),通常程序結(jié)束后由系統(tǒng)自動釋放
  • 全局靜態(tài)區(qū) -- 全局區(qū)又可分為未初始化全局區(qū):.bss段和初始化全局區(qū):data段。全局變量和靜態(tài)變量的存儲是放在一塊的,初始化的全局變量和靜態(tài)變量在一塊區(qū)域, 未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域,在程序結(jié)束后有系統(tǒng)釋放。
  • 堆(heap) -- 程序運(yùn)行過程中,動態(tài)分配的內(nèi)存
  • 棧(stack) -- 存放局部變量,臨時(shí)變量

10.堆區(qū)和棧取的區(qū)別

  • 按管理方式分

    • 對于棧來講,是由系統(tǒng)編譯器自動管理,不需要程序員手動管理
    • 對于堆來講,釋放工作由程序員手動管理,不及時(shí)回收容易產(chǎn)生內(nèi)存泄露
  • 按分配方式分

    • 堆是動態(tài)分配和回收內(nèi)存的,沒有靜態(tài)分配的堆
    • 棧有兩種分配方式:靜態(tài)分配和動態(tài)分配
    • 靜態(tài)分配是系統(tǒng)編譯器完成的,比如局部變量的分配
    • 動態(tài)分配是有alloc函數(shù)進(jìn)行分配的,但是棧的動態(tài)分配和堆是不同的,它的動 態(tài)分配也由系統(tǒng)編譯器進(jìn)行釋放,不需要程序員手動管理

11.怎么保證多人開發(fā)進(jìn)行內(nèi)存泄露的檢查.

  1. 使用Analyze進(jìn)行代碼的靜態(tài)分析
  2. 為避免不必要的麻煩, 多人開發(fā)時(shí)盡量使用ARC
  3. 使用leaks 進(jìn)行內(nèi)存泄漏檢測
  4. 使用一些三方工具

12.block在ARC中和MRC中的用法有什么區(qū)別,需要注意什么?

  1. 對于沒有引用外部變量的Block,無論在ARC還是非ARC下,類型都是 NSGlobalBlock,這種類型的block可以理解成一種全局的block,不 需要考慮作用域問題。同時(shí),對他進(jìn)行Copy或者Retain操作也是無效的
  2. 都需要應(yīng)注意避免循環(huán)引用,ARC 下使用__weak 來解決,MRC下使用__Block 來解決;

13.OC 如何對內(nèi)存管理解決方法?

Objective-C的內(nèi)存管理主要有三種方式 自動內(nèi)存管理、手動內(nèi)存管理、自動釋放池。

  1. 自動內(nèi)存計(jì)數(shù)
  2. 手動內(nèi)存計(jì)數(shù):
  3. 自動釋放池:

14.ARC 都幫我們做了什么?

  • LLVM + Runtime 會為我們代碼自動插入 retain 和 release 以及 autorelease等代碼,不需要我們手動管理

15.weak指針的實(shí)現(xiàn)原理

  • Runtime維護(hù)了一個(gè)weak表,用于存儲指向某個(gè)對象的所有weak指針。weak表其實(shí)是一個(gè)hash(哈希)表,Key是所指對象的地址,Value是weak指針的地址(這個(gè)地址的值是所指對象的地址)數(shù)組。
  • runtime對注冊的類, 會進(jìn)行布局,對于weak對象會放入一個(gè)hash表中。 用weak指向的對象內(nèi)存地址作為key,當(dāng)此對象的引用計(jì)數(shù)為0的時(shí)候會dealloc,假如weak指向的對象內(nèi)存地址是a,那么就會以a為鍵, 在這個(gè)weak表中搜索,找到所有以a為鍵的weak對象,從而設(shè)置為nil。

16.方法里有局部對象,出了方法后會立即釋放嗎

  • 如果是普通的 局部對象 會立即釋放
  • 如果是放在了 autoreleasePool 自動釋放池,在 runloop 迭代結(jié)束的時(shí)候釋放

17.MRC情況下怎么做單例模式

創(chuàng)建單例設(shè)計(jì)模式的基本步驟 : ·

  1. 聲明一個(gè)單件對象的靜態(tài)實(shí)例,并初始化為nil。
  2. 創(chuàng)建一個(gè)類的類工廠方法,當(dāng)且僅當(dāng)這個(gè)類的實(shí)例為nil時(shí)生成一個(gè)該類 的實(shí)例
  3. 實(shí)現(xiàn)NScopying協(xié)議, 覆蓋allocWithZone:方法,確保用戶在直接分配和 初始化對象時(shí),不會產(chǎn) 生另一個(gè)對象。
  4. 覆蓋release、autorelease、retain、retainCount方法, 以此確保單例的 狀態(tài)。
  5. 在多線程的環(huán)境中,注意使用@synchronized關(guān)鍵字或GCD,確保靜態(tài)實(shí) 例被正確的創(chuàng)建和初始化。

18.非OC對象如何管理內(nèi)存?

  • 非OC對象,其需要手動執(zhí)行釋放操作例:CGImageRelease(ref),否則會造成大量的內(nèi)存泄漏導(dǎo)致程序崩潰。其他的對于CoreFoundation框架下的某些對象或變量需要手動釋放、C語言代碼中的malloc等需要對應(yīng)free。

19. CADisplayLink、NSTimer會出現(xiàn)的問題,以及解決辦法?

問題:

  1. CADisplayLink、NSTimer會對target產(chǎn)生強(qiáng)引用,如果target又對它們產(chǎn)生強(qiáng)引用,那么就會引發(fā)循環(huán)引用
  2. CADisplayLink、NSTimer都是基于 runloop 實(shí)現(xiàn)的.runloop 會對 CADisplayLink、NSTimer進(jìn)行強(qiáng)引用, CADisplayLink、NSTimer又 會對 target 進(jìn)行引用,造成循環(huán)引用
  • 解決方案1.使用block
// 內(nèi)部使用 WeakSelf,并在視圖消失前,關(guān)閉定時(shí)器
__weak __typeof(self)weakSelf = self;
NSTimer * timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
    NSLog(@"timer");
}];
self.timer= timer;
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];

  • 解決方案2.使用代理對象(NSProxy)
.h
// 解決循環(huán)引用問題
@interface MyProxy : NSProxy
- (instancetype)initWithObjc:(id)objc;
+ (instancetype)proxyWithObjc:(id)objc;
.m
@interface MyProxy()
@property(nonatomic,weak) id objc;
@end

@implementation MyProxy
- (instancetype)initWithObjc:(id)objc{
    self.objc = objc;
    return self;
}
+ (instancetype)proxyWithObjc:(id)objc{
    return [[self alloc] initWithObjc:objc];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return [self.objc methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
    if ([self.objc respondsToSelector:invocation.selector]) {
        [invocation invokeWithTarget:self.objc];
    }
}

使用方法:

NSTimer * timer = [NSTimer timerWithTimeInterval:1
                                          target:[TimerProxy proxyWithTarget:self]
                                        selector:@selector(test1)
                                        userInfo:nil
                                         repeats:YES];
self.timer = timer;
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

20.什么是Tagged Pointer?

  • 從64bit開始,iOS引入了Tagged Pointer技術(shù),用于優(yōu)化NSNumber、NSDate、NSString等小對象的存儲
  • 在沒有使用Tagged Pointer之前, NSNumber等對象需要動態(tài)分配內(nèi)存、維護(hù)引用計(jì)數(shù)等,NSNumber指針存儲的是堆中NSNumber對象的地址值
  • 使用Tagged Pointer之后,NSNumber指針里面存儲的數(shù)據(jù)變成了:Tag + Data,也就是將數(shù)據(jù)直接存儲在了指針中
  • 當(dāng)指針不夠存儲數(shù)據(jù)時(shí),才會使用動態(tài)分配內(nèi)存的方式來存儲數(shù)據(jù)

21.copy和mutableCopy區(qū)別

22. 內(nèi)存泄漏可能會出現(xiàn)的幾種原因?

  • 第一種可能:第三方框架不當(dāng)使用;
  • 第二種可能:block循環(huán)引用;
  • 第三種可能:delegate循環(huán)引用;
  • 第四種可能:NSTimer循環(huán)引用
  • 第五種可能:非OC對象內(nèi)存處理
  • 第六種可能:地圖類處理
  • 第七種可能:大次數(shù)循環(huán)內(nèi)存暴漲

23. ARC下什么樣的對象由 Autoreleasepool 管理

  • 當(dāng)使用alloc/new/copy/mutableCopy開始的方法進(jìn)行初始化時(shí),會生成并持有對象(也就是不需要pool管理,系統(tǒng)會自動的幫他在合適位置release),不需要pool進(jìn)行管理
  • 一般類方法創(chuàng)建的對象需要使用Autoreleasepool進(jìn)管理

24. 如何實(shí)現(xiàn)AutoreleasePool?

  • AutoreleasePool(自動釋放池)其實(shí)并沒有自身的結(jié)構(gòu),他是基于多個(gè)AutoreleasePoolPage(一個(gè)C++類)以雙向鏈表組合起來的結(jié)構(gòu); 可以通過 push操作添加對象,pod 操作彈出對象,以及通過 release 操作釋放對象;

25. AutoreleasePoolPage的結(jié)構(gòu)?以及如何 push 和 pod 的?

  • 調(diào)用push方法會將一個(gè)POOL_BOUNDARY入棧,并且返回其存放的內(nèi)存地址
  • 調(diào)用pop方法時(shí)傳入一個(gè)POOL_BOUNDARY的內(nèi)存地址,會從最后一個(gè)入棧的對象開始發(fā)送release消息,直到遇到這個(gè)POOL_BOUNDARY
  • id *next指向了下一個(gè)能存放autorelease對象地址的區(qū)域

26.Autoreleasepool 與 Runloop 的關(guān)系

  • 主線程默認(rèn)為我們開啟 Runloop,Runloop 會自動幫我們創(chuàng)建Autoreleasepool,并進(jìn)行Push、Pop 等操作來進(jìn)行內(nèi)存管理

iOS在主線程的Runloop中注冊了2個(gè)Observer

  • 第1個(gè)Observer監(jiān)聽了kCFRunLoopEntry事件,會調(diào)用objc_autoreleasePoolPush()
  • 第2個(gè)Observer 監(jiān)聽了kCFRunLoopBeforeWaiting事件,會調(diào)用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()監(jiān)聽了kCFRunLoopBeforeExit事件,會調(diào)用objc_autoreleasePoolPop()

27.子線程默認(rèn)不會開啟 Runloop,那出現(xiàn) Autorelease 對象如何處理?不手動處理會內(nèi)存泄漏嗎?

  • 在子線程你創(chuàng)建了 Pool 的話,產(chǎn)生的 Autorelease 對象就會交給 pool 去管理。如果你沒有創(chuàng)建 Pool ,但是產(chǎn)生了 Autorelease 對象,就會調(diào)用 autoreleaseNoPage 方法。在這個(gè)方法中,會自動幫你創(chuàng)建一個(gè) hotpage(hotPage 可以理解為當(dāng)前正在使用的 AutoreleasePoolPage,如果你還是不理解,可以先看看 Autoreleasepool 的源代碼,再來看這個(gè)問題 ),并調(diào)用 page->add(obj)將對象添加到 AutoreleasePoolPage 的棧中,也就是說你不進(jìn)行手動的內(nèi)存管理,也不會內(nèi)存泄漏啦!StackOverFlow 的作者也說道,這個(gè)是 OS X 10.9+和 iOS 7+ 才加入的特性。

收錄 | 原文地址


結(jié)語

再次說一聲,對于答案,不一定都合適,歡迎大家積極討論;整理不易,如果您覺得還不錯(cuò),麻煩在文末 “點(diǎn)個(gè)贊” ,或者留下您的評論“Mark” 一下,謝謝您的支持


推薦文集

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容