被面試問的問題, 和網(wǎng)上找到的我覺得不錯的問題
會保持更新 -- 因都寫在一起了, 所以可能會穿插添加. 請諒解
iOS-多線程
常見的幾種線程鎖 / 保證線程安全
- @synchronized 適用線程不多,任務(wù)量不大的多線程加鎖
- NSLock 常用鎖 性能一般
- dispatch_semaphore_t GCD信號鎖,性能不錯
- OSSpinLock 性能非常高 但是線程出現(xiàn)了問題
Run字輩
簡單講一下Runtime
Runtime 它是OC的運行時, 其中最重要的就是消息機制 C語言是編譯時就已經(jīng)確定了如何調(diào)用.
OC是在運行的時候才會確定
當我們調(diào)用了一個方法的時候 運行過程是?
- Runtime會將我們的方法調(diào)用 轉(zhuǎn)為消息發(fā)送
objc_msgSend
, 并且把方法的調(diào)用者和方法選擇器 作為參數(shù)一起傳遞過去 - 方法的調(diào)用者會通過isa指針查找到該方法所屬的類, 然后在Cache 或者 Method_Lists中查找該方法, 如果有則調(diào)用執(zhí)行
- 如果沒有找到該方法, 則通過SuperClass 繼續(xù)像父類查找(如何到NSObject還未查找到則進入消息轉(zhuǎn)發(fā))
動態(tài)方法解析
一般情況下程序Carsh 類似
unrecognized selector sent to instance 內(nèi)存地址
在Crash之前Runtime會給我們補救的機會(消息轉(zhuǎn)發(fā))
- 檢測這個 selector 是不是要忽略的。比如 Mac OS X 開發(fā),有了垃圾回收就不理會 retain,release 這些函數(shù)了
- 檢測這個 target 是不是 nil 對象。ObjC 的特性是允許對一個 nil 對象執(zhí)行任何一個方法不會 Crash玻墅,因為會被忽略掉
- 如果上面兩個都過了牍帚,那就開始查找這個類的 IMP,先從 cache 里面找抄邀,完了找得到就跳到對應(yīng)的函數(shù)去執(zhí)行. 如果 cache 找不到就找一下方法分發(fā)表
- 如果分發(fā)表找不到就到超類的分發(fā)表去找坷随,一直找房铭,直到找到NSObject類為止
如果還找不到就要開始進入消息轉(zhuǎn)發(fā)了,消息轉(zhuǎn)發(fā)的大致過程如圖:
- 進入
resolveInstanceMethod:
方法,詢問是否動態(tài)添加方法,如果返回Yes, 通過class_addMethod
動態(tài)添加函數(shù)方法, 消息的處理完畢, 此流程完畢. 返回No 進入 2 - 進入
ForwardTargetForSelector:
方法, 用于指定那個對象來響應(yīng)這個Selector
, 如果返回某個對象, 該對象調(diào)用此方法, 此流程完畢. 返回nil 進入 3 - 通過
methodSignatureForSelector:
來指定方法簽名,返回nil, 表示不處理, 返回簽名(通過NSMethodSignature signatureWithObjcType:"v@:"
來進行重新簽名)則進入 4 - 調(diào)用
forwardInvlication:
方法, 通過invocation
修改實現(xiàn)方法温眉,修改響應(yīng)對象等。
Runtime 能做什么
- 字典和模型相互轉(zhuǎn)換
- 方法交換
- 給分類添加屬性
- 自動歸解檔
Category 原理
通過
attachCategoryMethods
這個函數(shù)來處理的, 將類中舊方法和Category新添加的方法整合成一個新的方法列表, 并賦值給method_lists
或者method_list
. 主類中的方法和 Category 中的方法在 runtime 看來并沒有區(qū)別翁狐,它們是被同等對待的类溢,都保存在主類的方法列表中。我們需要調(diào)用時引入頭文件, 是要"照顧"編輯器大大的感受而已...
Runtime 常見的定義
- IMP implementation 實現(xiàn)的意思 指向一個方法實現(xiàn)的指針
Runloop
一般來講,一個線程一次只能執(zhí)行一個任務(wù)闯冷,執(zhí)行完成后線程就會退出砂心。如果我們需要一個機制,讓線程能隨時處理事件但并不退出
NSTime 為什么在滑動UIScrollView時候會暫停
因為Runloop 原因, 為了保證UIScrollView滑動時候的流暢度, 會將Runloop的Mode切換為
TrackingRunLoopMode
, 這時候NSTimer就不會得到回調(diào). 只需要將NSTimer的model 改為commonModeItems
即可了
NSAutoreleasePool
NSAutoreleasePool 類被用來支持自動引用計數(shù)內(nèi)存管理系統(tǒng).
autoreleased 對象什么時候釋放
對象創(chuàng)建后會被添加到最近的
autoReleasePool
中, 只有這個autoReleasePool
自身drain的時候,autoReleasePool
中的對象才會被release
根據(jù)蘋果官方文檔中對 NSAutoreleasePool 的描述蛇耀,我們可知辩诞,在主線程的 NSRunLoop 對象(在系統(tǒng)級別的其他線程中應(yīng)該也是如此,比如通過
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
獲取到的線程)的每個 event loop 開始前纺涤,系統(tǒng)會自動創(chuàng)建一個 autoreleasepool 译暂,并在 event loop 結(jié)束時 drain 。我們上面提到的場景 1 中創(chuàng)建的 autoreleased 對象就是被系統(tǒng)添加到了這個自動創(chuàng)建的 autoreleasepool 中撩炊,并在這個 autoreleasepool 被 drain 時得到釋放
AutoreleasePoolPage
那這里的 AutoreleasePoolPage 是什么東西呢外永?其實,autoreleasepool 是沒有單獨的內(nèi)存結(jié)構(gòu)的拧咳,它是通過以 AutoreleasePoolPage 為結(jié)點的雙向鏈表來實現(xiàn)的伯顶。
autoreleasepool 的運行過程可以簡單地理解為objc_autoreleasePoolPush()
、[對象 autorelease]
和objc_autoreleasePoolPop(void *)
三個過程
和線程之間的關(guān)系
每一個線程都會維護自己的 autoreleasepool 堆棧骆膝。換句話說 autoreleasepool 是與線程緊密相關(guān)的祭衩,每一個 autoreleasepool 只對應(yīng)一個線程。
iOS手勢問題
響應(yīng)者鏈
處理事件響應(yīng)者先后順序鏈. 有些時候阅签,Touch后系統(tǒng)通過hit test 機制找到了觸碰到的Initial View掐暮,但是Initial view并沒有或者無法正常處理此次Touch。這個時候愉择,系統(tǒng)便會通過響應(yīng)者鏈尋找下一個響應(yīng)者劫乱,以對此次Touc 進行響應(yīng)。
Hit-Test機制
當你點擊屏幕一個點時, 這個操作由硬件系統(tǒng)傳遞到操作系統(tǒng), 然后封裝為一個事件(Event) , 由UIApplication->UIWindows->RootView到最接近你手指的View來響應(yīng)事件
子類超出父類邊緣時候, 亦可以響應(yīng)事件(正常是不可以的, 你需要重寫
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
這個方法)
iOS關(guān)鍵字
static聲明局部變量
static 聲明的局部變量, 會改變它的存儲方式(生命周期), 使變量成為靜態(tài)變量, 在編譯時候就會分配內(nèi)存, 知道程序退出才會釋放內(nèi)存. 這樣就帶有記憶的能力, 可以記錄上一次的數(shù)據(jù), 但是因為變量還是局部變量, 所有只能在其代碼塊內(nèi)使用.
static 聲明外部變量
用static聲明外部變量锥涕,其本身就是靜態(tài)變量衷戈,這只會改變其連接方式,使其只在本文件內(nèi)部有效层坠,而其他文件不可連接或引用該變量殖妇。
作用域函數(shù)
使用static用于函數(shù)定義時,對函數(shù)的連接方式產(chǎn)生影響破花,使得函數(shù)只在本文件內(nèi)部有效谦趣,對其他文件是不可見的。這樣的函數(shù)又叫作靜態(tài)函數(shù)座每。使用靜態(tài)函數(shù)的好處是前鹅,不用擔心與其他文件的同名函數(shù)產(chǎn)生干擾,另外也是對函數(shù)本身的一種保護機制峭梳。
網(wǎng)絡(luò)部分
簡單講一下Socket
Socket 套接字, 他本身不是協(xié)議, 是對TCP/IP協(xié)議的封裝, 通過Socket才可以使用TCP/IP協(xié)議.
建立socket連接. 服務(wù)器監(jiān)聽->客戶端進行請求->連接確認
在iOS中, 一般使用CocoaAsyncSocket 這個庫來使用Socket
簡單講一下HTTP請求,以及GET和POST的區(qū)別
HTTP 請求
HTTP是計算機通過網(wǎng)絡(luò)進行通信的原則. HTTP是一中無狀態(tài)協(xié)議(即Web服務(wù)器和Web服務(wù)器間不用建立持久的鏈接,當客戶端發(fā)送請求,服務(wù)器端返回響應(yīng),鏈接就關(guān)閉了).
一次完整的通信過程 需要經(jīng)歷7個步驟
- 建立TCP鏈接
- Web瀏覽器向Web服務(wù)器發(fā)送請求命令
- Web瀏覽器發(fā)送請求頭信息
- Web服務(wù)器應(yīng)答
- Web服務(wù)器發(fā)送應(yīng)答頭信息
- Web服務(wù)器向瀏覽器發(fā)送數(shù)據(jù)
- Web服務(wù)器關(guān)閉TCP鏈接
GET和POST區(qū)別
'標準答案'
- GET在瀏覽器回退時是無害的舰绘,而POST會再次提交請求。
- GET產(chǎn)生的URL地址可以被Bookmark,而POST不可以捂寿。
- GET請求會被瀏覽器主動cache口四,而POST不會,除非手動設(shè)置秦陋。
- GET請求只能進行url編碼蔓彩,而POST支持多種編碼方式。
- GET請求參數(shù)會被完整保留在瀏覽器歷史記錄里驳概,而POST中的參數(shù)不會被保留赤嚼。
- GET請求在URL中傳送的參數(shù)是有長度限制的,而POST么有抡句。
- 對參數(shù)的數(shù)據(jù)類型探膊,GET只接受ASCII字符,而POST沒有限制待榔。
- GET比POST更不安全逞壁,因為參數(shù)直接暴露在URL上,所以不能用來傳遞敏感信息锐锣。
- GET參數(shù)通過URL傳遞腌闯,POST放在Request body中。
裝B答案
無論是GET還是POST 本質(zhì)上都是TCP鏈接,并無差別.
但是他們有重要的區(qū)別在于GET方式請求產(chǎn)生一個TCP數(shù)據(jù)包;POST是兩個.
對于GET方式的請求雕憔,瀏覽器會把http header和data一并發(fā)送出去姿骏,服務(wù)器響應(yīng)200(返回數(shù)據(jù)
而對于POST,瀏覽器先發(fā)送header斤彼,服務(wù)器響應(yīng)100 continue分瘦,瀏覽器再發(fā)送data,服務(wù)器響應(yīng)200 ok(返回數(shù)據(jù))琉苇。 并不是所有瀏覽器都會在POST中發(fā)送兩次包嘲玫,F(xiàn)irefox就只發(fā)送一次。在網(wǎng)絡(luò)環(huán)境好的情況下并扇,發(fā)一次包的時間和發(fā)兩次包的時間差別基本可以無視去团。而在網(wǎng)絡(luò)環(huán)境差的情況下,兩次包的TCP在驗證數(shù)據(jù)包完整性上穷蛹,有非常大的優(yōu)點土陪。
HTTPS的加密方式
HTTPS的加密方式
對稱加密
對稱加密是指加密和解密使用相同密鑰的加密算法。它要求發(fā)送方和接收方在安全通信之前肴熏,商定一個密鑰鬼雀。對稱算法的安全性依賴于密鑰,泄漏密鑰就意味著任何人都可以對他們發(fā)送或接收的消息解密蛙吏,所以密鑰的保密性對通信至關(guān)重要取刃。
優(yōu)點:算法公開蹋肮、計算量小出刷、加密速度快璧疗、加密效率高。
-
缺點:
交易雙方都使用同樣鑰匙馁龟,安全性得不到保證崩侠;
每對用戶每次使用對稱加密算法時,都需要使用其他人不知道的惟一鑰匙坷檩,這會使得發(fā)收信雙方所擁有的鑰匙數(shù)量呈幾何級數(shù)增長却音,密鑰管理成為用戶的負擔。
能提供機密性矢炼,但是不能提供驗證和不可否認性系瓢。
非對稱加密
這種加密或許理解起來比較困難,這種加密指的是可以生成公鑰和私鑰句灌。凡是公鑰加密的數(shù)據(jù)夷陋,公鑰自身不能解密,而需要私鑰才能解密胰锌;凡是私鑰加密的數(shù)據(jù)骗绕,私鑰不能解密,需要公鑰才能解密(比如說RSA)
非對稱加密相比對稱加密更加安全资昧,但也存在兩個明顯缺點:
CPU計算資源消耗非常大酬土。一次完全TLS握手,密鑰交換時的非對稱解密計算量占整個握手過程的90%以上格带。而對稱加密的計算量只相當于非對稱加密的0.1%撤缴,如果應(yīng)用層數(shù)據(jù)也使用非對稱加解密,性能開銷太大叽唱,無法承受屈呕。
非對稱加密算法對加密內(nèi)容的長度有限制,不能超過公鑰長度尔觉。比如現(xiàn)在常用的公鑰長度是2048位凉袱,意味著待加密內(nèi)容不能超過256個字節(jié)。
所以公鑰加密目前只能用來作密鑰交換或者內(nèi)容簽名侦铜,不適合用來做應(yīng)用層傳輸內(nèi)容的加解密
流程
首先服務(wù)器端用非對稱加密(RSA)產(chǎn)生公鑰和私鑰专甩。然后把公鑰發(fā)給客 戶端,路徑或許有人會截取钉稍,但是沒有用涤躲,因為用公鑰加密的文件只有私鑰可以解密,而私鑰永遠都不會離開服務(wù)器的贡未。當公鑰到達客戶端之后种樱,客戶端會用對稱加密產(chǎn)生一個秘鑰并且用公鑰來加密發(fā)送給服務(wù)器端蒙袍,這個秘鑰就是以后用來通信的鑰匙。這樣服務(wù)器端收到公鑰加密的秘鑰時就可以用私鑰來解公鑰從而獲得秘鑰嫩挤。這樣的話客戶端和服務(wù)器端都獲得了秘鑰害幅,信息交流相對是安全的
<div align = "center">
<img src = "http://upload-images.jianshu.io/upload_images/535139-206dfc1d526b4b1f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" align = "center" > </img>
</div>
? 聽起來確實是挺安全的,但實際上岂昭,還有一種更惡劣的攻擊是這種方法無 法防范的以现,這就是傳說中的“中間人攻擊”。在身份認證的過程中约啊,出現(xiàn)了一個“中間人”攔截我們的信息邑遏,他有意想要知道你們的消息。我們將這個中間人稱為M恰矩。當服務(wù)器第一次給客戶端發(fā)送公鑰的時候记盒,途徑M。M知道你要進行密鑰交換了外傅,它把公鑰扣了下來纪吮,假裝自己是客戶端,偽造了一個偽秘鑰(對稱加密產(chǎn)生的)栏豺,然后用服務(wù)器發(fā)來的公鑰加密了偽秘鑰發(fā)還給服務(wù)器彬碱,這樣服務(wù)器以為和客戶端完成了密鑰交換,實際上服務(wù)器是和M完成了密鑰交換(獲得了偽秘鑰)奥洼。同時M假扮成服務(wù)器自行用非對稱加密產(chǎn)生偽公鑰和偽私鑰巷疼,與客戶端進行秘鑰交換,拿到客戶端發(fā)送過來的秘鑰×榻保現(xiàn)在客戶端拿著秘鑰嚼沿,M拿著秘鑰和為偽秘鑰,服務(wù)器拿著偽秘鑰瓷患,整個交流的過程就是:
<div align = "center">
<img src = "https://user-gold-cdn.xitu.io/2017/6/21/5d38312283e1e59840315ba2099aff38 " align = "center" > </img>
</div>
這種問題就需要CA證書去解決. (應(yīng)該可以滿足大部分面試問題不再過多深入)
持續(xù)化存儲方式
- NSUserDefaults
- plist
- NSKeyedArchiver
- SQL
- CoreData
- Realm(新興數(shù)據(jù)庫, 暫時沒接觸)
一些三方實現(xiàn)過程
SDWebImage
- 入口 setImageWithURL:placeholderImage:options: 會先把 placeholderImage 顯示骡尽,然后 SDWebImageManager 根據(jù) URL 開始處理圖片。
- 進入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:擅编,交給 SDImageCache 從緩存查找圖片是否已經(jīng)下載 queryDiskCacheForKey:delegate:userInfo:.
- 先從內(nèi)存圖片緩存查找是否有圖片攀细,如果內(nèi)存中已經(jīng)有圖片緩存,SDImageCacheDelegate 回調(diào) imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager爱态。
- SDWebImageManagerDelegate 回調(diào) webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示圖片谭贪。
- 如果內(nèi)存緩存中沒有,生成 NSInvocationOperation 添加到隊列開始從硬盤查找圖片是否已經(jīng)緩存锦担。
- 根據(jù) URLKey 在硬盤緩存目錄下嘗試讀取圖片文件俭识。這一步是在 NSOperation 進行的操作,所以回主線程進行結(jié)果回調(diào) notifyDelegate:洞渔。
- 如果上一操作從硬盤讀取到了圖片套媚,將圖片添加到內(nèi)存緩存中(如果空閑內(nèi)存過小缚态,會先清空內(nèi)存緩存)。SDImageCacheDelegate 回調(diào) imageCache:didFindImage:forKey:userInfo:堤瘤。進而回調(diào)展示圖片玫芦。
- 如果從硬盤緩存目錄讀取不到圖片,說明所有緩存都不存在該圖片宙橱,需要下載圖片姨俩,回調(diào) imageCache:didNotFindImageForKey:userInfo:。
- 共享或重新生成一個下載器 SDWebImageDownloader 開始下載圖片师郑。
- 圖片下載由 NSURLConnection 來做,實現(xiàn)相關(guān) delegate 來判斷圖片下載中调窍、下載完成和下載失敗宝冕。
- connection:didReceiveData: 中利用 ImageIO 做了按圖片下載進度加載效果。
- connectionDidFinishLoading: 數(shù)據(jù)下載完成后交給 SDWebImageDecoder 做圖片解碼處理邓萨。
- 圖片解碼處理在一個 NSOperationQueue 完成地梨,不會拖慢主線程 UI。如果有需要對下載的圖片進行二次處理缔恳,最好也在這里完成宝剖,效率會好很多。
- 在主線程 notifyDelegateOnMainThreadWithInfo: 宣告解碼完成, imageDecoder:didFinishDecodingImage:userInfo: 回調(diào)給 SDWebImageDownloader歉甚。
- imageDownloader:didFinishWithImage: 回調(diào)給 SDWebImageManager 告知圖片下載完成万细。
- 通知所有的 downloadDelegates 下載完成,回調(diào)給需要的地方展示圖片纸泄。
- 將圖片保存到 SDImageCache 中赖钞,內(nèi)存緩存和硬盤緩存同時保存。寫文件到硬盤也在以單獨 NSInvocationOperation 完成聘裁,避免拖慢主線程雪营。
- SDImageCache 在初始化的時候會注冊一些消息通知,在內(nèi)存警告或退到后臺的時候清理內(nèi)存圖片緩存衡便,應(yīng)用結(jié)束的時候清理過期圖片献起。
- SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用镣陕。
- SDWebImagePrefetcher 可以預(yù)先下載圖片谴餐,方便后續(xù)使用。
優(yōu)化問題
iOS離屏渲染
直接將圖層合成到幀的緩沖區(qū)中(在屏幕上)比先創(chuàng)建屏幕外緩沖區(qū)茁彭,然后渲染到紋理中总寒,最后將結(jié)果渲染到幀的緩沖區(qū)中要廉價很多。因為這其中涉及兩次昂貴的環(huán)境轉(zhuǎn)換(轉(zhuǎn)換環(huán)境到屏幕外緩沖區(qū)理肺,然后轉(zhuǎn)換環(huán)境到幀緩沖區(qū))摄闸。觸發(fā)離屏渲染后這種轉(zhuǎn)換發(fā)生在每一幀善镰,在界面的滾動過程中如果有大量的離屏渲染發(fā)生時會嚴重影響幀率
iOS中可能會觸發(fā)離屏渲染的操作
- mask
- shadow
- group opacity
- edge antialiasing
在iOS 8之前 Core Graphics 也會觸發(fā)離屏渲染, 但是不是GPU離屏渲染是, Core Graphics 繪制 API 是在 CPU 上執(zhí)行,觸發(fā)的是 CPU 版本的離屏渲染年枕。
layer.cornerRaius > 0 是否會觸發(fā)離屏渲染
答案是不會. 只有它和
layer.masksToBounds = true
一起才會觸發(fā)
shouldRasterize = YES 是否會觸發(fā)呢
是
但是他較為特殊, 他會將離屏渲染的的同時, 會將光柵化的內(nèi)容緩存起來, 如果layer 或者 SubLayer 未發(fā)生變化, 下一幀會拿來直接使用.
當你使用光柵化時炫欺,你可以開啟“Color Hits Green and Misses Red”來檢查該場景下光柵化操作是否是一個好的選擇。綠色表示緩存被復(fù)用熏兄,紅色表示緩存在被重復(fù)創(chuàng)建品洛。
對于經(jīng)常變動的內(nèi)容,這個時候不要開啟,否則會造成性能的浪費(比如TableView)
Crash問題
什么情況下會產(chǎn)生崩潰日志
- 應(yīng)用中有Bug 崩潰
- Watchdog 超時機制. 如果用戶強制退出
- 低內(nèi)存日志
Crash收集的幾種方式
- 簡單可以自己寫一個方法 之后在寫入詞磁盤中, 下次進入時候上傳服務(wù)器, 之后刪除
void handleException(NSException *exception) {
NSArray *callStack = [exception callStackSymbols];
NSString *reason = [exception reason];
NSString *name = [exception name];
}
- 使用Xcode自帶的工具.Windows->Organizer->選擇你需要的App->Crashes會自己動下載崩潰 直接打開就可以了
- 使用第三方框架例如Bugly等
Crash分析
一般采用 .Crash 文件 + .dSYM文件 + symbolicatecrash(Xcode自帶工具) 輸出.log
詳細請看參考 )
其他知識
在不知道二進制文件格式情況下如何區(qū)分文件
可以通過二進制頭識別文件類型(說幾個簡單的 不好意思 一個沒記住)或者UE等工具打開