程序員面試時這樣介紹自己的項目經驗,成功率能達到98.99%

聲明:面試是對自我審視的一種過程,面試題和iOS程序員本身技術水平沒任何關聯,無論你能否全部答出,都不要對自己產生任何正面或消極的評價!(面試題均來自羣成員提供)

面試題預覽:

1.KVO實現原理?

2.說說你理解的埋點?

3.消息轉發機制原理?

4.說說你理解weak屬性?

5.假如Controller太臃腫,如何優化?

6.項目中網絡層如何做安全處理?

7.main()之前的過程有哪些?

1.KVO實現原理?

KVO在Apple中的API文檔如下:

Automatic key-value observing is implemented using a technique called isa-swizzling… When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class …

KVO基本原理:

1.KVO是基於runtime機制實現的

2.當某個類的屬性對象第一次被觀察時,系統就會在運行期動態地創建該類的一個派生類,在這個派生類中重寫基類中任何被觀察屬性的setter 方法。派生類在被重寫的setter方法內實現真正的通知機制

3.如果原類爲Person,那麼生成的派生類名爲NSKVONotifying_Person

4.每個類對象中都有一個isa指針指向當前類,當一個類對象的第一次被觀察,那麼系統會偷偷將isa指針指向動態生成的派生類,從而在給被監控屬性賦值時執行的是派生類的setter方法

5.鍵值觀察通知依賴於NSObject 的兩個方法: willChangeValueForKey: 和 didChangevlueForKey:;在一個被觀察屬性發生改變之前, willChangeValueForKey:一定會被調用,這就 會記錄舊的值。而當改變發生後,didChangeValueForKey:會被調用,繼而 observeValueForKey:ofObject:change:context: 也會被調用。

KVO深入原理:

1.Apple 使用了 isa 混寫(isa-swizzling)來實現 KVO 。當觀察對象A時,KVO機制動態創建一個新的名爲: NSKVONotifying_A的新類,該類繼承自對象A的本類,且KVO爲NSKVONotifying_A重寫觀察屬性的setter 方法,setter 方法會負責在調用原 setter 方法之前和之後,通知所有觀察對象屬性值的更改情況。

2.NSKVONotifying_A類剖析:在這個過程,被觀察對象的 isa 指針從指向原來的A類,被KVO機制修改爲指向系統新創建的子類 NSKVONotifying_A類,來實現當前類屬性值改變的監聽;

3.所以當我們從應用層面上看來,完全沒有意識到有新的類出現,這是系統“隱瞞”了對KVO的底層實現過程,讓我們誤以爲還是原來的類。但是此時如果我們創建一個新的名爲“NSKVONotifying_A”的類(),就會發現系統運行到註冊KVO的那段代碼時程序就崩潰,因爲系統在註冊監聽的時候動態創建了名爲NSKVONotifying_A的中間類,並指向這個中間類了。

4.(isa 指針的作用:每個對象都有isa 指針,指向該對象的類,它告訴 Runtime 系統這個對象的類是什麼。所以對象註冊爲觀察者時,isa指針指向新子類,那麼這個被觀察的對象就神奇地變成新子類的對象(或實例)了。) 因而在該對象上對 setter 的調用就會調用已重寫的 setter,從而激活鍵值通知機制。

5.子類setter方法剖析:KVO的鍵值觀察通知依賴於 NSObject 的兩個方法:willChangeValueForKey:和 didChangevlueForKey:,在存取數值的前後分別調用2個方法: 被觀察屬性發生改變之前,willChangeValueForKey:被調用,通知系統該 keyPath 的屬性值即將變更;當改變發生後, didChangeValueForKey: 被調用,通知系統該 keyPath 的屬性值已經變更;之後, observeValueForKey:ofObject:change:context: 也會被調用。且重寫觀察屬性的setter 方法這種繼承方式的注入是在運行時而不是編譯時實現的。

KVO原理圖

2.說說你理解的埋點?

3.消息轉發機制原理?

消息轉發機制基本分爲三個步驟:

1、動態方法解析

2、備用接受者

3、完整轉發

轉發機制原理

新建一個HelloClass的類,定義兩個方法:

@interfaceHelloClass:NSObject

  • (void)hello;

  • (HelloClass *)hi;@end

動態方法解析

對象在接收到未知的消息時,首先會調用所屬類的類方法+resolveInstanceMethod:(實例方法)或者+resolveClassMethod:(類方法)。在這個方法中,我們有機會爲該未知消息新增一個”處理方法”“。不過使用該方法的前提是我們已經實現了該”處理方法”,只需要在運行時通過class_addMethod函數動態添加到類裏面就可以了。

void functionForMethod(id self, SEL _cmd)

{

NSLog(@"Hello!");

}

Class functionForClassMethod(id self, SEL _cmd)

{

NSLog(@"Hi!");

return [HelloClass class];

}

#pragma mark - 1、動態方法解析

  • (BOOL)resolveClassMethod:(SEL)sel

{

NSLog(@"resolveClassMethod");

NSString *selString = NSStringFromSelector(sel);

if ([selString isEqualToString:@"hi"])

{

Class metaClass = objc_getMetaClass("HelloClass");

class_addMethod(metaClass, @selector(hi), (IMP)functionForClassMethod, "v@:");

return YES;

}

return [super resolveClassMethod:sel];

}

  • (BOOL)resolveInstanceMethod:(SEL)sel

{

NSLog(@"resolveInstanceMethod");

NSString *selString = NSStringFromSelector(sel);

if ([selString isEqualToString:@"hello"])

{

class_addMethod(self, @selector(hello), (IMP)functionForMethod, "v@:");

return YES;

}

return [super resolveInstanceMethod:sel];

}

備用接受者

動態方法解析無法處理消息,則會走備用接受者。這個備用接受者只能是一個新的對象,不能是self本身,否則就會出現無限循環。如果我們沒有指定相應的對象來處理aSelector,則應該調用父類的實現來返回結果。

#pragma mark - 2、備用接收者

  • (id)forwardingTargetForSelector:(SEL)aSelector

{

NSLog(@"forwardingTargetForSelector");

NSString *selectorString = NSStringFromSelector(aSelector);

// 將消息交給_helper來處理 if ([selectorString isEqualToString:@"hello"]) {

return _helper;

}

return [super forwardingTargetForSelector:aSelector];

}

在本類中需要實現這個新的接受對象

@interfaceHelloClass()

{

RuntimeMethodHelper *_helper;

}

@end

@implementationHelloClass- (instancetype)init

{

self = [super init];

if (self)

{

_helper = [RuntimeMethodHelper new];

}

return self;

}

RuntimeMethodHelper 類需要實現這個需要轉發的方法:

#import"RuntimeMethodHelper.h"

@implementationRuntimeMethodHelper- (void)hello

{

NSLog(@"%@, %p", self, _cmd);

}@end

完整消息轉發

如果動態方法解析和備用接受者都沒有處理這個消息,那麼就會走完整消息轉發:

#pragma mark - 3、完整消息轉發

  • (void)forwardInvocation:(NSInvocation *)anInvocation

{

NSLog(@"forwardInvocation");

if ([RuntimeMethodHelper instancesRespondToSelector:anInvocation.selector]) {

[anInvocation invokeWithTarget:_helper];

}

}

/必須重新這個方法,消息轉發機制使用從這個方法中獲取的信息來創建NSInvocation對象/

  • (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

{

NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];

if (!signature)

{

if ([RuntimeMethodHelper instancesRespondToSelector:aSelector])

{

signature = [RuntimeMethodHelper instanceMethodSignatureForSelector:aSelector];

}

}

return signature;

}

4.說說你理解weak屬性?

weak實現原理:

Runtime維護了一個weak表,用於存儲指向某個對象的所有weak指針。weak表其實是一個hash(哈希)表,Key是所指對象的地址,Value是weak指針的地址(這個地址的值是所指對象的地址)數組。

1、初始化時:runtime會調用objc_initWeak函數,初始化一個新的weak指針指向對象的地址。

2、添加引用時:objc_initWeak函數會調用 objc_storeWeak() 函數, objc_storeWeak() 的作用是更新指針指向,創建對應的弱引用表。

3、釋放時,調用clearDeallocating函數。clearDeallocating函數首先根據對象地址獲取所有weak指針地址的數組,然後遍歷這個數組把其中的數據設爲nil,最後把這個entry從weak表中刪除,最後清理對象的記錄。

追問的問題一:

1.實現weak後,爲什麼對象釋放後會自動爲nil?

runtime 對註冊的類, 會進行佈局,對於 weak 對象會放入一個 hash 表中。 用 weak 指向的對象內存地址作爲 key,當此對象的引用計數爲 0 的時候會 dealloc,假如 weak 指向的對象內存地址是 a ,那麼就會以 a 爲鍵, 在這個 weak 表中搜索,找到所有以 a 爲鍵的 weak 對象,從而設置爲 nil 。

追問的問題二:

2.當weak引用指向的對象被釋放時,又是如何去處理weak指針的呢?

1、調用objc_release

2、因爲對象的引用計數爲0,所以執行dealloc

3、在dealloc中,調用了_objc_rootDealloc函數

4、在_objc_rootDealloc中,調用了object_dispose函數

5、調用objc_destructInstance

6、最後調用objc_clear_deallocating,詳細過程如下:

a. 從weak表中獲取廢棄對象的地址爲鍵值的記錄

b. 將包含在記錄中的所有附有 weak修飾符變量的地址,賦值爲 nil

c. 將weak表中該記錄刪除

d. 從引用計數表中刪除廢棄對象的地址爲鍵值的記錄

5.假如Controller太臃腫,如何優化?

1.將網絡請求抽象到單獨的類中

方便在基類中處理公共邏輯;

方便在基類中處理緩存邏輯,以及其它一些公共邏輯;

方便做對象的持久化。

2.將界面的封裝抽象到專門的類中

構造專門的 UIView 的子類,來負責這些控件的拼裝。這是最徹底和優雅的方式,不過稍微麻煩一些的是,你需要把這些控件的事件回調先接管,再都一一暴露回 Controller。

3.構造 ViewModel

借鑑MVVM。具體做法就是將 ViewController 給 View 傳遞數據這個過程,抽象成構造 ViewModel 的過程。

4.專門構造存儲類

專門來處理本地數據的存取。

5.整合常量

6.項目中網絡層如何做安全處理?

1、儘量使用https

https可以過濾掉大部分的安全問題。https在證書申請,服務器配置,性能優化,客戶端配置上都需要投入精力,所以缺乏安全意識的開發人員容易跳過https,或者拖到以後遇到問題再優化。https除了性能優化麻煩一些以外其他都比想象中的簡單,如果沒精力優化性能,至少在註冊登錄模塊需要啓用https,這部分業務對性能要求比較低。

2、不要傳輸明文密碼

不知道現在還有多少app後臺是明文存儲密碼的。無論客戶端,server還是網絡傳輸都要避免明文密碼,要使用hash值。客戶端不要做任何密碼相關的存儲,hash值也不行。存儲token進行下一次的認證,而且token需要設置有效期,使用refresh

token去申請新的token。

3、Post並不比Get安全

事實上,Post和Get一樣不安全,都是明文。參數放在QueryString或者Body沒任何安全上的差別。在Http的環境下,使用Post或者Get都需要做加密和簽名處理。

4、不要使用301跳轉

301跳轉很容易被Http劫持***。移動端http使用301比桌面端更危險,用戶看不到瀏覽器地址,無法察覺到被重定向到了其他地址。如果一定要使用,確保跳轉發生在https的環境下,而且https做了證書綁定校驗。

5、http請求都帶上MAC

所有客戶端發出的請求,無論是查詢還是寫操作,都帶上MAC(Message Authentication

Code)。MAC不但能保證請求沒有被篡改(Integrity),還能保證請求確實來自你的合法客戶端(Signing)。當然前提是你客戶端的key沒有被泄漏,如何保證客戶端key的安全是另一個話題。MAC值的計算可以簡單的處理爲hash(request

params+key)。帶上MAC之後,服務器就可以過濾掉絕大部分的非法請求。MAC雖然帶有簽名的功能,和RSA證書的電子簽名方式卻不一樣,原因是MAC簽名和簽名驗證使用的是同一個key,而RSA是使用私鑰簽名,公鑰驗證,MAC的簽名並不具備法律效應。

6、http請求使用臨時密鑰

高延遲的網絡環境下,不經優化https的體驗確實會明顯不如http。在不具備https條件或對網絡性能要求較高且缺乏https優化經驗的場景下,http的流量也應該使用AES進行加密。AES的密鑰可以由客戶端來臨時生成,不過這個臨時的AES

key需要使用服務器的公鑰進行加密,確保只有自己的服務器才能解開這個請求的信息,當然服務器的response也需要使用同樣的AES

key進行加密。由於http的應用場景都是由客戶端發起,服務器響應,所以這種由客戶端單方生成密鑰的方式可以一定程度上便捷的保證通信安全。

7、AES使用CBC模式

不要使用ECB模式,記得設置初始化向量,每個block加密之前要和上個block的祕文進行運算。

7.main()之前的過程有哪些?

1、main之前的加載過程

1)dyld 開始將程序二進制文件初始化

2)交由ImageLoader 讀取 image,其中包含了我們的類,方法等各種符號(Class、Protocol 、Selector、 IMP)

3)由於runtime 向dyld 綁定了回調,當image加載到內存後,dyld會通知runtime進行處理

4)runtime 接手後調用map_images做解析和處理

5)接下來load_images 中調用call_load_methods方法,遍歷所有加載進來的Class,按繼承層次依次調用Class的+load和其他Category的+load方法

6)至此 所有的信息都被加載到內存中

7)最後dyld調用真正的main函數

注意:dyld會緩存上一次把信息加載內存的緩存,所以第二次比第一次啓動快一點

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