iOS開發面試題

1. KVO的實現原理

KVO的底層實現是使用runtime實現的,在程序運行過程中,創建一個繼承於對象觀察者的類 (例如觀察者對象的類爲Person,動態創建NSKVONofitying_Person類,並且繼承於Person)使用斷點查看發現age屬性雖然屬於Person類,但是內部的isa指針指向的是 NSKVONofitying_Person
對應的.m文件,假設被觀察的對象爲age:
- (void)setAge:(int)age {
[super setAge:age];
//在該類中調用下面的的兩個方法,這兩個方法會觸發觀察者的observeValueForKeyPath:ofObject:change:context 方法
[self willChangeValueForKey:@"age"];
[self didChangeValueForKey:@"age"];

}

2.代理屬性的引用 weak & strong

一般情況下都使用weak,爲了避免循環引用。
特殊情況:蘋果使用strong引用的代理
NSUrlSession :官方文檔
Important
The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you do not invalidate the session, your app leaks memory until it exits.
蘋果要求所有的網絡請求都使用一個類去操作,並且該類應該成爲session的代理,將其設置爲單例,可以全局使用,不會被銷燬(後臺下載)

3.代理可以一對多

正常下,代理與通知的區別就是,前者爲一對一,後者爲一對多。但是代理是可以一對多的,多播代理 ,xmpp 就是這樣,即時通訊的時候,多個界面在接受到消息時做出相應的改變

4.runloop

NSRunLoop & CFRunLoop
主線程的runloop是由UIApplicationMain函數開啓的
runloop:
保證程序正常運行;
  1. 負責監聽系統內核時間,時鐘,觸摸,網絡時間;observe,timer,source(事件源)
  2. 如果沒有時間需要處理,就暫時休息
  3. 在一次循環中,需要繪製屏幕上的點

4.1NSRunLoop中的5種mode

  1. NSDefaultRunLoopMode— 默認模式
  2. UITrackingRunLoopMode — 用戶交互模式
  3. NSRunLoopCommonMode — 佔位模式,也可以理解爲兼容以上兩種模式
  4. NSConnectionReplyMode
  5. NSModalPanelRunLoopMode
    子線程異步銷燬,eg:子線程中創建NSTimer ,timer的方法並不會執行;
    解決方案:常駐線程。每個線程都有一個runloop,主線程不會掛掉,子線程上面的runloop默認不啓動,因此子線程執行完代碼就會銷燬
    [[NSRunLoop currentRunLoop] run];//runloop啓動後就保證運行,後續代碼無法執行,子線程如果需要執行timer,使用dipatch就可以。

4.2 runloop應用案例,性能優化

  1. 耗時操作放子線程,更新UI放主線程
  2. 假設更新UI做了很多操作,如何進行性能優化
    案例:UITableView上加載多張高清大圖,並且不讓tableView複用,在滾動時runloop會造成卡段。runloop在進行UITracking時,會繪製很多的點,這個是很消耗的資源的。
    思路:
  3. 減少性能開銷
  4. 分步加載
    分步加載原理,監聽runloop循環,將耗時操作放在一個數組中,不去執行,一次runloop循環,從數組中拿出來一個執行
    使用CFRunLoop解決出現的問題
    NULL指針賦值,對象賦值nil

5.nonatomic與natomic

前者非原子性,線程不安全;後者原子性,性能安全. Apple這麼設計是爲了提高性能,所以會出現非原子性的屬性。
使用時nonatomic我們不能使用多個線程去訪問,會出現搶奪資源的現象

6.NSURLConnection與NSURLSession

connection複雜的網絡請求默認是同步的(雖然也有異步網絡請求),一旦異步做下載,Connection需要手動開啓runloop(使用後關閉runloop以及線程),否則會出現子線程銷燬.

7.將 UIImage 保存到磁盤,用什麼方式最好?

目前來說,保存 UIImage 有三種方式:1.直接用 NSKeyedArchiver 把 UIImage 序列化保存,2.用 UIImagePNGRepresentation() 先把圖片轉爲 PNG 保存,3.用 UIImageJPEGRepresentation() 把圖片壓縮成 JPEG 保存。
實際上,NSKeyedArchiver 是調用了 UIImagePNGRepresentation 進行序列化的,用它來保存圖片是消耗最大的。蘋果對 JPEG 有硬編碼和硬解碼,保存成 JPEG 會大大縮減編碼解碼時間,也能減小文件體積。所以如果圖片不包含透明像素時,UIImageJPEGRepresentation(0.9) 是最佳的圖片保存方式,其次是 UIImagePNGRepresentation()。

8. weak&assign

  1. assign除了可以修飾OC對象以外,還可以修飾基本數據類型
  2. weak修改的屬性的成員變量爲__ weak 修飾的,assign修飾的屬性的成員變量是使用 __ unsafe __unretained 修飾的
__weak_unsafe__unretained
  1. 都不是強指針,不能保證對象不被銷燬
  2. __weak:所指向的對象銷燬後,會自動變成nil指針(空指針);指針被銷燬
  3. _unsafe__unretained:所指向的對象銷燬後,仍舊指向已經銷燬的對象,從而引起BAD_ACCESS崩潰。指針未被銷燬
    1.在 ARC 中,在有可能出現循環引用的時候,往往要通過讓其中一端使用 weak 來解決,比如: delegate 代理屬性。
    2.自身已經對它進行一次強引用,沒有必要再強引用一次,此時也會使用 weak,自定義 IBOutlet 控件屬性一般也使用 weak;當然,也可以使用strong。
    IBOutlet連出來的視圖屬性爲什麼可以被設置成weak?
    因爲父控件的subViews數組已經對它有一個強引用。
    不同點:
    assign 可以用非 OC 對象,而 weak 必須用於 OC 對象。
    weak 表明該屬性定義了一種“非擁有關係”。在屬性所指的對象銷燬時,屬性值會自動清空(nil)。

9 消息轉發

[receiver message]調用方法時,如果在message方法在receiver對象的類繼承體系中沒有找到方法,那怎麼辦?一般情況下,程序在運行時就會Crash掉,拋出unrecognized selector sent to…類似這樣的異常信息。但在拋出異常之前,還有三次機會按以下順序讓你拯救程序。
  1. Method Resolution
    動態方法解析:向當前類發送 resolveInstanceMethod: 信號,檢查是否動態向該類添加了方法
  2. Fast Forwarding
    快速消息轉發:檢查該類是否實現了 ·之間傳遞之間是地址傳遞
    3.蘋果推薦我們在swift開發中多使用結構體。使用結構體遵守協議實現類似於類的繼承效果

野指針&殭屍對象

weak:被修飾指針指向的對象釋放以後,該指針會自動指向0x0(nil)。
unsafe_unretain 特點,當指向的對象釋放以後,被其修飾的指針依舊會指向原來的地址;
殭屍對象:指向的地址 在原來對象釋放以後還沒有被分配。此時指向的即爲殭屍對象
野指針:指向的地址,在原來對象釋放完成以後,重新分配給其他對象使用。此時該指針爲野指針,即指向了不該指向的內存地址 。

幀數

一秒小於16幀,用戶就可以明顯感覺到卡頓,一般電影是20幀或者30幀

10.hitTest 事件分發分析

userInteractionEnabled = NO(這個屬性大家肯定不陌生,UILabel和UIImageView無法接收點擊事件就是因爲這個屬性默認設置爲了NO),類似還有Hidden=YES/alpha<0.01等都可以使某個視圖不響應點擊事件。userInteractionEnabled = NO等價於使當前的hitTest:withEvent返回nil。
UIView是如何判定這個事件是否是自己應該處理的呢?iOS系統檢測到一個觸摸操作時會打包一個UIEvent對象,並放入Application的隊列,Application從隊列中取出事件後交給UIWindow來處理,UIWindow會使用hitTest:withEvent:方法來遞歸的尋找操作初始點所在的view,這個過程成爲hit-test view.
蘋果官方文檔:
The hitTest:withEvent: method returns the hit test view for a given CGPoint and UIEvent. The hitTest:withEvent: method begins by calling the pointInside:withEvent: method on itself. If the point passed into hitTest:withEvent: is inside the bounds of the view, pointInside:withEvent: returns YES. Then, the method recursively calls hitTest:withEvent: on every subview that returns YES.
If the point passed into hitTest:withEvent: is not inside the bounds of the view, the first call to the pointInside:withEvent: method returns NO, the point is ignored, and hitTest:withEvent: returns nil. If a subview returns NO, that whole branch of the view hierarchy is ignored, because if the touch did not occur in that subview, it also did not occur in any of that subview’s subviews. This means that any point in a subview that is outside of its superview can’t receive touch events because the touch point has to be within the bounds of the superview and the subview. This can occur if the subview’s clipsToBounds property is set to NO.
翻譯下,差不多就是:
1.首先調用當前視圖的pointInside:withEvent:方法判斷觸摸點是否在當前視圖內;
2.若pointInside:withEvent:方法返回NO,說明觸摸點不在當前視圖內,則當前視圖的hitTest:withEvent:返回nil.
3.若pointInside:withEvent:方法返回YES,說明觸摸點在當前視圖內,則遍歷當前視圖的所有子視圖(subviews),調用子視圖的hitTest:withEvent:方法重複前面的步驟,子視圖的遍歷順序是從top到bottom,即從subviews數組的末尾向前遍歷,直到有子視圖的hitTest:withEvent:方法返回非空對象或者全部子視圖遍歷完畢.
4.若第一次有子視圖的hitTest:withEvent:方法返回非空對象,則當前視圖的hitTest:withEvent:方法就返回此對象,處理結束;
5.若所有子視圖的hitTest:withEvent:方法都返回nil,則當前視圖的hitTest:withEvent:方法返回當前視圖自身(self).
MJ大神的事件分發示意圖

11.堆與棧

Objective-C的對象在內存中是以堆的方式分配空間的,並且堆內存是由你釋放的,即release
棧由編譯器管理自動釋放的,在方法中(函數體)定義的變量通常是在棧內,因此如果你的變量要跨函數的話就需要將其定義爲成員變量。
1.棧區(stack):由編譯器自動分配釋放,存放函數的參數值,局部變量等值。其操作方式類似於數據結構中的棧。
2.堆區(heap):一般由程序員分配釋放,若程序員不釋放,則可能會引起內存泄漏。注堆和數據結構中的堆棧不一樣,其類是與鏈表。
操作系統iOS 中應用程序使用的計算機內存不是統一分配空間,運行代碼使用的空間在三個不同的內存區域,分成三個段:“text segment “,“stack segment ”,“heap segment ”。
段“text segment ”是應用程序運行時應用程序代碼存在的內存段。每一個指令,每一個單個函數、過程、方法和執行代碼都存在這個內存段中直到應用程序退出。一般情況下,你不會真的不得不知道這個段的任何事情。
當應用開始以後,函數main() 被調用,一些空間分配在”stack” 中。這是爲應用分配的另一個段的內存空間,這是爲了函數變量存儲需要而分配的 內存。每一次在應用中調用一個函數,“stack ”的一部分會被分配在”stack” 中,稱之爲”frame” 。新函數的本地變量分配在這裏。
正如名稱所示,“stack ”是後進先出(LIFO )結構。當函數調用其他的函數時,“stack frame ”會被創建;當其他函數退出後,這個“frame ”會自動被破壞。
“heap” 段也稱爲”data” 段,提供一個保存中介貫穿函數的執行過程,全局和靜態變量保存在“heap”中,直到應用退出。
爲了訪問你創建在heap 中的數據,你最少要求有一個保存在stack 中的指針,因爲你的CPU 通過stack 中的指針訪問heap 中的數據。
你可以認爲stack 中的一個指針僅僅是一個整型變量,保存了heap 中特定內存地址的數據。實際上,它有一點點複雜,但這是它的基本結構。
簡而言之,操作系統使用stack 段中的指針值訪問heap 段中的對象。如果stack 對象的指針沒有了,則heap 中的對象就不能訪問。這也是內存泄露的原因。
在iOS 操作系統的stack 段和heap 段中,你都可以創建數據對象。
stack 對象的優點主要有兩點,一是創建速度快,二是管理簡單,它有嚴格的生命週期。stack 對象的缺點是它不靈活。創建時長度是多大就一直是多 大,創建時是哪個函數創建的,它的owner 就一直是它。不像heap 對象那樣有多個owner ,其實多個owner 等同於引用計數。只有 heap 對象纔是採用“引用計數”方法管理它。

stack 對象的創建

只要棧的剩餘空間大於stack 對象申請創建的空間,操作系統就會爲程序提供這段內存空間,否則將報異常提示棧溢出。

heap 對象的創建

操作系統對於內存heap 段是採用鏈表進行管理的。操作系統有一個記錄空閒內存地址的鏈表,當收到程序的申請時,會遍歷鏈表,尋找第一個空間大於所申請的heap 節點,然後將該節點從空閒節點鏈表中刪除,並將該節點的空間分配給程序。
例如:
NSString 的對象就是stack 中的對象,NSMutableString 的對象就是heap 中的對象。前者創建時分配的內存長度固定且不可修改;後者是分配內存長度是可變的,可有多個owner, 適用於計數管理內存管理模式。
兩類對象的創建方法也不同,前者直接創建“NSString * str1=@”welcome”; “,而後者需要先分配再初始化“ NSMutableString * mstr1=[[NSMutableString alloc] initWithString:@”welcome”]; ”。
ios中堆棧的區別

管理方式:

對於棧來講,是由編譯器自動管理,無需我們手工控制;對於堆來講,釋放工作有程序員控制,容易產生memory Leak。

申請大小:

棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存區域。這句話的意思是棧頂上的地址和棧的最大容量是系統預先規定好的,在Windows下,棧的大小是2M(也有的說1M,總之是編譯器確定的一個常數),如果申請的空間超過了棧的剩餘空間時候,就overflow。因此,能獲得棧的空間較小。
堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是由於系統是用鏈表來存儲的空閒內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大笑受限於計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。

碎片的問題:

對於堆來講,頻繁的new/delete勢必會造成內存空間的不連續,從而造成大量的碎片,使程序效率降低。對於棧來講,則不會存在這個問題,因爲棧是先進後出的隊列,他們是如此的一一對應,以至於永遠都不可能有一個內存快從棧中彈出。

分配方式:

堆都是動態分配的,沒有靜態分配的堆。棧有兩種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如局部變量的分配。動態分配是有alloc函數進行分配的,但是棧的動態分配和堆是不同的,他的動態分配由編譯器進行釋放,無需我們手工實現。
分配效率:
棧是機器系統提供的數據結構,計算機會在底層堆棧提供支持,分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。堆則是C/C++函數庫提供的,他的機制是很複雜的。

12.淺談數據庫的存儲過程

13.殭屍對象、野指針、空指針分別指什麼,有什麼區別?

殭屍對象:一個OC對象引用計數爲0被釋放後就變成殭屍對象了,殭屍對象的內存已經被系統回收,雖然可能該對象還存在,數據依然在內存中,但殭屍對象已經是不穩定對象了,不可以再訪問或者使用,它的內存是隨時可能被別的對象申請而佔用的;
野指針:野指針出現的原因是指針沒有賦值,或者指針指向的對象已經被釋放掉了,野指針指向一塊隨機的垃圾內存,向他們發送消息會報EXC_BAD_ACCESS錯誤導致程序崩潰;
空指針:空指針不同於野指針,它是一個沒有指向任何東西的指針,空指針是有效指針,值爲nil、NULL、Nil或0等,給空指針發送消息不會報錯,只是不響應消息而已,應該給野指針及時賦予零值變成有效的空指針,避免內存報錯。

14.Objective-C有GC垃圾回收機制嗎?

GC(Garbage Collection),垃圾回收機制,簡單地說就是程序中及時處理廢棄不用的內存對象的機制,防止內存中廢棄對象堆積過多造成內存泄漏。Objective-C語言本身是支持垃圾回收機制的,但有平臺侷限性,僅限於Mac桌面系統開發中,而在iPhone和iPad等蘋果移動終端設備中是不支持垃圾回收機制的。在移動設備開發中的內存管理是採用MRC(Manual Reference Counting)以及iOS5以後的ARC(Automatic Reference Counting),本質都是RC引用計數,通過引用計數的方式來管理內存的分配與釋放,從而防止內存泄漏。
另外引用計數RC和垃圾回收GC是有區別的。垃圾回收是宏觀的,對整體進行內存管理,雖然不同平臺垃圾回收機制有異,但基本原理都是一樣的:將所有對象看做一個集合,然後在GC循環中定時檢測活動對象和非活動對象,及時將用不到的非活動對象釋放掉來避免內存泄漏,也就是說用不到的垃圾對象是交給GC來管理釋放的,而無需開發者關心,典型的是Java中的垃圾回收機制;相比於GC,引用計數是局部性的,開發者要管理控制每個對象的引用計數,單個對象引用計數爲0後會馬上被釋放掉。ARC自動引用計數則是一種改進,由編譯器幫助開發者自動管理控制引用計數(自動在合適的時機發送release和retain消息)。另外自動釋放池autorelease pool則像是一個局部的垃圾回收,將部分垃圾對象集中釋放,相對於單個釋放會有一定延遲。

15.自動釋放池跟GC(垃圾回收)有什麼區別?iPhone上有GC麼?[pool release]和[pool drain]有什麼區別?

[pool release]和[pool drain]在作用上是一樣的,都是傾倒自動釋放池,區別是drain在支持GC垃圾回收的系統中(Mac系統)可以引起GC回收操作,而release不可以。對於自動釋放池一般還是使用[pool drain]了,一是它的功能對系統兼容性更強,二者也是爲了跟普通對象的release釋放區別開。自動釋放池的釋放操作指的是向池內所有的對象發送release消息,以讓系統及時釋放池內的所有對象。

16.如果一個對象釋放前被加到了NotificationCenter中,不在NotificationCenter中remove這個對象可能會出現什麼問題?

首先對於NotificationCenter的使用,我們都知道,只要添加對象到消息中心進行通知註冊,之後就一定要對其remove進行通知註銷。將對象添加到消息中心後,消息中心只是保存該對象的地址,消息中心到時候會根據地址發送通知給該對象,但並沒有取得該對象的強引用,對象的引用計數不會加1。如果對象釋放後卻沒有從消息中心remove掉進行通知註銷,也就是通知中心還保存着那個指針,而那個指針指的對象可能已經被釋放銷燬了,那個指針就成爲一個野指針,當通知發生時,會向這個野指針發送消息導致程序崩潰。

17.Objective-C是如何實現內存管理的?autorealease pool自動釋放池是什麼?autorelease的對象是在什麼時候被release的?autorelease和release有什麼區別?

引用計數
Objective-C的內存管理本質上是通過引用計數實現的,每次RunLoop都會檢查對象的引用計數,如果引用計數爲0,說明該對象已經沒人用了,可以對其進行釋放了。其中引用計數可以大體分爲三種:MRC(手動內存計數)、ARC(自動內存計數,iOS5以後)和內存池。
其中引用計數是如何操作的呢?不論哪種引用計數方式,本質都是在合適的時機將對象的引用計數加1或者減1。
這裏簡單歸結一下:
使對象引用計數加1的常見操作有:alloc、copy、retain
使對象引用計數減1的常見操作有:release、autorealease
自動釋放池
自動釋放池是一個統一來釋放一組對象的容器,在向對象發送autorelease消息時,對象並沒有立即釋放,而是將對象加入到最新的自動釋放池(即將該對象的引用交給自動釋放池,之後統一調用release),自動釋放池會在程序執行到作用域結束的位置時進行drain釋放操作,這個時候會對池中的每一個對象都發送release消息來釋放所有對象。這樣其實就實現了這些對象的延遲釋放。
自動釋放池釋放的時機,也就是自動釋放池內的所有對象是在什麼時候釋放的,這裏要提到程序的運行週期RunLoop。對於每一個新的RunLoop,系統都會隱式的創建一個autorelease pool,RunLoop結束時自動釋放池便會進行對象釋放操作。

autorelease和release的區別主要是引用計數減一的時機不同,autorelease會在對象的使用真正結束的時候才做引用計數減1,而不是收到消息立馬釋放。
retain、release和autorelease的底層實現
最後通過了解這三者的較底層實現來理解它們的本質區別:
-(id)retain {
// 對象引用計數加1
NSIncrementExtraRefCount(self);
return self;
}

-(void)release {
// 對象引用計數減1,之後如果引用計數爲0則釋放
if(NSDecrementExtraRefCountWasZero(self)) {
NSDeallocateObject(self);
}
}

-(id)autorelease {
// 添加對象到自動釋放池
[NSAutoreleasePool addObject:self];
return self;
}

18. 爲什麼很多內置的類,如TableViewController的delegate的屬性是assign不是retain?

delegate代理的屬性通常設置爲assign或者weak是爲了避免循環引用,所有的引用計數系統,都存在循環引用的問題,但也有個別特殊情況,個別類的代理例如CAAnimation的delegate就是使用strong強引用。
其他問法: 委託的property聲明用什麼屬性?爲什麼?

19. CAAnimation的delegate代理是強引用還是弱引用?

CAAnimation的代理是強引用,是內存管理中的其中一個罕見的特例。我們知道爲了避免循環引用問題,delegate代理一般都使用weak修飾表示弱引用的,而CAAnimation動畫是異步的,如果動畫的代理是弱應用不是強應用的話,會導致其隨時都可能被釋放掉。在使用動畫時要注意採取措施避免循環引用,例如及時在視圖移除之前的合適時機移除動畫。
CAAnimation的代理定義如下,明確說了動畫的代理在動畫對象整個生命週期間是被強引用的,默認爲nil。
/* The delegate of the animation. This object is retained for the
* lifetime of the animation object. Defaults to nil. See below for the
* supported delegate methods. */

@property(nullable, strong) id <CAAnimationDelegate> delegate;

20. OC中,與alloc語義相反的方法是dealloc還是release?與retain語義相反的方法是dealloc還是release?需要與alloc配對使用的方法是dealloc還是release,爲什麼?

alloc與dealloc語意相反,alloc是創建變量,dealloc是釋放變量;
retain與release語義相反,retain保留一個對象,調用後使變量的引用計數加1,而release釋放一個對象,調用後使變量的引用計數減1。
雖然alloc對應dealloc,retain對應release,但是與alloc配對使用的方法是release,而不是dealloc。爲什麼呢?這要從他們的實際效果來看。事實上alloc和release配對使用只是表象,本質上其實還是retain和release的配對使用。alloc用來創建對象,剛創建的對象默認引用計數爲1,相當於調用alloc創建對象過程中同時會調用一次retain使對象引用計數加1,自然要有對應的release的一次調用,使對象在不用時能夠被釋放掉防止內存泄漏。
此外,dealloc是在對象引用計數爲0以後系統自動調用的,dealloc沒有使對象引用計數減1的作用,只是在對象引用計數爲0後被系統調用進行內存回收的收尾工作。

21.以下每行代碼執行後,person對象的retain count分別是多少

Person *person = [[Person alloc] init];
[person retain];
[person release];
[person release];
1-2-1-0。開始alloc創建對象並持有對象,初始引用計數爲1,retain一次引用計數加1變爲2,之後release對象兩次,引用計數減1兩次,先後變爲1、0。

22.問題:執行下面的代碼會發生什麼後果?

Ball *ball = [[[[Ball alloc] init] autorelease] autorelease];
程序會因其而崩潰,因爲對象被加入到自動釋放池兩次,當對象被移除時,自動釋放池將其釋放了不止一次,其中第二次釋放必定導致崩潰。

23.內存管理的幾條原則是什麼?按照默認法則,哪些關鍵字生成的對象需要手動釋放?哪些對象不需要手動釋放會自動進入釋放池?在和property結合的時候怎樣有效的避免內存泄露?

起初MRC中開發者要自己手動管理內存,基本原則是:誰創建,誰釋放,誰引用,誰管理。其中創建主要始於關鍵詞new、alloc和copy的使用,創建並持有開始引用計數爲1,如果引用要通過發送retain消息增加引用計數,通過發送release消息減少引用計數,引用計數爲0後對象會被系統清理釋放。現在有了ARC後編譯器會自動管理引用計數,開發者不再需要也不可以再手動顯示管理引用計數。
當使用new、alloc或copy方法創建一個對象時,該對象引用計數器爲1。不再需要使用該對象時可以向其發送release或autorelease消息,在其使用完畢時被系統釋放。如果retain了某個對象,需要對應release或autorelease該對象,保持retain方法和release方法使用次數相等。
使用new、alloc、copy關鍵字生成的對象和retain了的對象需要手動釋放。設置爲autorelease的對象不需要手動釋放,會直接進入自動釋放池。
下面代碼的輸出依次爲:
NSMutableArray* ary = [[NSMutableArray array] retain];
NSString *str = [NSString stringWithFormat:@"test"];
[str retain];
[ary addObject:str];
NSLog(@"%@%d",str,[str retainCount]);
[str retain];
[str release];
[str release];
NSLog(@"%@%d",str,[str retainCount]);
[ary removeAllObjects];
NSLog(@"%@%d",str,[str retainCount]);
  • 2,3,1
  • 3,2,1(right)
  • 1,2,3
  • 2,1,3
    此問題考查的是非MRC下引用計數的使用(只有在MRC下纔可以通過retain和release關鍵字手動管理內存對象,纔可以向對象發送retainCount消息獲取當前引用計數的值),開始使用類方法stringWithFormat在堆上新創建了一個字符串對象str,str創建並持有該字符串對象默認引用計數爲1,之後retain使引用計數加1變爲2,然後又動態添加到數組中且該過程同樣會讓其引用計數加1變爲3(數組的add操作是添加對成員對象的強引用),此時打印結果引用計數爲3;之後的三次操作使引用計數加1後又減2,變爲2,此時打印引用計數結果爲2;最後數組清空成員對象,數組的remove操作會讓移除的對象引用計數減1,因此str的引用計數變爲了1,打印結果爲1。因此先後引用計數的打印結果爲:3,2,1。
這裏要特別注意上面爲何說stringWithFormat方法是在堆上創建的字符串對象,這裏涉及到NSString的內存管理,下面單獨對其進行擴展和分析。
OC中常用的創建NSString字符串對象的方法主要有以下五種:
// 字面量直接創建
NSString *str1 = @"string";
// 類方法創建
NSString *str2 = [NSString stringWithFormat:@"string"];
NSString *str3 = [NSString stringWithString:@"string"]; // 編譯器優化後棄用,效果等同於str1的字面量創建方式
// 實例方法創建
NSString *str4 = [[NSString alloc] initWithFormat:@"string"];
NSString *str5 = [[NSString alloc] initWithString:@"string"]; // 編譯器優化後棄用,效果等同於str1的字面量創建方式
開發中推薦的是前兩種str1和str2的創建方式,分別用來創建不可變字符串和格式化字符串。最新的編譯器優化後棄用了str3的stringWithString和str5的initWithString創建方式,現在這樣創建會報警告,說這樣創建是多餘的,因爲實際效果和直接用字面量創建相同,也都是在常量內存區創建一個不可變字符串。另外,此處由於字符串的內容都是“string”,使用str1、str3和str5創建的的字符串對象實際在常量內存區只有一個備份,這是編譯器的優化效果,而str2和str4由於是在堆上創建因此各自有自己的備份。
此外最重要的是這五種方法創建的字符串對象所處的內存類型,str1、str3和str5都是創建的不可變字符串,是位於常量內存區的,由系統管理內存;stringWithFormat和initWithFormat創建的都是格式化的動態字符串對象,在堆上創建,需要手動管理內存。
相關問題:當你用stringWithString來創建一個新NSString對象的時候,你可以認爲:
這個新創建的字符串對象已經被autorelease了(right)
這個新創建的字符串對象已經被retain了
全都不對
這個新創建的字符串對象已經被release了

22.什麼是安全釋放?

釋放掉不再使用的對象同時不會造成內存泄漏或指針懸掛問題稱其爲安全釋放。

23.這段代碼有什麼問題,如何修改?

for (int i = 0; i < someLargeNumber; i++) {
NSString *string = @”Abc”;
string = [string lowercaseString];
string = [string stringByAppendingString:@"xyz"];
NSLog(@“%@”, string);
}
代碼通過循環短時間內創建了大量的NSString對象,在默認的自動釋放池釋放之前這些對象無法被立即釋放,會佔用大量內存,造成內存高峯以致內存不足。
爲了防止大量對象堆積應該在循環內手動添加自動釋放池,這樣在每一次循環結束,循環內的自動釋放池都會被自動釋放及時騰出內存,從而大大縮短了循環內對象的生命週期,避免內存佔用高峯。
代碼改進方法是在循環內部嵌套一個自動釋放池:
for (int i = 0; i < 1000000; i++) {
@autoreleasepool {
NSString *string = @"Abc";
string = [string lowercaseString];
string = [string stringByAppendingString:@"xyz"];
NSLog(@"%@",string);
}
}
相關問題:這段代碼有什麼問題?會不會造成內存泄露(多線程)?在內存緊張的設備上做大循環時自動釋放池是寫在循環內好還是循環外好?爲什麼?
for(int index = 0; index < 20; index++) {
NSString *tempStr = @”tempStr”;
NSLog(tempStr);
NSNumber *tempNumber = [NSNumber numberWithInt:2];
NSLog(tempNumber);
}

24.在block內如何修改block外部變量?

在block內部修改block外部變量會編譯不通過,提示變量缺少block修飾,不可賦值。要想在block內部修改block外部變量,則必須在外部定義變量時,前面加上block修飾符:
/* block外部變量 */
__block int var1 = 0;
int var2 = 0;
/* 定義block */
void (^block)(void) = ^{
/* 試圖修改block外部變量 */
var1 = 100;
/* 編譯錯誤,在block內部不可對var2賦值 */
// var2 = 1;
};
/* 執行block */
block();
NSLog(@"修改後的var1:%d",var1); // 修改後的var1:100

25.使用block時什麼情況會發生引用循環,如何解決?

常見的使用block引起引用循環的情況爲:在一個對象中強引用了一個block,在該block中又強引用了該對象,此時就出現了該對象和該block的循環引用,例如:
/* Test.h */
#import <Foundation/Foundation.h>
/* 聲明一個名爲MYBlock的block,參數爲空,返回值爲void */
typedef void (^MYBlock)();

@interface Test : NSObject
/* 定義並強引用一個MYBlock */
@property (nonatomic, strong) MYBlock block;
/* 對象屬性 */
@property (nonatomic, copy) NSString *name;

- (void)print;

@end

/* Test.m */
#import "Test.h"
@implementation Test

- (void)print {
self.block = ^{
NSLog(@"%@",self.name);
};
self.block();
}

@end
解決上面的引用循環的方法一個是強制將一方置nil,破壞引用循環,另外一種方法是將對象使用weak或者block修飾符修飾之後再在block中使用(注意是在ARC下):
- (void)print {
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%@",weakSelf.name);
};
self.block();
}

26.http://yulingtianxia.com/blog/2016/06/15/Objective-C-Message-Sending-and-Forwarding/“>消息發送和轉發機制

27.Block相關

  1. block的實質是什麼?一共有幾種block?都是什麼情況下生成的?
  2. 爲什麼在默認情況下無法修改被block捕獲的變量? __block都做了什麼?
  3. 模擬一下循環引用的一個情況?block實現界面反向傳值如何實現?

28.Runtime

  1. objc在向一個對象發送消息時,發生了什麼?
  2. 什麼時候會報unrecognized selector錯誤?iOS有哪些機制來避免走到這一步?
  3. 能否向編譯後得到的類中增加實例變量?能否向運行時創建的類中添加實例變量?爲什麼?
  4. runtime如何實現weak變量的自動置nil?
  5. 給類添加一個屬性後,在類結構體裏哪些元素會發生變化?

29.RunLoop

  1. runloop是來做什麼的?runloop和線程有什麼關係?主線程默認開啓了runloop麼?子線程呢?
  2. runloop的mode是用來做什麼的?有幾種mode?
  3. 爲什麼把NSTimer對象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主運行循環以後,滑動scrollview的時候NSTimer卻不動了?
  4. 蘋果是如何實現Autorelease Pool的?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章