iOS常見基本問題整理 (I)

1. Object-C有多繼承嗎?沒有的話用什麼代替?

cocoa 中所有的類都是NSObject 的子類,多繼承在這裏是用protocol 委託代理來實現的。你不用去考慮繁瑣的多繼承 ,虛基類的概念。ood的多態特性在 obj-c 中通過委託來實現。

2.Object-C有私有方法嗎?私有變量呢?

objective-c類裏面的方法只有兩種: 靜態方法和實例方法。這似乎就不是完整的面向對象了,按照OO的原則就是一個對象只暴露有用的東西。如果沒有了私有方法的話,對於一些小範圍的代碼重用就不那麼順手了。在類裏面聲名一個私有方法

<span style="font-size:14px;"><span style="font-size:14px;">@interface Controller : NSObject {
<span style="white-space:pre">	</span>NSString *something; 
}
+ (void)thisIsAStaticMethod;
–(void)thisIsAnInstanceMethod;
@end
@interface Controller (private) 
– (void)thisIsAPrivateMethod;
@end</span></span>

@private可以用來修飾私有變量
在Objective‐C中,所有實例變量默認都是私有的,所有實例方法默認都是公有的

3.const關鍵字的含義

(1)阻止一個變量被改變,可以使用 const 關鍵字。在定義該 const 變量時,通常需要對它進行初始化,因爲以後就沒有機會再去改變它了;
(2)對指針來說,可以指定指針本身爲 const,也可以指定指針所指的數據爲 const,或二者同時指定爲 const;
(3)在一個函數聲明中,const 可以修飾形參,表明它是一個輸入參數,在函數內部不能改變其值;
(4)對於類的成員函數,若指定其爲 const 類型,則表明其是一個常函數,不能修改類的成員變量;
(5)對於類的成員函數,有時候必須指定其返回值爲 const 類型,以使得其返回值不爲”左值”。

4.static作用?

(1)函數體內的 static 變量的作用範圍爲該函數體,不同於 auto 變量,該變量的內存只被分配一次,因此其值在下次調用時仍維持上次的值;
(2)在模塊內的 static 全局變量可以被模塊內所用函數訪問,但不能被模塊外其它函數訪問;
(3)在模塊內的 static 函數只可被這一模塊內的其它函數調用,這個函數的使用範圍被限制在聲明它的模塊內;
(4)在類中的 static 成員變量屬於整個類所擁有,對類的所有對象只有一份拷貝;
(5)在類中的 static 成員函數屬於整個類所擁有,這個函數不接收 this 指針,因而只能訪問類的static 成員變量。

5.#import和#include的區別,@class代表什麼?

@class一般用於頭文件中需要聲明該類的某個實例變量的時候用到,在m文件中還是需要使用#import而#import比起#include的好處就是不會引起重複包含

6.線程和進程的區別?

進程和線程都是由操作系統所體會的程序運行的基本單元,系統利用該基本單元實現系統對應用的併發性。
進程和線程的主要差別在於它們是不同的操作系統資源管理方式。進程有獨立的地址空間,一個進程崩潰後,在保護模式下不會對其它進程產生影響;而線程只是一個進程中的不同執行路徑,線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個進程死掉,所以多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行並且又要共享某些變量的併發操作,只能用線程,不能用進程。

7.堆和棧的區別?

管理方式:對於棧來講,是由編譯器自動管理,無需我們手工控制;對於堆來說,釋放工作由程序員控制,容易產生memory leak。
申請大小:
棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就確定的常數),如果申請的空間超過棧的剩餘空間時,將提示overflow。因此,能從棧獲得的空間較小。
堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是由於系統是用鏈表來存儲的空閒內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。
碎片問題:對於堆來講,頻繁的new/delete勢必會造成內存空間的不連續,從而造成大量的碎片,使程序效率降低。對於棧來講,則不會存在這個問題,因爲棧是先進後出的隊列,他們是如此的一一對應,以至於永遠都不可能有一個內存塊從棧中間彈出
分配方式:堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如局部變量的分配。動態分配由alloc函數進行分配,但是棧的動態分配和堆是不同的,他的動態分配是由編譯器進行釋放,無需我們手工實現。
分配效率:棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高堆則是C/C++函數庫提供的,它的機制是很複雜的。

8.Object-C的內存管理?

1.當你使用new,alloc和copy方法創建一個對象時,該對象的保留計數器值爲1。當你不再使用該對象時,你要負責向該對象發送一條release或autorelease消息。這樣,該對象將在使用壽命結束時被銷燬。
2.當你通過任何其他方法獲得一個對象時,則假設該對象的保留計數器值爲1,而且已經被設置爲自動釋放,你不需要執行任何操作來確保該對象被清理。如果你打算在一段時間內擁有該對象,則需要保留它並確保在操作完成時釋放它。
3.如果你保留了某個對象,你需要(最終)釋放或自動釋放該對象。必須保持retain方法和release方法的使用次數相等。

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

循環引用
所有的引用計數系統,都存在循環引用的問題。例如下面的引用關係:
• 對象a創建並引用到了對象b.
• 對象b創建並引用到了對象c.
• 對象c創建並引用到了對象b.
這時候b和c的引用計數分別是2和1。當a不再使用b,調用release釋放對b的所有權,因爲c還引用了b,所以b的引用計數爲1,b不會被釋放。b不釋放,c的引用計數就是1,c也不會被釋放。從此,b和c永遠留在內存中。
這種情況,必須打斷循環引用,通過其他規則來維護引用關係。比如,我們常見的delegate往往是assign方式的屬性而不是retain方式的屬性,賦值不會增加引用計數,就是爲了防止delegate兩端產生不必要的循環引用。如果一個UITableViewController 對象a通過retain獲取了UITableView對象b的所有權,這個UITableView對象b的delegate又是a,如果這個delegate是retain方式的,那基本上就沒有機會釋放這兩個對象了。自己在設計使用delegate模式時,也要注意這點。

10.定義屬性時,什麼情況使用copy、assign、retain?

assign用於簡單數據類型,如NSInteger,double,bool,
retain和copy用於對象,
copy用於當a指向一個對象,b也想指向同樣的對象的時候,如果用assign,a如果釋放,再調用b會crash,如果用copy 的方式,a和b各自有自己的內存,就可以解決這個問題。
retain 會使計數器加一,也可以解決assign的問題。
另外:atomic和nonatomic用來決定編譯器生成的getter和setter是否爲原子操作。在多線程環境下,原子操作是必要的,否則有可能引起錯誤的結果。
加了atomic,setter函數會變成下面這樣:

<span style="font-size:14px;">if (property != newValue) {
<span style="white-space:pre">	</span>[property release];
<span style="white-space:pre">	</span>property = [newValue retain];
}</span>
11.對象是什麼時候被release的?

引用計數爲0時。autorelease實際上只是把對release的調用延遲了,對於每一個Autorelease,系統只是把該Object放入了當前的Autorelease pool中,當該pool被釋放時,該pool中的所有Object會被調用Release。對於每一個Runloop,系統會隱式創建一個Autorelease pool,這樣所有的release pool會構成一個象CallStack一樣的一個棧式結構,在每一個Runloop結束時,當前棧頂的Autorelease pool會被銷燬,這樣這個pool裏的每個Object(就是autorelease的對象)會被release。那什麼是一個Runloop呢?一個UI事件,Timer call, delegate call,都會是一個新的Runloop。

12.iOS有沒有垃圾回收?

Objective-C 2.0也是有垃圾回收機制的,但是只能在Mac OS X Leopard 10.5 以上的版本使用。

13.tableView的重用機制?

查看UITableView頭文件,會找到NSMutableArray* visiableCells,和NSMutableDictnery* reusableTableCells兩個結構。visiableCells內保存當前顯示的cells,reusableTableCells保存可重用的cells。

TableView顯示之初,reusableTableCells爲空,那麼tableView dequeueReusableCellWithIdentifier:CellIdentifier返回nil。開始的cell都是通過[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]來創建,而且cellForRowAtIndexPath只是調用最大顯示cell數的次數。

比如:有100條數據,iPhone一屏最多顯示10個cell。程序最開始顯示TableView的情況是:

1. 用[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]創建10次cell,並給cell指定同樣的重用標識(當然,可以爲不同顯示類型的cell指定不同的標識)。並且10個cell全部都加入到visiableCells數組,reusableTableCells爲空。

2. 向下拖動tableView,當cell1完全移出屏幕,並且cell11(它也是alloc出來的,原因同上)完全顯示出來的時候。cell11加入到visiableCells,cell1移出visiableCells,cell1加入到reusableTableCells。

3. 接着向下拖動tableView,因爲reusableTableCells中已經有值,所以,當需要顯示新的cell,cellForRowAtIndexPath再次被調用的時候,tableView dequeueReusableCellWithIdentifier:CellIdentifier,返回cell1。cell1加入到visiableCells,cell1移出reusableTableCells;cell2移出visiableCells,cell2加入到reusableTableCells。之後再需要顯示的Cell就可以正常重用了。

14.ViewController 的loadView、viewDidLoad、viewDidUnload分別是什麼時候調用的,在自定義ViewCointroller時在這幾個函數中應該做什麼工作?

由init、loadView、viewDidLoad、viewDidUnload、dealloc的關係說起
init方法
在init方法中實例化必要的對象(遵從LazyLoad思想),init方法中初始化ViewController本身。

loadView方法

永遠不要主動調用這個函數。view controller會在view的property被請求並且當前view值爲nil時調用這個函數。如果你手動創建view,你應該重載這個函數。如果你用IB創建view並初始化view controller,那就意味着你使用initWithNibName:bundle:方法,這時,你不應該重載loadView函數。
這個方法的默認實現是這樣:先尋找有關可用的nib文件的信息,根據這個信息來加載nib文件,如果沒有有關nib文件的信息,默認實現會創建一個空白的UIView對象,然後讓這個對象成爲controller的主view。所以,重載這個函數時,你也應該這麼做。並把子類的view賦給view屬性(property)(你create的view必須是唯一的實例,並且不被其他任何controller共享),而且你重載的這個函數不應該調用super。
如果你要進行進一步初始化你的views,你應該在viewDidLoad函數中去做。在iOS 3.0以及更高版本中,你應該重載viewDidUnload函數來釋放任何對view的引用或者它裏面的內容(子view等等)。

viewDidLoad方法
這個函數在controller加載了相關的views後被調用,而不論這些views存儲在nib文件裏還是在loadView函數中生成。而多數情況下是做nib文件的後續工作,比如調用數據Model。

viewDidUnload方法

這個函數是viewDidLoad的對立函數。在程序內存欠缺時,這個函數被controller調用。由於controller通常保存着與view(這裏黑體的view指controller的view屬性)相關的對象(一般是view的子view)或者其他運行時創建的對象的引用,所以你必須使用這個函數來放棄這些對象的所有權以便內存回收。但不要釋放那些難以重建的數據(不要在這個函數中釋放view)。
通常controller會保存nib文件建立的views的引用,但是也可能會保存着loadView函數創建的對象的引用。最完美的方法是使用合成器方法:self.myCertainView = nil;這樣合成器會release這個view,如果你沒有使用property,那麼你得自己顯式釋放這個view。網上對這個函數的描述含含糊糊,看了等於沒看。另外:如果controller存儲了其他object和view的引用,你還得在dealloc方法中釋放這些內存。對於iOS2.x,你還必須在調用super dealloc方法前將這些引用置爲nil。

三個方法的關係
1.第一次訪問UIViewController的view時,view爲nil,然後就會調用loadView方法創建view
2.view創建完畢後會調用viewDidLoad方法進行界面元素的初始化
3.當內存警告時,系統可能會釋放UIViewController的view,將view賦值爲nil,並且調用viewDidUnload方法
4.當再次訪問UIViewController的view時,view已經在3中被賦值爲nil,所以又會調用loadView方法重新創建view
5.view被重新創建完畢後,還是會調用viewDidLoad方法進行界面元素的初始化

dealloc方法

viewDidUnload和dealloc方法沒有關聯,dealloc還是繼續做它該做的事情

15.ViewController的didReceiveMemoryWarning是在什麼時候調用的?默認的操作是什麼?

當程序接到內存警告時View Controller將會收到這個消息:didReceiveMemoryWarning

從iOS3.0開始,不需要重載這個函數,把釋放內存的代碼放到viewDidUnload中去。

這個函數的默認實現是:檢查controller是否可以安全地釋放它的view(這裏加粗的view指的是controller的view屬性),比如view本身沒有superview並且可以被很容易地重建(從nib或者loadView函數)。

如果view可以被釋放,那麼這個函數釋放view並調用viewDidUnload。

你可以重載這個函數來釋放controller中使用的其他內存。但要記得調用這個函數的super實現來允許父類(一般是UIVIewController)釋放view。

如果你的ViewController保存着view的子view的引用,那麼,在早期的iOS版本中,你應該在這個函數中來釋放這些引用。而在iOS3.0或更高版本中,你應該在viewDidUnload中釋放這些引用.



發佈了27 篇原創文章 · 獲贊 0 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章