iOS常見基礎面試題(附參考答案)

Toga21發表於2018-03-01

趁著開年的空閒時間,找了一些面試題寫寫, 水平渣,僅作參考!,歡迎指導討論,寫的不對的地方望及時提出! 原題在這裡

中級篇請看這裡: iOS常見面試題(block,runtime,runloop,類結構)附參考答案

基礎部分

1.為什麼說OC是一門動態的語言?

  • 動態和靜態是相對的,OC通過runtime執行時機制可以做到純靜態語言做不到的事情:例如動態地增加、刪除、替換ivar或者方法等
  • Objective-C 使用的是“訊息結構”並非“函式呼叫”:使用訊息結構的的語言,其執行時所應執行的程式碼由執行期決定;而使用函式呼叫的語言,則由編譯器決定

2.講一下MVC和MVVM,MVP?

  • MVC
    • M:業務資料, V:檢視,負責展示 C:控制器,負責協調M、V
    • C作為M和V之間的連線, 負責響應檢視事件,介面的跳轉,view的宣告週期,獲取業務資料, 然後將處理後的資料輸出到介面上做相應展示, 在資料有更新時, C需要及時提交相應更新到介面展示。View和Model之間沒有直接的聯絡,AppleMVC規範,理想的模型圖如下:
      iOS常見基礎面試題(附參考答案)
    • 在實際開發中,Model往往只有輕量級的資料,甚至.m中沒有任何實現,View和Controller成對出現,導致Controller中,越來越多的屬性、協議、網路資料處理等,View和Controller緊緊耦合在一起:
      iOS常見基礎面試題(附參考答案)
  • MVP
    • M:業務資料, V:檢視, P:協調器,業務處理層
    • P:業務邏輯的處理者,作為M、V的橋樑。當獲取到資料後進行相應處理, 處理完成後會通知繫結的View資料有更新,View收到更新通知後從P獲取格式化好的資料進行頁面渲染。相比較MVC,MVP把業務邏輯和View的展示分離開:
      iOS常見基礎面試題(附參考答案)
  • MVVM

3.為什麼代理要用weak?代理的delegate和dataSource有什麼區別?block和代理的區別?

  • 避免迴圈引用,weak表示該物件並不持有該delegate物件,delegate物件的銷燬由外部控制;如果用strong則該物件強引用delegate,外界不能銷燬delegate物件,會導致迴圈引用(Retain Cycles)
  • 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控制的是資料。他們本質都是回撥,只是回撥的物件不同。官網原文
  • delegateblock都可以實現回撥傳值。block寫法簡練,可以直接訪問上下文,程式碼閱讀性好,適合與狀態無關的操作,更加面向結果,使用過程中需要注意避免造成迴圈引用。delegate更像一個生產流水線,每個回撥方法是生產線上的一個處理步驟,一個回撥的變動可能會引起另一個回撥的變動,其更加程式導向,當有多個相關方法時建議使用delegate

4.屬性的實質是什麼?包括哪幾個部分?屬性預設的關鍵字都有哪些?@dynamic關鍵字和@synthesize關鍵字是用來做什麼的?

  • 屬性@"property" = ivar + getter + setter
  • 原子性: nonatomicatomic
  • 讀寫特性: readwritereadonly
  • 記憶體管理特性: assign:修飾簡單資料型別。 weak:弱引用,多數用來修飾delegateoutletcopy:拷貝,多用於修飾NSStringblock等,其作為屬性修飾符時是將_propertyrelease (_property release),然後拷貝引數內容(_property copy),建立一塊新的記憶體地址,最後_property = property。strong:強引用,其作為屬性修飾符時是將_propertyrelease (_property release),然後將引數retain(_property retain),最後_property = propertyretain:MRC下特有,等同於strongunsafe_unretained:和weak 一樣,唯一的區別便是,物件即使被銷燬,指標也不會自動置空, 此時指標指向的是一個無用的野地址。如果使用此指標,程式會丟擲 BAD_ACCESS 的異常。
  • @synthesize 讓編譯器自動生成getter/setter方法。當有自定義的存取方法時,會覆蓋該方法
  • @dynamic告訴編譯器,不自動生成getter/setter方法。由自己實現存取方法或存取方法在執行時動態建立繫結

5.屬性的預設關鍵字是什麼?

ARC下:

  • 基本資料型別預設關鍵字是 atomic,readwrite,assign
  • 其他型別預設關鍵字是 atomic,readwrite,strong

MRC下:

  • 基本資料型別預設關鍵字是 atomic,readwrite,assign
  • 其他型別預設關鍵字是 atomic,readwrite,retain

6.NSString為什麼要用copy關鍵字,如果用strong會有什麼問題?(注意:這裡沒有說用strong就一定不行。使用copy和strong是看情況而定的)

@interface Person : NSObject

@property(nonatomic ,copy)NSString *cpName;
@property(nonatomic ,strong)NSString *stName;

@end

@implementation Person
@end

main(){
    NSMutableString *string = [NSMutableString stringWithFormat:@"name"];
    
    Person *person = [[Person alloc]init];
    person.cpName = string;
    person.stName = string;
    
    [string appendString:@"name"];
    NSLog(@"cpName:%@ stName:%@",person.cpName,person.stName);
}
複製程式碼
列印結果如下:
cpName:name stName:namename複製程式碼

至於為什麼要用copy, 大概是為了防止mutableString被無意中修改。

7.如何令自己所寫的物件具有拷貝功能?

  • 若想讓自己所寫的物件具有拷貝功能,則需實現 NSCopying 協議。如果自定義的物件分為可變版本與不可變版本,那麼就要同時實現 NSCopyingNSMutableCopying 協議。
  • 要注意淺拷貝/深拷貝的不同。

8.可變集合類 和 不可變集合類的 copy 和 mutablecopy有什麼區別?如果是集合是內容複製的話,集合裡面的元素也是內容複製麼?

//不可變字串
NSString *string = @"string";
NSString *cpString1 = [string copy];            //指標拷貝
NSString *mcpString1 = [string mutableCopy];    //內容拷貝,生成可變物件
NSMutableString *mstr = [string mutableCopy];   //同上
NSLog(@"%p %p %p %p",string,cpString1,mcpString1,mstr);
列印結果如下:
0x100001070 0x100001070 0x10051bbc0 0x100600e10

//可變字串
NSMutableString *mstr1 = [[NSMutableString alloc]initWithString:@"abcde"];
NSMutableString *mstr2 = [mstr1 copy];          //內容拷貝,生成不可變字串
NSString *cpstring2 = [mstr1 copy];             //同上
NSMutableString *mstr3 = [mstr1 mutableCopy];   //內容拷貝
NSLog(@"%p %p %p %p",mstr1,mstr2, cpstring2,mstr3);
列印結果如下:
0x102063110 0x656463626155 0x656463626155 0x102063160複製程式碼

NS* NSMutable*等集合類的copy、mutableCopy同上述一樣,需要注意的是,集合裡面的元素並沒有內容拷貝!若集合層級很多,且需要完全內容拷貝,可以利用NSKeyedArchiver實現。

9.為什麼IBOutlet修飾的UIView也適用weak關鍵字?

  • 防止(Retain Cycles) 更新:感謝各位指出錯誤!

  • 在storyboard或者xib中建立的UIView,本身會被它的superView強引用,以UILable為例子:

    UIViewController -> UIView -> subView -> UILable

    此時控制元件拖線會預設為weak屬性,因為UIlable已經被UIView擁有,當UIViewController釋放的時候,UIView釋放,UILable才可以釋放,所以正常情況下UILable和UIView的生命週期是一樣的。設定成strong也沒什麼大問題, 但是當UILable從其父檢視UIView上remove掉,UIViewController對其還有一個strong強引用,UILable無法釋放,這時就比較尷尬了...

10.nonatomic和atomic的區別?atomic是絕對的執行緒安全麼?為什麼?如果不是,那應該如何實現?

  • nonatomic:表示非原子性,不安全,但是效率高。
  • atomic:表示原子性,安全,但是效率較低。
  • atomic:通過鎖定機制來確保其原子性,但只是讀/寫安全,不能絕對保證執行緒的安全,當多執行緒同時訪問的時候,會造成執行緒不安全。可以使用執行緒鎖來保證執行緒的安全。

11.UICollectionView自定義layout如何實現?

  • UICollectionViewLayout是為向UICollectionView提供佈局資訊的類,包括cell的佈局資訊等。
  • UICollectionView的自定義佈局可以分為三種方式:
    1. 初始化時傳入的UICollectionViewLayout物件,通過設定UICollectionViewLayout物件屬性的值可以設定item的基本佈局,包括大小,間距等。
    2. 實現UICollectionViewLayoutDelegate協議對應的方法,返回佈局需要的值。
    3. 繼承UICollectionViewLayout類實現自定義的MyCollectionViewLayout,重寫相關方法返回自定義的佈局。

12.用StoryBoard開發介面有什麼弊端?如何避免?

  • 難以維護,難以定位問題。
  • ...

13.程式和執行緒的區別?同步非同步的區別?並行和併發的區別?

  • 程式是具有一定獨立功能的程式關於某個資料集合上的一次執行活動,程式是系統進行資源分配和排程的一個獨立單位
  • 程式中所包含的一個或多個執行單元稱為執行緒thread
  • 同步:多個任務情況下,一個任務A執行結束,才可以執行另一個任務B。
    iOS常見基礎面試題(附參考答案)
  • 非同步:多個任務情況下,一個任務A正在執行,同時可以執行另一個任務B。任務B不用等待任務A結束才執行。非同步雖然具有開啟新執行緒的能力,但是並不一定開啟新執行緒,跟任務所指定的佇列型別有關。同步和非同步的主要區別在於會不會阻塞當前執行緒
    iOS常見基礎面試題(附參考答案)
  • 並行:指兩個或多個事件在同一時刻發生。多核CUP同時開啟多條執行緒供多個任務同時執行,互不干擾。
    iOS常見基礎面試題(附參考答案)
  • 併發:指兩個或多個事件在同一時間間隔內發生。可以在某條執行緒和其他執行緒之間反覆多次進行上下文切換,看上去就好像一個CPU能夠並且執行多個執行緒一樣。其實是偽非同步。如下併發圖,在同一執行緒,任務A先執行了20%,然後A停止,任務B重新開始接管執行緒開始執行。
    iOS常見基礎面試題(附參考答案)
  • 併發的關鍵是你有處理多個任務的能力,不一定要同時。 並行的關鍵是你有同時處理多個任務的能力。

14.執行緒間通訊?

  • 在1個程式中,執行緒往往不是孤立存在的,多個執行緒之間需要經常進行通訊。可以利用pthread,NSthread,GCD,NSOperation進行相關操作。如:
  • performSelector函式
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;複製程式碼
  • GCD
dispatch_async(otherQueue, ^{
    // dosth
});
複製程式碼
  • NSOperation
[otherQueue addOperationWithBlock:^{
    // dosth
}];複製程式碼

15.GCD的一些常用的函式?(group,barrier,訊號量,執行緒同步)

  • group : 當所有任務都執行完成之後,才執行dispatch_group_notify中的任務 dosthNotify。

- (void)gcdGroup{
    dispatch_group_t group =  dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // dosth1;
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // dosth2;
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // dosthNotify;
    });
}複製程式碼
  • barrier : 柵欄方法,當柵欄前一組操作執行完之後,才能開始執行後一組方法
- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        // dosth1;
    });
    dispatch_async(queue, ^{
        // dosth2;
    });
    dispatch_barrier_async(queue, ^{
        // doBarrier;
    });
    dispatch_async(queue, ^{
        // dosth4;
    });
    dispatch_async(queue, ^{
        // dosth5;
    });
}複製程式碼
  • 訊號量 : dispatch_semaphore
  • 主要作用:
    • 1.保持執行緒同步,將非同步執行任務轉換為同步執行任務
      dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
      dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
      dispatch_async(queue, ^{
          // dosth1
          // 使訊號量+1並返回
          dispatch_semaphore_signal(semaphore);
      });
      // 若訊號的訊號量為0,則會阻塞當前執行緒,直到訊號量大於0或者經過輸入的時間值;若訊號量大於0,則會使訊號量減1並返回,程式繼續住下執行
      dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
      // dosth2 ,只有當dosth1執行完,訊號量+1之後,才會執行這裡
      複製程式碼
    • 2.保證執行緒安全,為執行緒加鎖:訊號總量設為 1 時也可以當作鎖來用

16.如何使用佇列來避免資源搶奪?

  • 將需要訪問同一塊資源的任務新增到一個非非同步並行佇列執行
  • 使用GCD的group,barrier,semaphore函式等
  • 執行緒加鎖,如 : @synchronized 關鍵字加鎖、NSLock 物件鎖、NSConditionLock條件鎖、NSRecursiveLock遞迴鎖、pthread_mutex 互斥鎖等。

17.資料持久化的幾個方案(fmdb用沒用過)

  • plist檔案
  • preference偏好設定
  • NSKeyedArchiver
  • SQLite 3
  • CoreData
  • fmdb
  • realm

18.說一下AppDelegate的幾個方法?從後臺到前臺呼叫了哪些方法?第一次啟動呼叫了哪些方法?從前臺到後臺呼叫了哪些方法?

// 當應用程式啟動時(不包括已在後臺的情況下轉到前臺),呼叫此回撥。launchOptions是啟動引數,假如使用者通過點選push通知啟動的應用,這個引數裡會儲存一些push通知的資訊
– (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions NS_AVAILABLE_IOS(3_0);
– (void)applicationDidBecomeActive:(UIApplication *)application;

//應用即將從前臺狀態轉入後臺
- (void)applicationWillResignActive:(UIApplication *)application;
– (void)applicationDidEnterBackground:(UIApplication *)application NS_AVAILABLE_IOS(4_0);

//從後臺到前臺呼叫了:
– (void)applicationWillEnterForeground:(UIApplication *)application NS_AVAILABLE_IOS(4_0);
– (void)applicationDidBecomeActive:(UIApplication *)application;
複製程式碼

App啟動過程如下:

iOS常見基礎面試題(附參考答案)

19.NSCache優於NSDictionary的幾點?

  • NSCache執行緒安全,在多執行緒操作時,不需要手動加鎖
  • NSCache按照LRU規則,會對超出限制的資料進行自動清除,並且在系統發出低記憶體通知時,會自動刪減快取
  • NSCache的Key只是對物件的strong引用,物件不需要實現NSCopying協議,NSCache也不會像NSDictionary一樣拷貝鍵。

20.知不知道Designated Initializer(指定初始化)?使用它的時候有什麼需要注意的問題?

  • 在OC中,物件的生成分為兩步:記憶體分配,初始化例項變數
NSObject *object = [[NSObject alloc] init];複製程式碼

類方法+ alloc,其根據要建立的例項物件對應的類來分配足夠的記憶體空間。除了分配記憶體空間,其實+ alloc方法還做了其他事情,包括將物件的引用計數記為1,將物件的isa指標指向對應的執行時類物件,以及將物件的成員變數置為對應的0值(0、nil、NULL)。但是+ alloc方法返回的物件還是不可用的,在之後完成初始化方法的呼叫後,物件的建立工作才算完成。初始化方法會設定物件的成員變數為一個正確的合理的值,以及獲取一些其他額外的資源。

1.Designated Initializer 指定初始化方法

所有物件都是要初始化的,而且很多情況下,物件在初始化時是需要接收額外的引數,這就可能會提供多個初始化方法。

- (instancetype)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithTimeIntervalSinceNow:(NSTimeInterval)secs;
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)secs;
- (instancetype)initWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;複製程式碼

根據規範,通常選擇一個接收引數最多的初始化方法作為指定初始化方法,真正的資料分配和其他相關初始化操作在這個方法中完成。而其他的初始化方法則作為便捷初始化方法去呼叫這個指定初始化方法。這樣當實現改變時,只要修改指定初始化方法就可以了。便捷初始化方法接收的引數更少,它會在內部呼叫指定初始化方法時,直接設定未接收引數的預設值。便捷初始化方法也可以不直接呼叫指定初始化方法,它可以呼叫其他便捷初始化方法,但不管呼叫幾層,最終是要呼叫到指定初始化方法的,因為真正的實現操作是在指定初始化方法中完成的。所有初始化方法統一以- init開始。如上例程式碼所示,- initWithTimeIntervalSinceReferenceDate方法是一個指定初始化方法,而其他初始化方法最終是要呼叫它的。

2.子類實現指定初始化方法

當子類繼承父類後實現了新的指定初始化方法,此時如果呼叫父類中的指定初始化方法則無法呼叫到子類新實現的初始化邏輯,所以子類同時還要重寫父類的指定初始化方法,將其變為一個便捷初始化方法,最終去呼叫子類自己的指定初始化方法。而為了保證父類初始化邏輯的執行,在子類指定初始化方法中,首先要通過關鍵字super呼叫父類的指定初始化方法。 詳見正確編寫Designated Initializer的幾個原則

3.initWithCoder

如果父類也實現了協議,首先要呼叫父類的- initWithCoder:方法,如果父類沒有實現,則呼叫父類的指定初始化方法。

4.NS_DESIGNATED_INITIALIZER

當在介面中指定初始化方法的後面加上該巨集,編譯器就會檢查我們實現的初始化呼叫鏈是否符合規則,並提示相應的警告。

- (instancetype)init NS_DESIGNATED_INITIALIZER;複製程式碼

21.實現description方法能取到什麼效果?

  • 當使用log列印該物件時,可以詳細的知道該物件的資訊,方便程式碼除錯。

22.objc使用什麼機制管理物件記憶體?

  • 1.MRC(MannulReference Counting)
  • 2.Xcode4.2+引入了ARC(Automatic Reference Counting)
  • 3.在ObjC中記憶體的管理是依賴物件引用計數器來進行的:在ObjC中每個物件內部都有一個與之對應的整數(retainCount),叫“引用計數器”,當一個物件在建立之後它的引用計數器為1,當呼叫這個物件的alloc、retain、new、copy、mutableCopy方法之後該物件retainCount+1(ObjC中呼叫一個物件的方法就是給這個物件傳送一個訊息),當呼叫這個物件的release方法之後它的引用計數器減1,如果一個物件的引用計數器為0,則系統會自動呼叫這個物件的dealloc方法來銷燬這個物件。
  • 4.記憶體管理遵存這一規則:
    • 1.凡是用alloc,retain,new(或使用new開頭),copy(或使用copy開頭的方法),mutableCopy(或使用mutableCopy開頭的方法)“建立”對的物件都必須使用release或者autoRelease方法“釋放”。
    • 2.誰(在哪裡)建立誰釋放(哪個類建立,哪個類釋放;誰寫alloc,誰寫release)
  • 5.autorelease : 自動釋放。 當某物件呼叫autorelease方法後,在其所在的NSAutoreleasePool廢棄時,都將呼叫其release方法
  • 6.autoreleasePool : 自動釋放池。

相關文章