iOS面試題 - 總會有你需要的(一)


還有第二篇在這里->iOS面試題 - 總會有你需要的(二)

基礎

<a href="#overview">1. 為什么說Objective-C是一門動態的語言?</a>
<a href="#2">2. 講一下MVC和MVVM,MVP?</a>
<a href="#3">3. 為什么代理要用weak?代理的delegate和dataSource有什么區別?block和代理的區別?</a>
<a href="#4">4. 屬性的實質是什么?包括哪幾個部分?</a>
<a href="#5">5. 屬性默認的關鍵字都有哪些?@dynamic關鍵字和@synthesize關鍵字是用來做什么的?</a>
<a href="#6">6. NSString為什么要用copy關鍵字,如果用strong會有什么問題?(注意:這里沒有說用strong就一定不行。使用copy和strong是看情況而定的)</a>
<a href="#7">7. 如何令自己所寫的對象具有拷貝功能?</a>
<a href="#8">8. 可變集合類 和 不可變集合類的 copy 和 mutablecopy有什么區別?如果是集合是內容復制的話,集合里面的元素也是內容復制么?</a>
<a href="#9">9. 為什么IBOutlet修飾的UIView也適用weak關鍵字?</a>
<a href="#10">10. nonatomic和atomic的區別?atomic是絕對的線程安全么?為什么?如果不是,那應該如何實現?</a>
<a href="#11">11. UICollectionView自定義layout如何實現?</a>
<a href="#12">12. 用StoryBoard開發界面有什么弊端?如何避免?</a>
<a href="#13">13. 進程和線程的區別?同步異步的區別?并行和并發的區別?</a>
<a href="#14">14. 線程間通信?</a>
<a href="#15">15. GCD的一些常用的函數?(group,barrier,信號量,線程同步)</a>
<a href="#16">16. 如何使用隊列來避免資源搶奪?</a>
<a href="#17">17. 數據持久化的幾個方案(fmdb用沒用過)</a>
<a href="#18">18. 說一下AppDelegate的幾個方法?從后臺到前臺調用了哪些方法?第一次啟動調用了哪些方法?從前臺到后臺調用了哪些方法?</a>
<a href="#19">19. NSCache優于NSDictionary的幾點?</a>
<a href="#20">20. 知不知道Designated Initializer?使用它的時候有什么需要注意的問題?</a>
<a href="#21">21. 實現description方法能取到什么效果?</a>
<a href="#22">22. objc使用什么機制管理對象內存?</a>

中級

Block

<a href="#23">1. block的實質是什么?一共有幾種block?都是什么情況下生成的?</a>
<a href="#24">2. 為什么在默認情況下無法修改被block捕獲的變量? __block都做了什么?</a>
<a href="#25">3. 模擬一下循環引用的一個情況?block實現界面反向傳值如何實現?</a>

Runtime

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

RunLoop

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

類結構

1. isa指針?(對象的isa,類對象的isa,元類的isa都要說)
2. 類方法和實例方法有什么區別?
3. 介紹一下分類,能用分類做什么?內部是如何實現的?它為什么會覆蓋掉原來的方法?
4. 運行時能增加成員變量么?能增加屬性么?如果能,如何增加?如果不能,為什么?
5. objc中向一個nil對象發送消息將會發生什么?(返回值是對象,是標量,結構體)

高級

1. UITableview的優化方法(緩存高度,異步繪制,減少層級,hide,避免離屏渲染)
2. 有沒有用過運行時,用它都能做什么?(交換方法,創建類,給新創建的類增加方法,改變isa指針)
3. 看過哪些第三方框架的源碼?都是如何實現的?(如果沒有,問一下多圖下載的設計)
4. SDWebImage的緩存策略?
5. AFN為什么添加一條常駐線程?
6. KVO的使用?實現原理?(為什么要創建子類來實現)
7. KVC的使用?實現原理?(KVC拿到key以后,是如何賦值的?知不知道集合操作符,能不能訪問私有屬性,能不能直接訪問_ivar)

項目

1. 有已經上線的項目么?
2. 項目里哪個部分是你完成的?(找一個亮點問一下如何實現的)
3. 開發過程中遇到過什么困難,是如何解決的?

學習

1. 遇到一個問題完全不能理解的時候,是如何幫助自己理解的?舉個例子?
2. 有看書的習慣么?最近看的一本是什么書?有什么心得?
3. 有沒有使用一些筆記軟件?會在多平臺同步以及多渠道采集么?(如果沒有,問一下是如何復習知識的)
4. 有沒有使用清單類,日歷類的軟件?(如果沒有,問一下是如何安排,計劃任務的)
5. 平常看博客么?有沒有自己寫過?(如果寫,有哪些收獲?如果沒有寫,問一下不寫的原因)

答案區

<h2 id="overview">1.為什么說Objective-C是一門動態的語言? </h2>
Objective-C具有相當多的動態特性,基本的,也是經常被提到和用到的有動態類型(Dynamic typing),動態綁定(Dynamic binding)和動態加載(Dynamic loading)。

動態特性基礎
1、動態類型

即運行時再決定對象的類型。這類動態特性在日常應用中非常常見,簡單說就是id類型。id類型即通用的對象類,任何對象都可以被id指針所指,而在實際使用中,往往使用introspection來確定該對象的實際所屬類:


id obj = someInstance;
if ([obj isKindOfClass:someClass])
{
    someClass *classSpecifiedInstance = (someClass *)obj;
    // Do Something to classSpecifiedInstance which now is an instance of someClass
    //...
}

-isMemberOfClass:NSObject的方法,用以確定某個NSObject 對象是否是某個類的成員。與之相似的為-isKindOfClass:,可以用以確定某個對象是否是某個類或其子類的成員。這兩個方法為典型的introspection方法。在確定對象為某類成員后,可以安全地進行強制轉換,繼續之后的工作。

2、動態綁定
基于動態類型,在某個實例對象被確定后,其類型便被確定了。該對象對應的屬性和響應的消息也被完全確定,這就是動態綁定。在繼續之前,需要明確objective-c中消息的概念。由于OC的動態特性,在OC中其實很少提及“函數”的概念,傳統的函數一般在編譯時就已經把參數信息和函數實現打包到編譯后的源碼中了,而在OC中最常使用的是消息機制。調用一個實例的方法,所做的是向該實例的指針發送消息,實例在收到消息后,從自身的實現中尋找響應這條消息的方法。
動態綁定所做的,即是在實例所屬類確定后,將某些屬性和相應的方法綁定到實例上。這里所指的屬性和方法當然包括了原來沒有在類中實現的,而是在運行時才需要的新加入的實現。在Cocoa層,我們一般向一個NSObject對象發送-respondsToSelector:或者-instancesRespondToSelector:等來確定對象是否可以對某個SEL做出響應,而在OC消息轉發機制被觸發之前,對應的類的+resolveClassMethod:和+resolveInstanceMethod:將會被調用,在此時有機會動態地向類或者實例添加新的方法,也即類的實現是可以動態綁定的。一個例子:

void dynamicMethodIMP(id self, SEL _cmd)
{
    // implementation ....
}
//該方法在OC消息轉發生效前被調用
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{ 
    if (aSEL == @selector(resolveThisMethodDynamically)) {
        //向[self class]中新加入返回為void的實現,SEL名字為aSEL,實現的具體內容為dynamicMethodIMP class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, “v@:”);
        return YES;
    }
    return [super resolveInstanceMethod:aSel];
}  

當然也可以在任意需要的地方調用class_addMethod或者method_setImplementation(前者添加實現,后者替換實現),來完成動態綁定的需求。

3、動態加載
根據需求加載所需要的資源,這點很容易理解,對于iOS開發來說,基本就是根據不同的機型做適配。最經典的例子就是在Retina設備上加載@2x的圖片,而在老一些的普通屏設備上加載原圖。隨著Retina iPad的推出,和之后可能的Retina Mac的出現,這個特性相信會被越來越多地使用。
深入運行時特性
基本的動態特性在常規的Cocoa開發中非常常用,特別是動態類型和動態綁定。由于Cocoa程序大量地使用Protocol-Delegate的設計模式,因此絕大部分的delegate指針類型必須是id,以滿足運行時delegate的動態替換(在Java里這個設計模式被叫做Strategy?不是很懂Java,求糾正)。而Objective-C還有一些高級或者說更底層的運行時特性,在一般的Cocoa開發中較為少見,基本被運用與編寫OC和其他語言的接口上。但是如果有所了解并使用得當的話,在Cocoa開發中往往可以輕易解決一些棘手問題。
這類運行時特性大多由/usr/lib/libobjc.A.dylib這個動態庫提供,里面包括了對于類、實例成員、成員方法和消息發送的很多API,包括獲取類實例變量列表,替換類中的方法,為類成員添加變量,動態改變方法實現等,十分強大。完整的API列表和手冊可以在這里找到。雖然文檔開頭表明是對于Mac OS X Objective-C 2.0適用,但是由于這些是OC的底層方法,因此對于ios開發來說也是完全相同的。
一個簡單的例子,比如在開發Universal應用或者游戲時,如果使用IB構建了大量的自定義的UI,那么在由iPhone版轉向iPad版的過程中所面臨的一個重要問題就是如何從不同的nib中加載界面。在iOS5之前,所有的UIViewController
在使用默認的界面加載時(init或者initWithNibName:bundle:),都會走-loadNibNamed:owner:options:。而因為我們無法拿到loadNibNamed:owner:options的實現,因此對其重載是比較困難而且存在風險的。因此在做iPad版本的nib時,一個簡單的辦法是將所有的nib的命名方式統一,然后使用自己實現的新的類似-loadNibNamed:owner:options的方法將原方法替換掉,同時保證非iPad的設備還走原來的loadNibNamed:owner:options
方法。使用OC運行時特性可以較簡單地完成這一任務。
代碼如下,在程序運行時調用+swizze,交換自己實現的loadPadNibNamed:owner:options和系統的loadNibNamed:owner:options
,之后所有的loadNibNamed:owner:options消息都將會發為loadPadNibNamed:owner:options,由自己的代碼進行處理。

+(BOOL)swizze { Method oldMethod = class_getInstanceMethod(self, @selector(loadNibNamed:owner:options:)); 
if (!oldMethod){ 
    return NO;
 } 
Method newMethod = class_getInstanceMethod(self, @selector(loadPadNibNamed:owner:options:));
 if (!newMethod) {
    return NO; 
} 
method_exchangeImplementations(oldMethod, newMethod); 
    return YES;
}

loadPadNibNamed:owner:options的實現如下,注意在其中的loadPadNibNamed:owner:options由于之前已經進行了交換,因此實際會發送為系統的loadNibNamed:owner:options。以此完成了對不同資源的加載。

-(NSArray *)loadPadNibNamed:(NSString *)name owner:(id)owner options:(NSDictionary *)options { 
NSString *newName = [name stringByReplacingOccurrencesOfString:@"@pad" withString:@""]; 
newName = [newName stringByAppendingFormat:@"@pad"]; 

//判斷是否存在 
NSFileManager *fm = [NSFileManager defaultManager]; 
NSString* filepath = [[NSBundle mainBundle] pathForResource:newName ofType:@”nib”]; 

//這里調用的loadPadNibNamed:owner:options:實際為為交換后的方法,即loadNibNamed:owner:options:
   if ([fm fileExistsAtPath:filepath]) { 
      return [self loadPadNibNamed:newName owner:owner options:options]; 
  } else {
      return [self loadPadNibNamed:name owner:owner options:options];
  }
}

當然這只是一個簡單的例子,而且這個功能也可以通過別的方法來實現。比如添加UIViewController的類別來重載init,但是這樣的重載會比較危險,因為你UIViewController的實現你無法完全知道,因此可能會由于重載導致某些本來應有的init代碼沒有覆蓋,從而出現不可預測的錯誤。當然在上面這個例子中重載VC的init的話沒有什么問題(因為對于VC,init的方法會被自動轉發為loadNibNamed:owner:options,因此init方法并沒有做其他更復雜的事情,因此只要初始化VC時使用的都是init的話就沒有問題)。但是并不能保證這樣的重載對于其他類也是可行的。因此對于實現未知的系統方法,使用上面的運行時交換方法會是一個不錯的選擇~

<a id="2">2.講一下MVC和MVVM,MVP</a>

MVC:Model-View-Controller
MVP:Model-View-Presenter
MVVM:Model-View-ViewModel

先說一下三者的共同點,也就是Model和View Model就是領域模型,數據對象,同時,提供外部對應用程序數據的操作的接口,也可能在數據變化時發出變更通知。Model不依賴于View的實現,只要外部程序調用Model的接口就能夠實現對數據的增刪改查。
View就是UI層,提供對最終用戶的交互操作功能,包括UI展現代碼及一些相關的界面邏輯代碼。

三者的差異在于如何粘合View和Model,實現用戶的交互操作以及變更通知


Controller接收View的操作事件,根據事件不同,或者調用Model的接口進行數據操作,或者進行View的跳轉,從而也意味著一個Controller可以對應多個View。Controller對View的實現不太關心,只會被動地接收,Model的數據變更不通過Controller直接通知View,通常View采用觀察者模式監聽Model的變化。
Presenter,與Controller一樣,接收View的命令,對Model進行操作;與Controller不同的是Presenter會反作用于View,Model的變更通知首先被Presenter獲得,然后Presenter再去更新View。一個Presenter只對應于一個View。根據Presenter和View對邏輯代碼分擔的程度不同,這種模式又有兩種情況:Passive View和Supervisor Controller。
ViewModel,注意這里的“Model”指的是View的Model,跟上面那個Model不是一回事。所謂View的Model就是包含View的一些數據屬性和操作的這么一個東東,這種模式的關鍵技術就是數據綁定(data binding),View的變化會直接影響ViewModel,ViewModel的變化或者內容也會直接體現在View上。這種模式實際上是框架替應用開發者做了一些工作,開發者只需要較少的代碼就能實現比較復雜的交互。

MVP和MVVM完全隔離了Model和View,但是在有些情況下,數據從Model到ViewModel或者Presenter的拷貝開銷很大,可能也會結合MVC的方式,Model直接通知View進行變更。在實際的應用中很有可能你已經在不知不覺中將幾種模式融合在一起,但是為了代碼的可擴展、可測試性,必須做到模塊的解耦,不相關的代碼不要放在一起。
個人理解,在廣義地談論MVC架構時,并非指本文中嚴格定義的MVC,而是指的MV*,也就是視圖和模型的分離,只要一個框架提供了視圖和模型分離的功能,我們就可以認為它是一個MVC框架。在開發深入之后,可以再體會用到的框架到底是MVC、MVP還是MVVM。

<a id="3">3.為什么代理要用weak?代理的delegate和dataSource有什么區別?block和代理的區別?</a>

  • weak:指明該對象并不負責保持delegate這個對象,delegate這個對象的銷毀由外部控制
@property (nonatomic, weak) id<HSDogDelegate>delegate;
  • strong:該對象強引用delegate,外界不能銷毀delegate對象,會導致循環引用(Retain Cycles)
@property (nonatomic, strong) id<HSDogDelegate>delegate;

詳細理解查看此篇文章iOS 代理為啥要用weak修飾? (刨根問底一)

  • delegate是代理
  • datasource是數據源

datasource協議里面東西是跟內容有關的,主要是cell的構造函數,各種屬性
delegate協議里面的方法主要是操作相關的,移動編輯之類的

首先Delegate是委托的意思,在oc中則是一個類委托另一個類實現某個方法。當一個對象接受到某個事件或者通知的時候, 會向它的Delegate對象查詢它是否能夠響應這個事件或者通知,如果可以這個對象就會給它的Delegate對象發送一個消息(執行一個方法調用)。
Datasource字面是數據源,一般和Delegate伴生,這時數據源處理的數據就是Delegate中發送委托的類中的數據,并通過Datasource發送給接受委托的類。

Instead of being delegated control of the user interface, a data source is delegated control of data.官網上這句話解釋的比較好,我們可以發現,delegate控制的是UI,是上層的東西;而datasource控制的是數據。他們本質都是回調,只是回調的對象不同。(官網原文:https://developer.apple.com/library/ios/#documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html

Block 和 Delegate 兩種在 iOS 開發中都有廣泛的應用,如果加上 NSNotification 的話就把三大 iOS 回調方式給湊齊了。我這里根據這三者來進行說明。

  • Delegation (代理)是一種很常見的回調方式,和 Singleton (一樣),在大部分編程語言里都有實現方法。Block 是一種蘋果開發的基于 C 的調用方式 ,在 iOS 和 Mac 開發上都有廣泛的應用。

  • Block 從某種程度上來說是一種很新的回調方式,蘋果在2009年將其引入 Mac OS X 10.6,之后在2010年將其引入到 iOS 4.0。值得一提的是,同樣是在 iOS 4.0 ,蘋果引入了 ARC (Automatic Reference Counting),準確的說的話是 ARCLite , iOS 5 才正式加入了 ARC 。

  • NSNotification 這個作為蘋果在 2001 年發布 Mac OS X 的時候就集成在 Foundation 庫里面的類,一直作為一種全局回調通知的功能而存在。有趣的是,按照蘋果官方文檔對于 Delegation 的說明,在 Cocoa Framwroks 中,蘋果的 Delegation 被表述為是一種單觀察者通知的實現。

三種回調方式,能共存于 Objective-C 之中,現在又集體被繼承到了 Swift 里面,說明三者肯定是有不同和各自的優劣之處的。下面我就我個人這些年的 iOS 開發了解,簡單說一下三者的優劣。
Delegation 是一種很清晰回調形式,從 Protocol 的建立,到之后的引用,和對于 delegate 聲明的變量處理,都非常具有條理。建立完 Delegation 之后,其他方法在調用的時候會很清晰整個回調的流程和處理。在處理一些延遲回調或者觸發回掉的時候,聲明調用的類里面的回調方法在編寫時可以按照很獨立的邏輯在制作。在使用 Delegation 的時候,一個回調方法可以對應多個不同的 Delegation ,這不僅簡化了編程過程,也讓能回調處理更加清晰。Delegation 不好的地方在于,在類中調用 delegate 方法的時候,需要對 delegate 本體進行一定的驗證核對,防止出現方法對象為空的情況,再一個就是受制于 performSelector 這個 selector 方法,回掉返回的參數被限制在了 NS 類的范圍內,數量也很有限(當然可以用直接調用方法的形式在繞過,并不推薦;也可以用 Array 套著傳, 不過這樣需要有文檔支持,不然不夠清晰,回調方法也需要獨立的驗證,故也不推薦):

if (self.delegate && [self.delegate respondsToSelector:@selector(ABC:)]){
      [self.delegate performSelector:@selector(ABC:) withObject:@"ABC"];
}

//需要驗證代理是否為空,同時驗證代理方法是否存在
這個問題在 Swfit 中有了很不錯的解決:

delegate?.ABC?(@"ABC")

至于說 Delegation 的另外一個問題,那就是使用了 Delegation 之后,代碼閱讀變成了一件很困難的事情,你需要在不同的 Class 文件中一次次的跳轉來理解整個代碼思路,要知道糟糕的 XCode 還經常會跳錯方法(Command + 點擊跳轉,跳到了同名的其他方法),導致代碼可讀性的下降。要說的話,這一點在 Swift(2.2)版本中有一定的優化,不過 Swift 的 Selector 還在修正中,之后也許會在對于 Delegation 的可讀性上做更多的優化。
最后一個比較核心的問題就在于,在一批變量聲明了代理之后,在代理回掉被執行時,你是不太好知道這個變量究竟是這一批中的哪一個的。這也就是為什么蘋果在制作 delgate 和 datasource 的時候都會在代理方法的返回值中帶上聲明代理類本身的原因。

Block 是一種很好用的回掉方式,其解決了 Delegation 在確認聲明對象上的問題,由于 Block 回調方法一般都跟隨在聲明之后,所以可以很快確認回調來源,通過 __block 聲明的變量也可以很方便的穿越到回調方法中進行使用,代碼可讀性上,一般來說使用 Block 的代碼比使用 Delegation 的代碼可讀性更強(當然你也可以聲明 Block 回調方法對應變量,然后按照和 Delegation 差不多的方法來調用他)。
Block 的缺陷主要就在于使用 ARC 情況下的循環引用。從某種程度上來說, Block 回調的方法內變量實際上是關聯在聲明 Block 的類里面的,但 Block 回調方法本身是在使用 Block 的類中的,同時使用類的 self 本身是可以在 Block 回調方法中被請求的,這里就會出現使用類 A 引用聲明類 B ,B 在關聯的 Block 中又引用了使用類 A ,這樣一個循環之后引用可以無限循環下去,在這種無限循環的引用下, ARC 就無法知道 A 和 B 的確切棄用時間,所以 A 和 B 會在內存中永遠存活下去,直到進程被消滅。所以,在很多時候,我們必須要使用 __weak 來聲明一個 weakself ,來幫助 ARC 判斷內存回收的時間。
這里要指出的是, Block 的回調方法也是可以復用,創建回調方法這一套東西在 Objective-C 中是繼承于 C 樣式的,一般來說,這個東西是沒人用。

void (^simpleBlock)(id responseObject) = ^(id responseObject){
        NSLog(@"This is a block");
    };
//這里創建了 simpleBlock 之后就可以像 Delegation 回調方法那樣在各種回調聲明中使用了

NSNotification 本身是一個非常強大的回調,因為他的回調是全局而且一對多的,廣播的特性使得很多時候使用 NSNotification 能一次解決大面積的問題。同時,由于 NSNotification 是一套很古老的回調機制,在一些時候,當我們需要對 iOS Framework 做一些特殊處理的時候,會用到一些獲取隱藏 NSNotification 的方法,這個在之前權限不完善的時候,企業開發中使用很廣泛。
至于說 NSNotification,就和上面說到的一樣,也主要是在于他的全局廣播屬性和權限機制。在使用 NSNotification 的時候,注冊完回調方法之后,開發者需要自行對注冊進行取消操作。比如說你注冊一個 A 方法到 B 通知,由于某些原因 A 方法所在的類被 ARC 回收掉了,那么在 B 通知被觸發的時候,由于 A 的通知注冊信息還在,就會出現調用了非法的內存地址的情況。我曾經遇到了一次,應該是在調用 Jianting Zhu 還是 Jim Liu 的 WeiboSDK 的時候,記得那一版 SDK 特別奇葩,所有回調都是通過 NSNotification 來進行的,一開始用的時候不太懂,各種注冊回調方法,然后發現 App 跑起來會莫名的閃退,那時候也不太懂,只能硬著頭皮把代碼都改成代理的形式。后來在做鍵盤的時候碰到了類似的報錯,花時間研究了之后才發現是通知回調了已經被回收的類里面的方法,這時候才知道要如果通知沒有注銷會帶來調用上的問題。至于說 NSNotification 的權限問題,對于寫類的人來說是比較頭疼的,很多時候只能靠文檔和調用者的自覺,出于權限考慮,如果不是特別需求全局廣播這個特性,一般不太建議使用 NSNotification。

NSNotification 由于自身的限制,在回調可以傳遞的內容上也存在數量和內容的限制,雖然可以通過 Array 的方法繞過,但這樣在代碼可讀性就會有折扣,對于文檔也需要有要求,回調方法中還需要驗證。

那么,在何種情況下使用上面三者呢?對于初級的開發人員來說,關鍵在于使用習慣。如果你從其他語言轉到 Objective-C 或者 Swift ,相信 Delegation 肯定讓你覺得更加親切,那么在初級階段請使用好這個語法糖,多用,多去理解;如果你用著 AFNetworking 看著其他老前輩的說法用 Block 覺得效率很高很開心,那就開心的用,直到你被循環引用煩到了為止(笑);如果你用 NSNotification ……你還是別用了。然后,在你代碼寫多了之后,你可以開始嘗試接觸其他回調方式,去感受這些回調方式的不同。

對于中級的開發人員,關鍵在于對于回調流程的理解。你要知道你的回調是一個什么性質的回調,如果這個回調是一個不定期觸發,或者會多次觸發的,那么 Delegation 應該更適合;如果這個回調是一個一次性的,并且和調用方法是單線性關系的,那么 Block 應該更適合;如果這個回調是廣播性質的,需要很多個不同的類都接收到,那么 NSNotification 更適合。

在不同的執行線(不是線程),不同的執行次數、執行數量上的區別,是鑒別使用哪一種回調的最好判斷方法。

對于 Block 來說,他的執行線應該是和調用方法、回調方法連續在一起的;對于 Delegation 和 NSNotification 來說,他的執行線可以是連續的,也可以是調用方法和回調方法之間有很長的間隔,或者說回調方法在執行線上會多次出現。

對于高級的開發人員……你們要是不懂上面那些就不要做高級開發了,你們應該去研究線程、 GCD 、 NSOperation 這些玩意。

<a id="4">4. 屬性的實質是什么?包括哪幾個部分?</a>

屬性是一個類中用來描述對象的抽象概念。
屬性包括的部分有setter和getter方法

<a id="5">5. 屬性默認的關鍵字都有哪些?@dynamic關鍵字和@synthesize關鍵字是用來做什么的?</a>

atomic: 
nonatomic:
@synthesize
@dynamic
getter=getterName
setter=setterName
readwrite
readonly
assign
retain
copy

atomic:
原子操作(原子性是指事務的一個完整操作,操作成功就提交,反之就回滾. 原子操作就是指具有原子性的操作)在objective-c 屬性設置里面 默認的就是atomic ,意思就是 setter /getter函數是一個原子操作,如果多線程同時調用setter時,不會出現某一個線程執行完setter所有語句之前,另一個線程就開始執行setter,相當于 函數頭尾加了鎖 . 這樣的話 并發訪問性能會比較低 .

nonatomic:
非原子操作 一般不需要多線程支持的時候就用它,這樣在 并發訪問的時候效率會比較高 . 在objective-c里面通常對象類型都應該聲明為非原子性的. iOS中程序啟動的時候系統只會自動生成一個單一的主線程.程序在執行的時候一般情況下是在同一個線程里面對一個屬性進行操作. 如果在程序中 我們確定某一個屬性會在多線程中被使用,并且需要做數據同步,就必須設置成原子性的,但也可以設置成非原子性的,然后自己在程序中用加鎖之類的來做數據同步.

在頭文件中聲明屬性的時候使用atomic 和 nonatomic等價于在頭文件里面添加2個函數一個是用于設置這個屬性的,一個是用于讀取這個屬性,例如:- (nsstring *)name; - (void)setName:(NSString *)str;
atomic / nonatomic 需要和@synthesize/@dynamic配和使用才有意義.

@synthesize
如果沒有實現setter和getter方法,編譯器將會自動在生產setter和getter方法。

@dynamic
表示變量對應的屬性訪問器方法 , 是動態實 現的 , 你需要在 NSObject 中繼承而來的 +(BOOL) resolveInstanceMethod:(SEL) sel 方法中指定 動態實現的方法或者函數。
屬性修飾其他關鍵字:

getter=getterName
指定 get 方法,并需要實現這個方法 。必須返回與聲明類型相同的變量,沒有參數

setter=setterName
指定 set 方法,并需要實現這個方法 。帶一個與聲明類型相同的參數,沒有返回值(返回空值)
當聲明為 readonly 的時候,不能指定 set 方法

readwrite
如果沒有聲明成 readonly ,那就 默認是 readwrite 。可以用來賦值,也可以被賦值

readonly
不可以被賦值

assign
所有屬性都 默認 assign ,通常用于標量(簡單變量 int , float , CGRect 等)
一種典型情況是用在對對象沒有所有權的時候,通常是 delegate ,避免造成死循環(如果用 retain 的話會死循環)

retain
屬性必須是 objc 對象,擁有對象所有權,必須在 dealloc 中 release 一次。

copy
屬性必須是 objc 對象,擁有對象所有權,必須在 dealloc 中 release 一次。且屬性必須實現 NSCopying 協議
一般常用于 NSString 類型

<a id="6">6.NSString為什么要用copy關鍵字,如果用strong會有什么問題?</a>

因為用copy,以免因可變字符串的修改導致的一些非預期問題。用strong也不會出現問題, 不管是strong還是copy屬性的對象,都是指向源對象,copy操作只是做了次淺拷貝。
這里先感謝南峰子并引用了他的文章

我們在聲明一個NSString屬性時,對于其內存相關特性,通常有兩種選擇(基于ARC環境):strong與copy。那這兩者有什么區別呢?什么時候該用strong,什么時候該用copy呢?讓我們先來看個例子。

示例

我們定義一個類,并為其聲明兩個字符串屬性,如下所示:

@interface TestStringClass ()
@property (nonatomic, strong) NSString *strongString;
@property (nonatomic, copy) NSString *copyedString;
@end

上面的代碼聲明了兩個字符串屬性,其中一個內存特性是strong,一個是copy。下面我們來看看它們的區別。
首先,我們用一個不可變字符串來為這兩個屬性賦值,

- (void)test {
NSString *string = [NSString stringWithFormat:@"abc"];
    
self.strongString = string;  
self.copyedString = string;

NSLog(@"origin string: %p, %p", string, &string);
NSLog(@"strong string: %p, %p", _strongString, &_strongString);    
NSLog(@"copy string: %p, %p", _copyedString, &_copyedString);
}

其輸出結果是:

origin string: 0x7fe441592e20, 0x7fff57519a48

strong string: 0x7fe441592e20, 0x7fe44159e1f8

copy string: 0x7fe441592e20, 0x7fe44159e200

我們要以看到,這種情況下,不管是strong還是copy屬性的對象,其指向的地址都是同一個,即為string指向的地址。如果我們換作MRC環境,打印string的引用計數的話,會看到其引用計數值是3,即strong操作和copy操作都使原字符串對象的引用計數值加了1。

接下來,我們把string由不可變改為可變對象,看看會是什么結果。即將下面這一句

NSString *string = [NSString stringWithFormat:@"abc"];

改成:

NSMutableString *string = [NSMutableString stringWithFormat:@"abc"];

其輸出結果是:

origin string: 0x7ff5f2e33c90, 0x7fff59937a48

strong string: 0x7ff5f2e33c90, 0x7ff5f2e2aec8

copy string: 0x7ff5f2e2aee0, 0x7ff5f2e2aed0

可以發現,此時copy屬性字符串已不再指向string字符串對象,而是深拷貝了string字符串,并讓_copyedString對象指向這個字符串。在MRC環境下,打印兩者的引用計數,可以看到string對象的引用計數是2,而_copyedString對象的引用計數是1。

此時,我們如果去修改string字符串的話,可以看到:因為_strongString與string是指向同一對象,所以_strongString的值也會跟隨著改變(需要注意的是,此時_strongString的類型實際上是NSMutableString,而不是NSString);而_copyedString是指向另一個對象的,所以并不會改變。

結論

由于NSMutableString是NSString的子類,所以一個NSString指針可以指向NSMutableString對象,讓我們的strongString指針指向一個可變字符串是OK的。

而上面的例子可以看出,當源字符串是NSString時,由于字符串是不可變的,所以,不管是strong還是copy屬性的對象,都是指向源對象,copy操作只是做了次淺拷貝。

當源字符串是NSMutableString時,strong屬性只是增加了源字符串的引用計數,而copy屬性則是對源字符串做了次深拷貝,產生一個新的對象,且copy屬性對象指向這個新的對象。另外需要注意的是,這個copy屬性對象的類型始終是NSString,而不是NSMutableString,因此其是不可變的。

這里還有一個性能問題,即在源字符串是NSMutableString,strong是單純的增加對象的引用計數,而copy操作是執行了一次深拷貝,所以性能上會有所差異。而如果源字符串是NSString時,則沒有這個問題。

所以,在聲明NSString屬性時,到底是選擇strong還是copy,可以根據實際情況來定。不過,一般我們將對象聲明為NSString時,都不希望它改變,所以大多數情況下,我們建議用copy,以免因可變字符串的修改導致的一些非預期問題。

關于字符串的內存管理,還有些有意思的東西,可以參考NSString特性分析學習

<a id="7">7. 如何令自己所寫的對象具有拷貝功能?</a>

對象具有拷貝功能,則需實現 NSCopying 協議。如果自定義的對象分為可變版本與不可變版本,那么就要同時實現 NSCopying與 NSMutableCopying協議。

需聲明該類遵從 NSCopying 協議
實現 NSCopying 協議。該協議只有一個方法

- (id)copyWithZone:(NSZone *)zone;

注意:一提到讓自己的類用 copy 修飾符,我們總是想覆寫copy方法,其實真正需要實現的卻是 “copyWithZone” 方法。

至于如何重寫帶 copy 關鍵字的 setter這個問題,如果拋開本例來回答的話,如下:

- (void)setName:(NSString *)name { 
   //[_name release];
    _name = [name copy];
}

<a id="8">8.可變集合類 和 不可變集合類的 copy 和 mutablecopy有什么區別?如果是集合是內容復制的話,集合里面的元素也是內容復制么?</a>

  • 對于系統的非容器類對象,對一不可變對象復制,copy是指針復制(淺拷貝)和mutableCopy就是對象復制(深拷貝)。如果是對可變對象復制,都是深拷貝,但是copy返回的對象是不可變的。

<a id="9">9. 為什么IBOutlet修飾的UIView也適用weak關鍵字?</a>

IBOutlet的屬性一般可以設為weak是因為它已經被view引用了,除非view被釋放,否則IBOutlet的屬性也不會被釋放,另外IBOutlet屬性的生命周期和view應該是一致的,所以IBOutlet屬性一般設為weak。

可參考如下:
From a practical perspective, in iOS and OS X outlets should be defined as declared properties. Outlets should generally be weak, except for those from File’s Owner to top-level objects in a nib file (or, in iOS, a storyboard scene) which should be strong. Outlets that you create will therefore typically be weak by default, because:Outlets that you create to, for example, subviews of a view controller’s view or a window controller’s window, are arbitrary references between objects that do not imply ownership.The strong outlets are frequently specified by framework classes (for example, UIViewController’s view outlet, or NSWindowController’s window outlet).

簡單的說,如果IBOutlet對象是nib/sb scene的擁有者(File’s owner)所持有的對象,那么很顯然擁有者必須“擁有”對象的指針,因此屬性應設置為strong。而其他的IBOutlet對象的屬性需要設置為weak,因為擁有者并不需要“擁有”他們的指針。舉例來說,UIViewController的view屬性是strong,因為controller要直接擁有view。而添加到view上的subviews,作為IBOutlet只需要設置為weak就可以了,因為他們不是controller直接擁有的。直接擁有subviews的是controller的view,ARC會幫助管理內存。

緊接著,文檔里又提到:
Outlets should be changed to strong when the outlet should be considered to own the referenced object:
As indicated previously, this is often the case with File’s Owner—top level objects in a nib file are frequently considered to be owned by the File’s Owner.
You may in some situations need an object from a nib file to exist outside of its original container. For example, you might have an outlet for a view that can be temporarily removed from its initial view hierarchy and must therefore be maintained independently.

第一種情形前面已經解釋過了,對于第二種,通俗點講,就是controller需要直接控制某一個subview并且將subview添加到其他的view tree上去。單純從ARC的角度思考,用weak也是很顯然的:因為subview添加到view上時,view會“擁有”subview。當然,給IBOutlet屬性設置為strong也沒有錯,“糾結誰對誰錯“的問題可能需要上升到模式或者編碼習慣的問題,已經超出本文的范圍。

<a id="10">10. nonatomic和atomic的區別?atomic是絕對的線程安全么?為什么?如果不是,那應該如何實現?</a>

  • atomic:默認是有該屬性的,這個屬性是為了保證程序在多線程情況下,編譯器會自動生成一些互斥加鎖代碼,避免該變量的讀寫不同步問題。
    nonatomic:如果該對象無需考慮多線程的情況,請加入這個屬性,這樣會讓編譯器少生成一些互斥加鎖代碼,可以提高效率。
  • atomic的意思就是setter/getter這個函數,是一個原語操作。如果有多個線程同時調用setter的話,不會出現某一個線程執行完setter全部語句之前,另一個線程開始執行setter情況,相當于函數頭尾加了鎖一樣,可以保證數據的完整性。nonatomic不保證setter/getter的原語行,所以你可能會取到不完整的東西。因此,在多線程的環境下原子操作是非常必要的,否則有可能會引起錯誤的結果。
  • 比如setter函數里面改變兩個成員變量,如果你用nonatomic的話,getter可能會取到只更改了其中一個變量時候的狀態,這樣取到的東西會有問題,就是不完整的。當然如果不需要多線程支持的話,用nonatomic就夠了,因為不涉及到線程鎖的操作,所以它執行率相對快些

一般iOS程序中,所有屬性都聲明為nonatomic。這樣做的原因是:
在iOS中使用同步鎖的開銷比較大, 這會帶來性能問題。一般情況下并不要求屬性必須是“原子的”,因為這并不能保證“線程安全”(thread safety),若要實現“線程安全”的操作,還需采用更為深層的鎖定機制才醒。

例如:一個線程在連續多次讀取某個屬性值的過程中有別的線程在同時改寫該值,那么即便將屬性聲明為atomic,也還是會讀取到不同的屬性值。

因此,iOS程序一般都會使用nonatomic屬性。但是在Mac OS X程序時, 使用atomic屬性通常都不會有性能瓶頸

atomic一定是線程安全的么,回答是NO

nonatomic的內存管理語義是非原子性的,非原子性的操作本來就是線程不安全,而atomic的操作是原子性的,但并不意味著他就是線程安全的,它會增加正確的幾率,能夠更好的避免線程錯誤,但仍舊是不安全的。

當使用atomic時,雖然對屬性的讀和寫是原子性的,但是仍然可能出現線程錯誤:當線程A進行寫操作,這時其他線程的讀或者寫操作會因為等該操作而等待。當A線程的寫操作結束后,B線程進行寫操作,所有這些不同線程上的操作都將依次順序執行——也就是說,如果一個線程正在執行 getter/setter,其他線程就得等待。如果有線程C在A線程讀操作之前release了該屬性,那么還會導致程序崩潰。所以僅僅使用atomic并不會使得線程安全,我們還要為線程添加lock來確保線程的安全。

更準確的說應該是讀寫安全,但并不是線程安全的,因為別的線程還能進行讀寫之外的其他操作。線程安全需要開發者自己來保證。

其實無論是否是原子性的只是針對于getter和setter而言,比如用atomic去操作一個NSMutableArray ,如果一個線程循環讀數據,一個線程循環寫數據,肯定會產生內存問題,這個就跟getter和setter就木有關系了。

<a id="11">11. UICollectionView自定義layout如何實現?</a>

UICollectionViewLayout是一個應該子類化的抽象基類,用來生成collection view的布局信息。 布局對象決定cell,追加視圖(supplementary views), 和裝飾視圖(decoration views) 在collection view內部的位置,并在 collection view 獲取的時候提供這些布局信息。collection view將把這些布局信息應用到相應的視圖上以便在屏幕上進行展示。
如果想使用UICollectionViewLayout,你必須把它子類化。在子類化UICollectionViewLayout之前,應該先了解UICollectionViewFlowLayout,看看它是否已經能夠滿足你的布局需求。(注:UICollectionViewFlowLayout是官方提供的子類化的UICollectionViewLayout,能滿足絕大部分需求)

詳細內容請看這篇文章 ->創建自定義UICollectionView layout
和這篇文章 ->iOS: 玩轉UICollectionViewLayout

<a id="12">12. 用StoryBoard開發界面有什么弊端?如何避免?</a>

優點:
1: storyboard是蘋果在iOS5之后提供一種全新制作UI方式,他提供了非常強大界面可視化,可以快速進行拖拉界面,完成自己APP.
2:可以非常清晰看出每個控制器(View Controller)中界面邏輯關系,結構非常一目了
3: 開發速度快

缺點
1: 在開發過程中,只要點擊一下storyboard,不做任何修改,SVN工具就要提醒重新提交,非常蛋疼。
2: 用過storyboard人都知道,storyboard復用性很差。
3: 在團隊開發者中,極易造成沖突。

在相對界面簡單 沒有多次修改的需求上建議用storyboard 或者xib編寫 這樣可以提升整體項目的速度 而對于復雜頁面就用純代碼編寫 ,可控性強 易維護。

<a id="13">13. 進程和線程的區別?同步異步的區別?并行和并發的區別?</a>

  • 進程和線程的區別
    (1)一個線程只能屬于一個進程,而一個進程可以有多個線程,但至少有一個線程。線程是操作系統可識別的最小執行和調度單位。
    (2)資源分配給進程,同一進程的所有線程共享該進程的所有資源。 同一進程中的多個線程共享代碼段(代碼和常量),數據段(全局變量和靜態變量),擴展段(堆存儲)。但是每個線程擁有自己的棧段,棧段又叫運行時段,用來存放所有局部變量和臨時變量。
    (3)處理機分給線程,即真正在處理機上運行的是線程。
    (4)線程在執行過程中,需要協作同步。不同進程的線程間要利用消息通信的辦法實現同步。
    進程和線程并不是一一對應的,一個程序執行在不同的數據集上就成為不同的進程,可以用進程控制塊來唯一地標識每個進程。而這一點正是程序無法做到的,由于程序沒有和數據產生直接的聯系,即使是執行不同的數據的程序,他們的指令的集合依然是一樣的,所以無法唯一地標識出這些運行于不同數據集上的程序。一般來說,一個進程肯定有一個與之對應的程序,而且只有一個。而一個程序有可能沒有與之對應的進程(因為它沒有執行),也有可能有多個進程與之對應(運行在幾個不同的數據集上)。

  • 同步異步的區別
    線程同步是在多線程下才會產生的,多個線程同時訪問同一塊資源,為了安全高效的訪問,就要解決同步訪問帶來的一系列問題。
    打個比方,兩個人從一個籃子里拿蘋果,如果一個拿完之后再讓另一個拿,那就不會出現問題,可是效率也不高。(totalTime = timeOfA + timeOfB)
    如果讓他們同時拿,你拿一個我拿一個,這樣效率就高了,可是會出現問題,兩個人都搶到一個蘋果那該歸誰呢,這時可以有幾個方法,A拿蘋果時B先等著,等A拿到了B在拿。或者A只能拿A這邊的,B只能拿B這邊的。這樣就能解決問題了。
    在iOS上最主要的方法就是在訪問期間加鎖,等訪問完畢在解鎖。
    你拿一個蘋果,然后我拿一個蘋果, 這叫同步執行你和我同時去拿蘋果,這叫兩個線程異步執行,這里為了實現異步,使用了多線程的手段,你和我各屬于一個線程然后就會出現問題了,如果你和我伸手拿了同一個蘋果怎么辦? 這個時候就需要你和我進行線程同步,比如:誰先看到籃子,就對籃子加鎖,讓另一個人等著,拿完之后解鎖

  • 并行和并發的區別
    并發行和并行性的區別可以用饅頭做比喻。前者相當于一個人同時吃三個饅頭和三個人同時吃一個饅頭。
    并發性(Concurrence):指兩個或兩個以上的事件或活動在同一時間間隔內發生。并發的實質是一個物理CPU(也可以多個物理CPU) 在若干道程序之間多路復用,并發性是
    對有限物理資源強制行使多用戶共享以提高效率。
    并行性(parallelism)指兩個或兩個以上事件或活動在同一時刻發生。在多道程序環境下,并行性使多個程序同一時刻可在不同CPU上同時執行。
    區別:一個處理器同時處理多個任務和多個處理器或者是多核的處理器同時處理多個不同的任務。
    前者是邏輯上的同時發生(simultaneous),而后者是物理上的同時發生。
    兩者的聯系:
    并行的事件或活動一定是并發的,但反之并發的事件或活動未必是并行的。并行性是并發性的特例,而并發性是并行性的擴展。

<a id="14">14. 線程間通信?</a>

  • 線程間通信的體現
1 .一個線程傳遞數據給另一個線程
2 .在一個線程中執行完特定任務后,轉到另一個線程繼續執行任務
  • 線程間通信常用的方法
 `NSThread`可以先將自己的當前線程對象注冊到某個全局的對象中去,這樣相互之間就可以獲取對方的線程對象,然后就可以使用下面的方法進行線程間的通信了,由于主線程比較特殊,所以框架直接提供了在主線程執行的方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);

<a id="15">15. GCD的一些常用的函數?(group,barrier,信號量,線程同步)</a>

  1. The main queue(主線程串行隊列): 與主線程功能相同,提交至Main queue的任務會在主線程中執行,
  • Main queue 可以通過dispatch_get_main_queue()來獲取。
  1. Global queue(全局并發隊列): 全局并發隊列由整個進程共享,有高、中(默認)、低、后臺四個優先級別。
  • Global queue 可以通過調用dispatch_get_global_queue函數來獲取(可以設置優先級)
  1. Custom queue (自定義隊列): 可以為串行,也可以為并發。
  • Custom queue 可以通過dispatch_queue_create()來獲取;
  1. Group queue (隊列組):將多線程進行分組,最大的好處是可獲知所有線程的完成情況。
  • Group queue 可以通過調用dispatch_group_create()來獲取,通過dispatch_group_notify,可以直接監聽組里所有線程完成情況。

<a id="16">16. 如何使用隊列來避免資源搶奪?</a>

<a id="17">17. 數據持久化的幾個方案(fmdb用沒用過)</a>

<a id="18">18. 說一下AppDelegate的幾個方法?從后臺到前臺調用了哪些方法?第一次啟動調用了哪些方法?從前臺到后臺調用了哪些方法?</a>

  1. – (void)applicationDidFinishLaunching:(UIApplication *)application;
    此方法基本已經棄用,改用第2個方法代替。

  2. – (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions NS_AVAILABLE_IOS(3_0);
    當應用程序啟動時(不包括已在后臺的情況下轉到前臺),調用此回調。launchOptions是啟動參數,假如用戶通過點擊push通知啟動的應用,這個參數里會存儲一些push通知的信息。

  3. – (void)applicationDidBecomeActive:(UIApplication *)application;
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    當應用程序全新啟動,或者在后臺轉到前臺,完全激活時,都會調用這個方法。如果應用程序是以前運行在后臺,這時可以選擇刷新用戶界面。

  4. – (void)applicationWillResignActive:(UIApplication *)application;
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
    當應用從活動狀態主動到非活動狀態的應用程序時會調用這個方法。這可導致產生某些類型的臨時中斷(如傳入電話呼叫或SMS消息)。或者當用戶退出應用程 序,它開始過渡到的背景狀態。使用此方法可以暫停正在進行的任務,禁用定時器,降低OpenGL ES的幀速率。游戲應該使用這種方法來暫停游戲。
    調用時機可能有以下幾種:鎖屏,按HOME鍵,下接狀態欄,雙擊HOME鍵彈出低欄,等情況。

  5. – (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url;
    // Will be deprecated at some point, please replace with application:openURL:sourceApplication:annotation:
    這個方法已不再支持,可能會在以后某個版本中去掉。建議用下面第6個方法代替

  6. – (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation NS_AVAILABLE_IOS(4_2);
    // no equiv. notification. return NO if the application can’t open for some reason
    當用戶通過其它應用啟動本應用時,會回調這個方法,url參數是其它應用調用openURL:方法時傳過來的。

  7. – (void)applicationDidReceiveMemoryWarning:(UIApplication *)application;
    // try to clean up as much memory as possible. next step is to terminate app
    當應用可用內存不足時,會調用此方法,在這個方法中,應該盡量去清理可能釋放的內存。如果實在不行,可能會被強行退出應用。

  8. – (void)applicationWillTerminate:(UIApplication *)application;
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    當應用退出,并且進程即將結束時會調到這個方法,一般很少主動調到,更多是內存不足時是被迫調到的,我們應該在這個方法里做一些數據存儲操作。

  9. // one of these will be called after calling -registerForRemoteNotifications

  • (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken NS_AVAILABLE_IOS(3_0);
  • (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error NS_AVAILABLE_IOS(3_0);
    當客戶端注冊遠程通知時,會回調上面兩個方法。
    如果成功,則回調第一個,客戶端把deviceToken取出來發給服務端,push消息的時候要用。
    如果失敗了,則回調第二個,可以從error參數中看一下失敗原因。
    注:注冊遠程通知使用如下方法:
UIRemoteNotificationType t = UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeAlert|UIRemoteNotificationTypeSound;
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:t];
  1. – (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo NS_AVAILABLE_IOS(3_0);
    當應用在前臺運行中,收到遠程通知時,會回調這個方法。
    當應用在后臺狀態時,點擊push消息啟動應用,也會回調這個方法。

  2. – (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification NS_AVAILABLE_IOS(4_0);
    當應用收到本地通知時會調這個方法,同上面一個方法類似。
    如果在前臺運行狀態直接調用,如果在后臺狀態,點擊通知啟動時,也會回調這個方法
    本地通知可見另一篇文章:http://bluevt.org/?p=70

  3. – (void)applicationDidEnterBackground:(UIApplication *)application NS_AVAILABLE_IOS(4_0);
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    當用戶從臺前狀態轉入后臺時,調用此方法。使用此方法來釋放資源共享,保存用戶數據,無效計時器,并儲存足夠的應用程序狀態信息的情況下被終止后,將應用 程序恢復到目前的狀態。如果您的應用程序支持后臺運行,這種方法被調用,否則調用applicationWillTerminate:用戶退出。

  4. – (void)applicationWillEnterForeground:(UIApplication *)application NS_AVAILABLE_IOS(4_0);
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
    當應用在后臺狀態,將要進行動前臺運行狀態時,會調用此方法。
    如果應用不在后臺狀態,而是直接啟動,則不會回調此方法。

<a id="19">19. NSCache優于NSDictionary的幾點?</a>

NSCache 是一個容器類,類似于NSDIctionary,通過key-value 形式存儲和查詢值,用于臨時存儲對象。
注意一點它和NSDictionary區別就是,NSCache 中的key不必實現copy,NSDictionary中的key必須實現copy
NSCache中存儲的對象也不必實現NSCoding協議,因為畢竟是臨時存儲,類似于內存緩存,程序退出后就被釋放了。

NSCache勝過NSDictionary之處在于,當系統資源將要耗盡時,它可以自動刪減緩存。如果采用普通的字典,那么就要自己編寫掛鉤,在系統發出“低內存”通知時手工刪減緩存。

NSCache并不會“拷貝”鍵,而是會“保留”它。此行為用NSDictionary也可以實現,然而需要編寫相當復雜的代碼。NSCache對象不拷貝鍵的原因在于:很多時候,鍵都是不支持拷貝操作的對象來充當的。因此,NSCache不會自動拷貝鍵,所以說,在鍵不支持拷貝操作的情況下,該類用起來比字典更方便。另外,NSCache是線程安全的,而NSDictionary則絕對不具備此優勢。

<a id="20">20. 知不知道Designated Initializer?使用它的時候有什么需要注意的問題?</a>
Designated Initializer(指定初始化器)

編寫類的初始化函數時,需要注意以下五點:

  1. 確保類的Designated Initializer中,調用了父類的Designated Initializer函數。
  2. 父類的Designated Initializer函數的返回值保存在變量self中。
  3. 第2步結束后,如果self的值為nil,不可繼續初始化操作。
  4. 確保子類覆寫(override)了父類的Designated Initializer函數。
  5. 確保類里的每個非Designated Initializer的初始化函數都會調用Designated Initializer。

如第4點所要求,子類繼承父類時,須知道父類的Designated Initializer函數。
下面列出常被繼承的Cocoa類的Designated Initializer函數。

Class Designated Initializer
NSObject -init
NSView -initWithFrame:
NSCell -initImageCell: & -initTextCell:
NSControl -initWithFrame:
NSDocument -init
NSWindowController -initWithWindow:

<a id="21">21. 實現description方法能取到什么效果?</a>

因為NSObject并不是唯一的“根類”,所以許多方法都要定義再NSObject協議里。比方說NSProxy也是一個遵從了NSObject協議的“根類”。由于description等方法定義在NSObject協議里,因此像NSProxy這種“根類”及其子類也必須實現他們。如前所見,這些實現好的方法并沒有打印出較為有用的內容,只不過是輸出了類名和對像的內存地址。只有在你想判斷兩指針是否真的指向同一對象時,這種信息才有用處。除此之外,再也看不出其他有用的內容了。我們想打印出來的對象信息應該比這更多才對。

要向輸出更為有用的信息也很簡單,只需覆寫description方法并將描述此對象的字符串返回即可

<a id="22">22. objc使用什么機制管理對象內存?</a>
1).MRC(manual retain-release)手動內存管理
2).ARC(automatic reference counting)自動引用計數
詳細內容查看此篇文章->iOS內存管理:基本概念與原理

中級

Block
<a id="23">1. block的實質是什么?一共有幾種block?都是什么情況下生成的?</a>

Block實質就是Object-C對象
也可以理解為是一個函數指針加上一個對應捕獲上下文變量的內存塊(結構體或者類)

//

根據isa指針,block一共有3種類型的block
_NSConcreteGlobalBlock 全局靜態
_NSConcreteStackBlock 保存在棧中,出函數作用域就銷毀
_NSConcreteMallocBlock 保存在堆中,retainCount == 0銷毀

要點一:當block在函數內部,且定義的時候就使用了函數內部的變量,那么這個 block是存儲在棧上的。
要點二:當block定義在函數體外面,或者定義在函數體內部且當時函數執行的時候,block體中并沒有需要使用函數內部的局部變量時,也就是block在函數執行的時候只是靜靜地待在一邊定義了一下而不使用函數體的內容,那么block將會被編譯器存儲為全局block。
要點三:全局block存儲在堆中,對全局block使用copy操作會返回原函數指針;而對棧中的block使用copy操作,會產生兩個不同的block地址,也就是兩個匿名函數的入口地址。
要點四:ARC機制優化會將stack的block,轉為heap的block進行調用。

<a id="24">2. 為什么在默認情況下無法修改被block捕獲的變量? __block都做了什么?</a>

默認情況下無法修改被block捕獲的變量,也就是說并不會在構造函數里面傳入它們的值。Block捕獲外部變量僅僅只捕獲Block閉包里面會用到的值,其他用不到的值,它并不會去捕獲。

__block 改變存儲方式。
_block修飾自動變量后,_block的變量也被轉化成了一個結構體
詳情可參考這篇文章iOS block 捕獲外部變量以及注意點

<a id="25">3. 模擬一下循環引用的一個情況?block實現界面反向傳值如何實現?</a>

- (id)init
{
    if (self = [super init]) {
         self.arr = @[@111, @222, @333];
        self.block = ^(NSString *name){
            NSLog(@"arr:%@", self.arr);
        };
    }
    return  self;
}

block實現界面反向傳值如何實現

//A頁面跳轉代碼
BViewController *b = [[BViewController alloc]init];
    B.saveBlock = ^(Person *person){
//接收到B頁面傳過來的數據 進行操作
        [contactList addObject:person];
        [tableView reloadData];
    };
[self.navigationController pushViewController:two animated:YES];
//在B頁面的.h文件里聲明塊屬性
@property(nonatomic,strong)void(^saveBlock)(Person *);
//在B頁面的.m文件里返回A頁面的跳轉方法里
Person *person = [[Person alloc]init];
person.name = nameText.text;
person.psw = pswText.text;
//這里用block傳值
!self.saveBlock?:self.saveBlock(person);
[self.navigationController popViewControllerAnimated:YES];

剩下的來這里->iOS面試題 - 總會有你需要的(二)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,210評論 30 471
  • 把網上的一些結合自己面試時遇到的面試題總結了一下,以后有新的還會再加進來。 1. OC 的理解與特性 OC 作為一...
    AlaricMurray閱讀 2,610評論 0 20
  • 1.屬性readwrite,readonly,assign,retain,copy,nonatomic 各是什么作...
    曾令偉閱讀 1,080評論 0 10
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,765評論 18 399
  • 題目:實現pow(base, n) 解法:注意分解 a^n = (a(n/2))*(a(n/2))
    qmss閱讀 327評論 0 0