前言
今年年初求職時,整理、回顧了學習iOS開發以來收獲的知識,此篇為當時的筆記。
插一段我對面試的看法。
公司要在短短的幾個小時內要詳細了解求職者,并且求職者可能遠遠大于崗位需求,這個并不是一件簡單的事情。
求職分為幾大部分:
1、簡歷篩選,去掉大部分不符合要求的;
2、筆試,去掉沒有準備的;
3、初面,去掉基礎不扎實的;
4、復試,去掉綜合能力欠缺的;
5、HR面,去掉三觀不正確的;
在這個過程中,一個好的求職者會不斷修改簡歷,已適應不同公司的要求;提前整理、回顧基礎知識,以應對筆試和初試;對過去的工作經歷進行總結,不斷提高自己的綜合能力;并在生活與工作中,培養好的工作習慣和態度。
在這個過程中,筆記就顯得很重要。
面試題
這是面試的常問問題,僅供大家參考;
如果覺得有用,不妨點個喜歡。
1、strong、weak、unsafe_unretained 這三個修飾符分別是什么?
2、performSelector為什么會內存泄漏?
3、如何對真機的crash日志進行分析?
4、對RunLoop的理解?
5、對象回收時Weak指針自動被置為nil的實現原理?
6、常見的持久化實現方法?
7、動畫中的圖層樹、邏輯樹、動畫樹、顯示樹分別是什么?
8、APP的生命周期(應用程序的狀態)有哪些?
9、多線程中同步方式有哪些?
10、一個十級臺階,你在第一級臺階,每次能往上走一級或者兩級臺階,問走到第十級臺階有多少種方案?
正文
以下是iOS相關的知識點。
異常和捕獲
1、try-catch
@try{
//raise exception
}
@catch (NSException *exception) {
// cannot raise exception
}
@finally {
// execute
}
// execute
2、捕獲
NSSetUncaughtExceptionHandler
3、線上崩潰分析
在上面的捕獲函數中,捕捉堆棧。
[NSThread callStackSymbols]
4、線上卡頓統計
CADisplayLink每幀回調,用時間間隔算幀率;
計算每次runloop的耗時。
UIWindow
UIWindow繼承自UIView,是視圖的容器。
一般的app只需一個UIWindow,在AppDelegate中。
UIWindow的主要作用:
- 作為最頂層的視圖容器,存放app的視圖;
- 傳遞觸摸和鍵盤等事件;
KVO與Notification的異同
KVO和Notification本質都是觀察者模式。
KVO是被觀察者直接發消息(-willChange
和-didChange
),耦合性較強,適合某些綁定,比如說界面上的進度條顯示;
Notification是被觀察者發消息給NotificationCenter,再由NotificationCenter轉發出去,耦合性較低,適合登錄、等級變化、監聽全局的某個屬性變化;
Objective-C消息機制的原理
先介紹Objective-C的類結構:
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
typedef struct objc_class *Class;
每一個OC對象本質上都是一個結構體,包括成員變量列表和成員方法列表,對象通過isa指針指向類;
類本質上也是一個對象,是元類(meteClass
)的實例,元類包括類方法的列表,類通過類的isa指針指向元類;
所有的元類繼承根元類,根元類isa指針指向本身;
objc_msgSend
方法:objc_msgSend含兩個必要參數:receiver、方法名(selector)
[receiver message];
將被轉換為:objc_msgSend(receiver, selector);
帶參數的情況是:objc_msgSend(receiver, selector, arg1, arg2, …);
當向一個對象發送消息時,objc_msgSend
方法根據對象的isa指針找到對象的原來類,然后在類的方法列表中查找selector;
如果查找不到,通過Class super_class
指針找到父類,并在父類的方法列表查找,直到NSObject類;
查找到selector,objc_msgSend方法根據方法列表的內存地址調用該實現;
每個類都有一個獨立的緩存struct objc_cache *cache
,緩存方法調用的結果。
對象回收時Weak指針自動被置為nil的實現原理
Runtime維護著一個Weak表,用于存儲指向某個對象的所有Weak指針;
Weak表是Hash表,Key是所指對象的地址,Value是Weak指針地址的數組;
在對象被回收的時候,經過層層調用,會最終觸發下面的方法將所有Weak指針的值設為nil。
runtime源碼,objc-weak.m 的
arr_clear_deallocating
函數。
Weak指針如何注冊到Weak表中、如何維護hash表可以參考objc-weak.m中的其它源碼。
從實現中可以看出,Weak指針的使用涉及到Hash表的增刪改查,有一定的性能開銷。
Weak指針的實際應用:
iOS 8 特有iOS相關的漏洞
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
iOS 8 的UIScrollView的delegate屬性
持久化實現
ios中存儲數據基本上就是plist、sqlite和CoreData (NSUserDefault其實也是plist)
常見的持久化實現:
1、實現NSCoding,配合runtime讀取屬性,再用NSKeyedArchiver存儲到文件中;
2、實現NSCoding,存儲到NSUserDefault;
3、數據庫,使用SQLitePersistentObjects寫入db;
4、使用CoreData;
動畫性能優化
動畫的視圖結構
視圖樹/圖層樹:UIView,每個view對應一個calayer,管理觸摸、手勢等交互事件;
模型樹/邏輯樹(目標幀) :CALayer - modelLayer(),不涉及響應鏈(通過視圖層級關系傳遞觸摸事件的機制)
**呈現樹/動畫樹(當前幀) **:presentationLayer(), 動畫過程中的當前值
渲染樹/顯示樹(下一幀) :圖層和動畫打包提交到渲染服務后反序列化所得樹,被用于生成gl三角形
常見的動畫方式:
- UIView blockAnimation
- CAAnimation
動畫耗時在于:
圖片加載、alpha透明、動畫代碼混亂、離屏渲染、幀動畫過多、布局計算、遮罩、圖片過大;
某些問題不可避免,比如說圖片加載、幀動畫等,以下是自己總結的一些經驗:
- CADisplayLink控制幀動畫的幀率,避免動畫的繪制速率超過幀率;
??想想為什么?
避免使用alpha透明的圖片;
使用GCD和CAAnimation來管理動畫的流程;
使用NSOperationQueue或許也是解決方案。
- 減少遮罩以避免離屏渲染,避免光柵化視圖的頻繁更新;
- 使用代碼布局,避免autolayout;
聊天室中UITableView的優化
聊天室中,消息的顯示使用的是UITableView;
每一條消息是單獨的UITableViewCell,通過富文本顯示聊天消息,耗時操作是:富文本拼接、高度計算、滾動顯示;
優化兩個方面:
業務方向:
- 下發房間配置文件,房間分普通、熱鬧、火爆等狀態,某些情況下省略不必要的消息,再進行發言等級控制等;
- 消息合并,對同類型的消息進行合并;
代碼方向:
- 富文本根據消息內容進行拼接后緩存;
- 高度在計算過一次之后,同樣緩存;(boundingRectWithSize 可以提前計算出高度)
-
根據幀率動態加載消息數量,當進行消息追趕的時候,多條消息調用一次insert,用CADisplayLink保證添加速率和幀率一致;
代碼創建cell - 圖像預加載,程序在啟動的時候會進行禮物版本同步,把禮物圖片預先下載好,在顯示直接通過富文本進行圖片拼接;(為了避免鋸齒,圖像大小和顯示使用整數)
TCP/IP
3次握手-建立連接
1、A發送sync報文;seq=x Sync=1
2、B回復ack報文;seq=y Sync=1 ack=x+1
3、A回復ack報文;seq=x+1 Sync=1 ack=y+1
4次握手-斷開連接
1、A端發送FIN,停止發送報文;A進入FIN-WAIT
2、B端發送ACK,表示收到,繼續發送報文; A收到報文進入FIN2-WAIT
3、B端發送FIN,停止發送報文;B進入CLOSE_WAIT
4、A端收到FIN,發送ACK報文,A進入TIME_WAIT狀態
TIME_WAIT經過兩個最大報文段生存時間后,進入CLOSE狀態。(如果A在time_wait過程中,收到FIN報文,表示發送的ACK丟包了,重新發)
如何下載一個超大的文件?支持斷點續下、暫停、取消的功能。
1、NSURLConnection / NSURLSessionTask 實現下載,通過Range字段實現斷點續傳;
存在的內存占用過多的問題。
解決方案:新建文件,然后用NSOutputStream把下載的數據流直接append到文件中。
2、更簡單的解決方案:NSURLSessionDownloadTask。
缺點:下載完成之后才能獲得完整的文件,如果在下載過程中直接關閉退出程序,會丟失數據,因為數據保存在內存;
斷點續傳
http實現斷點續傳的關鍵地方就是在httprequest中加入“Range”頭。
Range頭域可以請求實體的一個或者多個子范圍。例如,
表示頭500個字節:bytes=0-499
表示第二個500字節:bytes=500-999
表示最后500個字節:bytes=-500
表示500字節以后的范圍:bytes=500-
利用NSOutputStream寫文件,在任務完成的代理方法里面,NSOutputStream關閉并且清空,對應的task清空,對應的session清空;
在 NSURLRequest中有一個HTTPBodyStream,可以方便的接受服務器返回的流數據。
HTTP協議
http(超文本傳輸協議)是一個基于請求與響應模式的、無狀態的、應用層的協議,常基于TCP的連接方式
http請求由三部分組成,分別是:請求行、消息報頭、請求正文。
常見狀態碼:
200 成功
400 請求的語法錯誤
403 Forbidden
404 not found 服務器找不到請求的資源
408 Request Time out
500 服務器內部錯誤
請求頭
GET 請求方法、地址、協議版本
GET /foo.php?first_name=John&last_name=Doe&action=Submit HTTP/1.1
請求體(POST請求有)
form-data
NSURLRequest 的allHTTPHeaderFields 可以看到以下屬性
Cache-Control →no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection →keep-alive
Content-Encoding →gzip
Content-Type →text/html; charset=utf-8
Date →Fri, 10 Mar 2017 13:35:42 GMT
Expires →Thu, 19 Nov 1981 08:52:00 GMT
Pragma →no-cache
Server →nginx
Transfer-Encoding →chunked
X-Powered-By →PHP/5.4.16
HTTP響應也是由三個部分組成,分別是:狀態行、消息報頭、響應正文。
HTTP/1.1 200 OK // 包含了HTTP協議版本、狀態碼、狀態英文名稱
Server: Apache-Coyote/1.1 // 服務器的類型
Content-Type: image/jpeg // 返回數據的類型
Content-Length: 56811 // 返回數據的長度
Date: Mon, 23 Jun 2014 12:54:52 GMT // 響應的時間
NSHTTPURLResponse的allHTTPHeaderFields 可以看到以上屬性
iOS目錄結構
Documents 是常用文檔目錄,會和iTunes同步;
Library 是資源庫文件,里面有Caches和Preference兩大目錄;
Caches一般存放緩存文件,較大;
Preference存放個人設置文件,iTunes會同步;
tmp 目錄是臨時文件,應用關閉后,可能會被刪除。
說道文件夾,就離不開序列化。
NSKeyedArchiver
實現NSCoding協議即可實現序列化;
配合運行時機制,可以動態實現將類的所有屬性序列化。
相對應的,可以用 NSKeyedUnarchiver 實現反序列化。
APP的生命周期
應用程序的狀態
- Not running未運行:程序沒啟動;
- Inactive未激活:程序在前臺運行,未接收到事件;
- Active激活:程序在前臺運行,接受到事件;
- Backgroud后臺:程序在后臺運行,在后臺停留一段時間后進入掛起狀態(Suspended),如果有音樂、下載等特殊任務的程序可以長期處于Backgroud狀態;
-
Suspended掛起:程序在后臺且不運行,當收到系統內存不足的warning時被清除出內存;
問題1:UIAlertView彈出的時候,APP處于哪一個狀態?
低電量提出彈出的時候,APP又處于哪一個狀態?
Inactive和Background。
在加載到前臺過程中,經歷了Launch和Running兩大狀態;
start->main() -> UIApplicationMain() -> Load UI file -> willFinishLaunchingWithOptions: -> Restore UI state -> didFinish
Active App => Application Become Active
接著就是不斷進行RunLoop。
還有一種:加載應用程序到后臺(在后臺打開網易云音樂)
前面的start部分一致,但最終進入的不是Foreground狀態,而是Background狀態;
在Background長時間不運行,會導致應用程序進入Suspended狀態;
線程安全問題
線程之間的資源共享,本質是對同一對象、變量、文件等進行修改和訪問,主要有以下同步方式:
- 加鎖;
- 原子操作;
- sync代碼塊;
@synchronized( 同一對象){
線程執行代碼;
}
NSOperationQueue 可以停止隊列還沒執行
suspended
但是不能終止當前操作。
RunLoop
此段,部分摘自文章
簡單運行執行runlooprun函數并不會讓系統停住等待事件,而是需要在運行runloop之前添加source,只有在有source的情況下線程才會停下來監聽各種事件。ios整個系統基本上是基于runloop這種架構的,ios程序的main線程整體上也是基于runloop的,各種事件的響應應該也是基于source這種思路。
UIApplicationMain() 函數,這個方法會為main thread 設置一個NSRunLoop 對象;
Run loop同時也負責autorelease pool的創建和釋放;
Run loop接收輸入事件來自兩種不同的來源:輸入源(input source)和定時源(timer source);
典型的子線程異步請求,需要開啟runLoop:AF 的 self.inputStream;
當在其他線程上面執行selector時,目標線程須有一個活動的run loop。對于你創建的線程,這意味著線程在你顯式的啟動run loop之前是不會執行selector方法的,而是一直處于休眠狀態;
蘋果不允許直接創建 RunLoop,它只提供了兩個自動獲取的函數:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。
一個 RunLoop 包含若干個 Mode,每個 Mode 又包含若干個 Source/Timer/Observer。
RunLoop 和 幀率之間的關系
用戶操作設備,相關的操作事件被系統生成并通過UIKit的指定端口分發。事件在 內部排成隊列,一個個的分發到Main run loop 去做處理。UIApplication對象是第一個接收到時間的對象,它決定事件如何被處理。觸摸事件分發到主窗口,窗口再分發到對應出發觸摸事件的 View。其他的事件通過其他途徑分發給其他對象變量做處理。
大部分的事件可以在你的應用里分發,類似于觸摸事件,遠程操控事件(線控耳機等) 都是由app的 responder objects 對象處理的。Responder objects 在你的app里到處都是,比如:UIApplication 對象,view對象,view controller 對象,都是resopnder objects。大部分事件的目標都指定了resopnder object,不過事件也可以傳遞給其他對象。比如,如果view對象不處理事件,可以傳給父類view或者view controller。
一個 RunLoop 包含若干個 Mode,每個 Mode 又包含若干個 Source/Timer/Observer。每次調用 RunLoop 的主函數時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode。如果需要切換 Mode,只能退出 Loop,再重新指定一個 Mode 進入。這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響。
main():
file_fd = open ("logfile")
x_fd = open_display ()
construct_interface ()
while changed_fds = select ({file_fd, x_fd}):
if file_fd in changed_fds:
data = read_from (file_fd)
append_to_display (data)
send_repaint_message ()
if x_fd in changed_fds:
process_x_messages ()
如何計算幀的持續時間:
用CACurrentMediaTime()記錄當前時間,然后和上一幀記錄的時間去比較。得到真實的每幀持續的時間,然后代替硬編碼的六十分之一秒,來更新UI。
總結
此篇的iOS知識點并不全面,僅僅是求職的一些筆記,后續接著寫工作遇到的iOS相關問題,歡迎關注iOS開發隨筆。