今天下午去面試去面試一家初創(chuàng)公司,然后又接到到了丁香園的電話面試,這篇blog記錄一下面試的一些問題,有的回答的還行,有點感覺不太好,主要是有些英文單詞說的太low了估計被鄙視了吧,下面給大家總結(jié)一下面試的一些問題,有些回答是摘要一些大神blog的出處,都有給原鏈接,希望見諒~~
簡單講解一下http請求,以及GET POST的區(qū)別
這個問得其實不是很難,主要看你了不了解了,我因為了解一些后臺的東西,所以回答的還行,下面我給大家看兩幅圖片大家就基本了解了:
- 請求Request的原數(shù)據(jù)
- 返回Resonse的原數(shù)據(jù)
總結(jié)一下,其實http請求就是發(fā)送和接受報文,報文的具體格式就如上圖所示,具體由三部分構(gòu)成,GET 和 POST比較明顯的不同就是請求方式和參數(shù)的位置不同,其他原理的不同大家可以去下面的鏈接去看:
- 請求的方法URL協(xié)議/版本
- 請求頭(Request Header)
- 請求正文(Content)
其中請求頭里面可以放很多參數(shù),比如報文的大小啊,啥的一些參數(shù),具體可以百度,這里就不展開了。
下面給兩個大神們推薦的鏈接,大家可以自行查看:
https的加密方式和幾次握手
這個問題回答的就比較菜了,程序比較復制,當時特意記了一下,沒想到面試的時候還是有點蒙,回答的非常菜,現(xiàn)在在普及一下,下面是一篇講的很詳細的blog地址,大家可以去原地址去看,我下面也簡單總結(jié)一下:
https的加密方式
- 對稱加密
? 對稱加密是指加密和解密使用相同密鑰的加密算法。它要求發(fā)送方和接收方在安全通信之前,商定一個密鑰。對稱算法的安全性依賴于密鑰,泄漏密鑰就意味著任何人都可以對他們發(fā)送或接收的消息解密,所以密鑰的保密性對通信至關(guān)重要。
對稱加密算法的優(yōu)、缺點:
優(yōu)點:算法公開、計算量小、加密速度快、加密效率高。
缺點:
1)交易雙方都使用同樣鑰匙,安全性得不到保證;
2)每對用戶每次使用對稱加密算法時,都需要使用其他人不知道的惟一鑰匙,這會使得發(fā)收信雙方所擁有的鑰匙數(shù)量呈幾何級數(shù)增長,密鑰管理成為用戶的負擔。
3)能提供機密性,但是不能提供驗證和不可否認性。
- 非對稱加密
? 這種加密或許理解起來比較困難,這種加密指的是可以生成公鑰和私鑰。凡是公鑰加密的數(shù)據(jù),公鑰自身不能解密,而需要私鑰才能解密;凡是私鑰加密的數(shù)據(jù),私鑰不能解密,需要公鑰才能解密。這種算法事實上有很多,常用的是RSA,其基于的數(shù)學原理是兩個大素數(shù)的乘積很容易算,而拿到這個乘積去算出是哪兩個素數(shù)相乘就很復雜了,具體原理有興趣可以自行研究。
非對稱加密相比對稱加密更加安全,但也存在兩個明顯缺點:
? 1)CPU計算資源消耗非常大。一次完全TLS握手,密鑰交換時的非對稱解密計算量占整個握手過程的90%以上。而對稱加密的計算量只相當于非對稱加密的0.1%,如果應用層數(shù)據(jù)也使用非對稱加解密,性能開銷太大,無法承受。
? 2)非對稱加密算法對加密內(nèi)容的長度有限制,不能超過公鑰長度。比如現(xiàn)在常用的公鑰長度是2048位,意味著待加密內(nèi)容不能超過256個字節(jié)。
所以公鑰加密目前只能用來作密鑰交換或者內(nèi)容簽名,不適合用來做應用層傳輸內(nèi)容的加解密。
加密的詳細過程
? 首先服務器端用非對稱加密(RSA)產(chǎn)生公鑰和私鑰。然后把公鑰發(fā)給客 戶端,路徑或許有人會截取,但是沒有用,因為用公鑰加密的文件只有私鑰可以解密,而私鑰永遠都不會離開服務器的。當公鑰到達客戶端之后,客戶端會用對稱加密產(chǎn)生一個秘鑰并且用公鑰來加密發(fā)送給服務器端,這個秘鑰就是以后用來通信的鑰匙。這樣服務器端收到公鑰加密的秘鑰時就可以用私鑰來解公鑰從而獲得秘鑰。這樣的話客戶端和服務器端都獲得了秘鑰,信息交流相對是安全的。流程圖如下:
? 聽起來確實是挺安全的,但實際上,還有一種更惡劣的攻擊是這種方法無 法防范的,這就是傳說中的“中間人攻擊”。在身份認證的過程中,出現(xiàn)了一個“中間人”攔截我們的信息,他有意想要知道你們的消息。我們將這個中間人稱為M。當服務器第一次給客戶端發(fā)送公鑰的時候,途徑M。M知道你要進行密鑰交換了,它把公鑰扣了下來,假裝自己是客戶端,偽造了一個偽秘鑰(對稱加密產(chǎn)生的),然后用服務器發(fā)來的公鑰加密了偽秘鑰發(fā)還給服務器,這樣服務器以為和客戶端完成了密鑰交換,實際上服務器是和M完成了密鑰交換(獲得了偽秘鑰)。同時M假扮成服務器自行用非對稱加密產(chǎn)生偽公鑰和偽私鑰,與客戶端進行秘鑰交換,拿到客戶端發(fā)送過來的秘鑰。現(xiàn)在客戶端拿著秘鑰,M拿著秘鑰和為偽秘鑰,服務器拿著偽秘鑰,整個交流的過程就是:
還有很多大家直接去大神的blog去看吧,寫的很詳細,我這就點到為止了~~
在不知道二進制文件格式的情況下如何區(qū)分文件
當聽到這么問題的時候還是有點倉促的,隱約記得是通過二進制的頭部的標識來區(qū)分的,當時也不太確定就含糊的回答了一下,說是通過二進制文件的頭部標識來區(qū)分的,看面試官的樣子不是很滿意,回答百度學習一波,百度結(jié)果如下所示,附帶原鏈接:
可以通過二進制頭識別文件類型,可以使用UE或者WinHex軟件打開:
1). JPEG/JPG
- 文件頭標識 (2 bytes): $ff, $d8 (SOI) (JPEG文件標識)
- 文件結(jié)束標識 (2 bytes): $ff, $d9 (EOI)
2). TGA
- 未壓縮的前5字節(jié) 00 00 02 0000
- RLE壓縮的前5字節(jié) 00 00 10 0000
3). PNG
- 文件頭標識 (8 bytes) 89 50 4E 470D 0A 1A 0A
4). GIF
- 文件頭標識 (6 bytes) 47 49 46 3839(37) 61
? G I F 8 9 (7) a
5). BMP
- 文件頭標識 (2 bytes) 424D
? B M
6). PCX
- 文件頭標識 (1 bytes) 0A
7). TIFF
- 文件頭標識 (2 bytes) 4D 4D 或 4949
8). ICO
- 文件頭標識 (8 bytes) 00 00 01 0001 00 20 20
9). CUR
- 文件頭標識 (8 bytes) 00 00 02 0001 00 20 20
10). IFF
- 文件頭標識 (4 bytes) 46 4F 524D
? F O R M
11). ANI
- 文件頭標識 (4 bytes) 52 49 4646
? R I F F
以上是一些文件的區(qū)別方式,回答的總方向還是對的,可能回答的不夠好,下次就知道了。
常見的幾種線程鎖
這個問題比較尷尬,因為英文不太好,加上平時用的也不多,回答的比較吞吞吐吐,就說了NSLock、@synchronized和dispatch的semaphore,其中有些單詞的讀法還不太準,想想就很尷尬,下面大概總結(jié)一下,有一下幾種:
- NSLock
- @synchronized
- dispatch的semaphore
- 條件鎖NSCondition
- 條件鎖NSConditionLock
- NSDistributedLock
- 互斥鎖POSIX
- 自旋鎖OSSpinLock
下面總結(jié)一下,說實話太多有點記不過來了 - . -,附帶詳細的原文地址:
| 各種線程鎖 | 使用場景和簡單介紹 |
|---|---|
@synchronized |
適用線程不多,任務量不大的多線程加鎖 |
NSLock |
比較常用的一種鎖,性能一般 |
dispatch_semaphore_t |
使用信號來做加鎖,性能很好 |
NSCondition |
使用其做多線程之間的通信調(diào)用不是線程安全的 |
NSConditionLock |
單純加鎖性能非常低,比NSLock低很多,但是可以用來做多線程處理不同任務的通信調(diào)用 |
NSRecursiveLock |
遞歸鎖的性能出奇的高,但是只能作為遞歸使用,所以限制了使用場景 |
NSDistributedLock |
因為是MAC開發(fā)的,就不討論了 |
POSIX(pthread_mutex) |
底層的api,復雜的多線程處理建議使用,并且可以封裝自己的多線程 |
OSSpinLock |
性能也非常高,可惜出現(xiàn)了線程問題 |
再總結(jié)一下,總的意思就是一般用dispatch_semaphore_t就行了,再簡單點用
NSLock,另外帶一個swift出的一個線程鎖的方式:
func synchronized(lock: AnyObject, closure: () -> ()) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
怎么保證線程安全
這個問題我是接著上一個問題之后回答的,感覺線程安全主要是數(shù)據(jù)競爭帶來的,下面簡單講解一下:
線程安全的代碼可以從多個線程或并發(fā)任務安全地調(diào)用,而不會造成任何問題(數(shù)據(jù)損壞,系統(tǒng)崩潰等)。例如當你多線程編程時,你用let定義一個數(shù)組,因為它是只讀的,你能在同一時間不同線程去使用它,而不會造成線程安全的問題,然而當你用var定義一個數(shù)組時就不一樣了,它不是線程安全的,當多個線程在同一時間訪問和修改數(shù)組時會產(chǎn)生不可預知的結(jié)果。
SDWebImage具體實現(xiàn)和具體類
這個問題回答的一般般吧,說了一下簡單的構(gòu)造和實現(xiàn),然后讓我說具體類的時候有點心累了,因為確實記得不是很清楚了,下面簡單總結(jié)一下一些主要的類:
SDWebImageManagerSDWebImageCombinedOperationSDImageCacheSDWebImageDownloader- 各種類目
這是一些簡單的類,大家想要詳細了解可以去這篇文章去看,非常詳細?。?!
Alamofire實現(xiàn)原理和主要的類
跟上個問題一樣,簡單的回答了一下,都怪自己沒仔細專研過這些,只是簡單看過,停留在應用層面上,下面同樣簡單介紹一下,附帶大神blog地址吧:
ManagerSessionDelegateResponseSerializationURLStringConvertible
這是一些簡單的類,大家想要詳細了解可以去這篇文章去看,非常詳細?。?!
RxSwift的原理使用和主要類?
這個幸好用過了,不過也沒深入過,就簡單抽象的講了一下響應式編程的思想,然后從應用使用方面講解了一下:RxSwift的目的是讓讓數(shù)據(jù)/事件流和異步任務能夠更方便的序列化處理,能夠使用swift進行響應式編程;讓后讓我說一下RxSwift里面有哪些Subjects,這個就比較尷尬了,這讓我只用過PublishSubject和Driver的人情何以堪,下面同樣列一下Subjects列表和大神地址:
PublishSubjectReplaySubjectBehaviorSubjectVariableDriver
下面是大神blog,有詳細介紹大家可以去閱讀~~~
realm的簡單介紹和使用時的線程問題
這個也簡單用過,也是沒往深入研究,也是大概說了一下使用過程,和多線程數(shù)據(jù)共享的坑,首先realm是一個跨平臺移動數(shù)據(jù)庫引擎,支持iOS、OS X(Objective-C和Swift)以及Android,核心數(shù)據(jù)引擎C++打造,并不是建立在SQLite之上的ORM。
跨線程時的使用
廢話不多說,直接上代碼:
let person = Person(name: "Jane")
try! realm.write {
realm.add(person)
}
// 以下是跨線程必要的操作,先建一個Reference
let personRef = ThreadSafeReference(to: person)
// 然后在需要返回數(shù)據(jù)的線程里面去resolve
DispatchQueue(label: "background").async {
let realm = try! Realm()
guard let person = realm.resolve(personRef) else {
// person 已被刪除
return
}
try! realm.write {
person.name = "Jane Doe"
}
}
這里是官方中文文檔,大家可以去看看,非常詳細~~~
簡單講一講RunTime和RunLoop
runtimer和runloop因為看過一篇文章寫的特別好,有一定了解,說了runtime的一些主要功能和應用的地方,下面簡單介紹一下:
RunTime
-
RunTime簡稱運行時; - OC就是運行時機制,就是在運行的時候調(diào)用一些機制;
- 對于C語言,在編譯的時候會決定調(diào)用哪個函數(shù);
- 對于OC,在編譯的時候并不能決定調(diào)用哪個函數(shù),只有在真正運行的時候才會根據(jù)函數(shù)的SEL來調(diào)用對應的函數(shù)。
RunTime 應用范圍
- 發(fā)送消息
- 交換方法
- 動態(tài)添加方法
- 動態(tài)添加屬性
其中用的比較多的就是用類目給某個類動態(tài)添加屬性。
RunLoop
RunLoop簡單來說就是事件循環(huán),保持APP一直處于存活方式的一種機制,讓線程能隨時處理事件但并不退出,下面有一篇超級棒的RunLoop文章給大家介紹一下,我這就不展開說了,了解RunLoop看那篇文章足夠了。
簡單說一下iOS的幾種持續(xù)化存儲方式
這個回答的就比較輕松了,下面隨便列幾個吧,大家有其他的可以補充一下:
NSUserDefaultsplistNSKeyedArchiverSQLcoredatarealm
WKWebView緩存和清理緩存的方法
WKWebView緩存的使用
主要通過NSURLCache對請求的數(shù)據(jù)進行緩存,具體實現(xiàn)可以去這個github上去查看~~~~
清楚緩存的方法
詳情看這篇blog,這里簡單陳述一下,其實在iOS9出了一個方法,調(diào)用一下就清除了:
NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
//// Date from
NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0];
//// Execute
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{
// Done
}];
WKWebview的cookie的使用
主要用到的類有NSHTTPCookie,大家同樣也可以去大神的blog去查看~~~~
autorelease的實現(xiàn),以及autorelease對象什么時候會被release
這個問題回答的不太好,說實話當時連續(xù)面試了兩家,鬧著已經(jīng)有點蒙了,這里借用喵神翻譯的一本書上的原話來描述一下:
? autorelease它會將接受該消息的對象放到一個預先建立的自動釋放池 (auto release pool) 中,并在 自動釋放池收到 drain 消息時將這些對象的引用計數(shù)減一,然后將它們從池子中移除 (這一過程形象地稱為“抽干池子”)。”
【摘錄來自: 王巍 (onevcat). “Swifter - Swift 必備 Tips (第三版)”。 iBooks. 】
實現(xiàn)原理的話大概就是被autorelease標記的類會被加入一個池子,當這個池子drain時里面的引用計數(shù)會減1。
談談對swift中extension的理解
說實話其實這個問題當時比較吵,聽的不是很清楚,問了好幾遍問題,挺尷尬的,下面簡單介紹一下吧,因為這個在swift中用的比較多:
- 首先
extension在swift中類似oc的類目,可以擴展方法,計算屬性,不能添加存儲屬性; - 可以通過
extension讓類實現(xiàn)某個協(xié)議,一般這個用的也比較多,比如實現(xiàn)Comparable這個協(xié)議; - 還有一個很重要的,就是可以通過
extension對協(xié)議進行擴展,添加默認實現(xiàn),屬于黑魔法吧,非常好用。
前兩點在面試的時候都又提到,最后一點壓根忘了,自己的面試發(fā)揮真不是一般的差- . -
swift寫時復制,自己的結(jié)構(gòu)體怎么實現(xiàn)寫時復制
幸好之前看了喵神翻譯的【Swift進階】 ,受益頗深,下面同樣借用喵神的話給大家簡單描述一下,大家可以去買這本書,還是挺劃算的,下面大量復制喵神書里面的內(nèi)容,請見諒:
在 Swift 標準庫中,像是 Array,Dictionary 和 Set 這樣的集合類型是通過一種叫做寫時復制 (copy-on-write) 的技術(shù)實現(xiàn)的。我們這里有一個整數(shù)數(shù)組:
var x = [1,2,3]
var y = x
如果我們創(chuàng)建了一個新的變量 y,并且把 x 賦值給它時,會發(fā)生復制,現(xiàn)在 x 和 y 含有的是獨立的結(jié)構(gòu)體。在內(nèi)部,這些 Array 結(jié)構(gòu)體含有指向某個內(nèi)存的引用。這個內(nèi)存就是數(shù)組中元素所存儲的位置,它們位于堆 (heap) 上。在這個時候,兩個數(shù)組的引用指向的是內(nèi)存中同一個位置,這兩個數(shù)組共享了它們的存儲部分。不過,當我們改變 x 的時候,這個共享會被檢測到,內(nèi)存將會被復制。這樣一來,我們得以獨立地改變兩個變量。昂貴的元素復制操作只在必要的時候發(fā)生,也就是我們改變這兩個變量的時候發(fā)生復制:
x.append(5)
y.removeLast()
x // [1, 2, 3, 5]
y // [1, 2]
如果 Array 結(jié)構(gòu)體中的引用在數(shù)組被改變的一瞬間時是唯一的話 (比如,沒有聲明 y),那么也不會有復制發(fā)生,內(nèi)存的改變將在原地進行。這種行為就是寫時復制,作為一個結(jié)構(gòu)體的作者,你并不能免費獲得這種特性,你需要自己進行實現(xiàn)。當你自己的類型內(nèi)部含有一個或多個可變引用,同時你想要保持值語義,并且避免不必要的復制時,為你的類型實現(xiàn)寫時復制是有意義的。
下面看看通過簡單的例子看一下:
var array = [COWStruct()]
array[0].change() // No copy
var otherArray = [COWStruct()]
var x = array[0]
x.change() // Copy
然而自己去實現(xiàn)一個寫時復制的話,首先你要判斷引用的唯一性,不是唯一的話進行寫時復制,唯一的話直接改變需要改變的值。
結(jié)構(gòu)體和類以及它們的使用時機
結(jié)構(gòu)體和類主要的區(qū)別就是一個是值類型,一個是引用類型;值類型是寫時復制的,引用類型是不會發(fā)生寫時復制的;當我們需要一個簡單不需要繼承、不多變的數(shù)據(jù)時候我們首選結(jié)構(gòu)體,因為在數(shù)據(jù)結(jié)構(gòu)上來說結(jié)構(gòu)體的存取效率是高于類的,反之當我們需要一個數(shù)據(jù)結(jié)構(gòu)比較大,需要繼承,變化比較多的時候我們選擇類,因為在變化的過程中結(jié)構(gòu)體可能會發(fā)生寫時復制,而類不會;下面舉一個簡單的例子:
以Array和NSMutableArray來說:
當有一個數(shù)組,數(shù)據(jù)量相對比較小,也不用去經(jīng)常改變它,只是用來存數(shù)據(jù)和取數(shù)據(jù),我們首先Array
當數(shù)組的數(shù)據(jù)量很大的時候,并且經(jīng)常要去對他進行添加,刪除等操作,并且經(jīng)常賦值給其他變量的話就推薦使用NSMutableArray
讓你實現(xiàn)一個數(shù)據(jù)存儲的框架的具體思路
這個是一個比較開發(fā)性的問題了,我想到的是用策略模式的方式來簡單實現(xiàn)一下,使用策略模式的好處是方便文件類型的擴展,下面我簡單畫個簡單的UML圖大家看一看吧:
當然這只是一個初步的模型,還有很多細節(jié)待考慮,比如文件緩存什么的,是存本地還是磁盤,這都得去考慮,小弟只是拋磚引玉給個簡單的思路。
寫完已經(jīng)是深夜了,以上是我丁香園電話面試的一些問題,和之前面試一些回答不好的問題,最后面試完我問了一下丁香園的面試官對我感覺怎樣,他說廣度還行深度不夠,我確實又有這點問題,想學的知識比較多,有時候也沒來得及去看實現(xiàn)原理,只是簡單的過一下,沒深入研究透徹,這是我需要加強的地方,之后如果有二面的話我會在繼續(xù)更新的,最后謝謝大家的閱讀~~我是WCL,大家可以去我github關(guān)注一波