iOS 開發者 2019 面試總結

suiling· 2019-04-26
本文來自 天下林子 ,作者 suiling

前言


在投遞簡歷之前,就是所謂的寒冬將至,開個年會都是守望寒冬,然后我身邊的準備跳槽的大佬們,都是有幾分涼意,不過我還好,總感覺一個人吃飽,全家不餓,O(∩_∩)O哈!沒想那么多,直接就全身投入,找工作。現在做個回顧吧,為自己,也為路過的各位大俠。


先說一個問題,是寒冬嗎?我真沒覺得,說自己的一個親身體會,不夸張的說,基本上是每天2家,且持續一個月,當然是距離可以接受,公司小中大都有的,我感覺不是互聯網的寒冬,是自己的寒冬,有一句說的很好,人生就兩季,努力是旺季,不努力是淡季!我感覺很有道理~~~~


現在面試要求高在要會各種語言,另外要很深入,要夠底層,要懂數據結構與算法之美(面試過的都會體會什么是真是一言難盡吧),看一些大佬,進入一個大廠,也寫了自己的準備,我感覺真是有付出有回報的,也看出自己的一些不足吧!so,革命尚未成功,同志們仍需努力伐??!


知識點總結


因為自己水平有限,可能有些路過的大佬感覺比較簡單,我也總結了下,請飄過~~還有一些答案僅供參考,如有錯誤,請不吝賜教,在此謝過---->


+(void)initinstance 與 +(void)load兩個方法的區別于比較//小紅書面試問題\


先看下面表格兩者的區別,后續會繼續介紹



+load+initialize
調用時機被添加runtime時收到第一條消息時,可能永遠不調用
調用順序父類->子類->分類父類->子類
調用次數1次多次
是否需要顯示調用父類實現
是否沿用父類的實現
分類中的實現類和分類都執行


相同點:


  1. 系統都執行一次。

  2. 假如父類和子類都被調用,父類在子類之前被調用


不同點:


  1. load 方法會在加載類的時候就被調用,也就是 ios 應用啟動的時候,就會加載所有的類,就會調用每個類的 + load 方法。

  2. +initialize 這個方法會在 第一次初始化這個類之前 被調用,我們用它來初始化靜態變量

  3. load 會在main()函數之前調用。initialize 則在類實例化 或 類方法被調用時調用;

  4. 如果子類中沒有initialize方法,則會再次調用父類的initialize方法,類別會覆蓋主類的initialize,load則不會被覆蓋

  5. load順序在 initialize之前;

  6.  ?  initialize 方法的調用看起來會更合理,通常在它里面寫代碼比在 + load 里寫更好,因為它是懶調用的,也有可能完全不被調用。類第一次被加載時,

  7. 類接收消息時,運行時會先檢查 + initialize 有沒有被調用過。如果沒有,會在消息被處理前調用


--->>>>
initialize 最終是通過 objc_msgSend 來執行的,objc_msgSend 會執行一系列方法查找,并且 Category 的方法會覆蓋類中的方法
load 是在被添加到 runtime 時開始執行,父類最先執行,然后是子類,最后是 Category。又因為是直接獲取函數指針來執行,不會像 objc_msgSend 一樣會有方法查找的過程。


---->>>>


怎么實現單例, 2種方法實現//喜馬拉雅面試問題\


//第一種方式:線程安全的單例2(不推薦 效率低)
+ (instancetype)shareSingleton2 {
    @synchronized(self) {
        if (!singleton) {
            singleton = [[self alloc]init];
        }
    }
    return singleton;
}

//第二種方式 線程安全的單例
+ (instancetype)shareSingleton {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        singleton = [[self alloc]init];
    });
    return singleton;
}


然而僅僅知道這些是不夠的,說了上面的,面試官會繼續問單例,怎么實現的,加鎖了嗎?單例什么時候釋放?然后你就會一臉懵~有同感的舉個手


  • 單例,怎么實現的,加鎖了嗎?單例什么時候釋放
    其實在上面的兩個單例的創建中,@synchronized是一個鎖,后面會講到,就是說第一種是通過加鎖的方式來實現,而第二種解析如下:
    GCD創建:dispatch_once中dispatch_once_t類型為typedef long
    ?   onceToken= 0,線程執行dispatch_once的block中代碼
    ?   onceToken= -1,線程跳過dispatch_once的block中代碼不執行
    ?   onceToken= 其他值,線程被線程被阻塞,等待onceToken值改變
    用途:限制創建,提供全局調用,節約資源和提高性能。參考
    常見的應用場景:
    ?   UIApplication
    ?   NSNotificationCenter
    ?   NSFileManager
    ?   NSUserDefaults
    ?   NSURLCache
    ?   NSHTTPCookieStorage




那么單例是怎么銷毀的呢?如下:


方法一:
+(void)attemptDealloc{
    [_instance release]; //mrc 需要釋放,當然你就不能重寫release的方法了.
    _instance = nil;
}


方法二:
1. 必須把static dispatch_once_t onceToken; 這個拿到函數體外,成為全局的.
2.
+(void)attempDealloc{
    onceToken = 0// 只有置成0,GCD才會認為它從未執行過.它默認為0.這樣才能保證下次再次調用shareInstance的時候,再次創建對象.
    [_instance release];
    _instance = nil;
 }


數據持久化


下面說下數據持久化吧?如果是在2年前,你說了數據持久化有NSUserDefaults,plist,歸檔,CoreData巴拉巴拉,感覺這位童靴還闊以,但是現在就有點low了,你懂得~
面試大佬會問有幾種?然后每種有什么不同?什么能存儲什么不能存儲?每個在具體使用應該注意什么?等等,問到你懷疑人生


  • 屬性列表(plist存儲)通常叫做plist文件,用于存儲在程序中不經常修改、數據量小的數據,不支持自定義對象存儲,支持數據存儲的類型為:Array,Dictionary,String,Number,Data,Date,Boolean,通常用來存放接口名、城市名、銀行名稱、表情名等極少修改的數據
    plist文件是將某些特定的類,通過xml的方式保存在目錄中。


  • 偏好設置(NSUserDefaults)
    用于存儲用戶的偏好設置,同樣適合于存儲輕量級的用戶數據,數據會自動保存在沙盒的Libarary/Preferences目錄下,本質上就是一個plist文件,所以同樣的不支持自定義對象存儲,支持數據存儲的類型為:Array,Dictionary,String,Number,Data,Date,Boolean,可以用做檢查版本是否更新、是否啟動引導頁、自動登錄、版本號等等,需要注意的是NSUserDefaults是定時的將緩存中的數據寫入磁盤,并不是即時寫入,為了防止在寫完NSUserDefaults后,程序退出導致數據的丟失,可以在寫入數據后使用synchronize強制立即將數據寫入磁盤
    如果這里你沒有調用synchronize方法的話,系統會根據I/O情況不定時刻地保存到文件中。所以如果需要立即寫入文件的就必須調用synchronize方法。




PS: 在這里說了小問題,就是有面試官會問,你在開發中用NSUserDefaults有沒有什么坑?你可以這樣答:比如你存儲一個值時,沒有進行及時的調用synchronize方法,然后此時程序就crash了或者強制殺死,那么你再下次去取值的時候,就會取不到你之前存儲的值,路過的大佬可以試下~~


  • 歸檔序列化存儲
    歸檔可以直接將對象存儲為文件,也可將文件直接解歸檔為對象,相對于plist文件與偏好設置數據的存儲更加多樣,支持自定義的對象存儲,歸檔后的文件是加密的,也更加的安全,文件存儲的位置可以自定義。
    遵守NSCoding或者NSSecureCoding協議


  • 沙盒存儲
    可以提高程序的體驗度,為用戶節約數據流量,主要在用戶閱讀書籍、聽音樂、看視頻等,在沙盒中做數據的存儲,主要包含文件夾:Documents: 最常用的目錄,存放重要的數據,iTunes同步時會備份該目錄Library/Caches: 一般存放體積大,不重要的數據,iTunes同步時不會備份該目錄Library/Preferences: 存放用戶的偏好設置,iTunes同步時會備份該目錄tmp: 用于存放臨時文件,在程序未運行時可能會刪除該文件夾中的數據,iTunes同步時不會備份該目錄


  • Core Data
    Core Data是框架,并不是數據庫,該框架提供了對象關系的映射功能,使得能夠將OC對象轉換成數據,將數據庫中的數據還原成OC對象,在轉換的過程中不需要編寫任何的SQL語句,在Core Data中有三個重要的概念:
    NSPersistentStoreCoordinator:持久化存儲協調器,在NSPersistentStoreCoordinator中包含了持久化存儲區,在持久化存儲區中包含了數據表中的很多數據,持久化存儲區的設置通常選擇NSSQLiteStoreType,也就是選擇SQLite數據庫
    NSManagedObjectModel:托管對象模型,用于描述數據結構的模型


  • SQLite3
    SQLite是輕量級的數據庫,占用資源很少,最初是用于嵌入式的系統,在iOS中使用SQLite,需要加入"libsqlite3.tbd"依賴庫并導入頭文件。不應該頻繁的打開關閉數據庫,有可能會影響性能, 應在啟動程序時打開數據庫,在退出程序是關閉數據庫


  • FMDB
    FMDB以OC的方式封裝了SQLite的C語言API,減去了冗余的C語言代碼,使得API更具有OC的風格,更加的面向對象,相對于Core Data框架更加的輕量級,FMDB還提供了多線程安全的數據庫操作方法,在FMDB中有三個重要的概念:
    FMDatabase:一個FMDatabase就代表一個SQLite數據庫,執行sql語句
    FMResultSet:執行查詢后的結果集
    FMDatabaseQueue:用于在多線程中執行多個查詢或更新,安全的


===
緊接著說下CoreData吧?它總是比你知道的還要多?
CoreData中的多線程問題


主要推薦的實施方案,也是最優方案,如下:
1.使用一個NSPersistentStoreCoordinator,以及兩個獨立的Contexts,一個context負責主線程與UI協作,一個context在后臺負責耗時的處理,用Notifications的方式通知主線程的NSManagedObjectContext進行mergeChangesFromContextDidSaveNotification操作


2.后臺線程做讀寫更新,而主線程只讀


3.CoreData中的NSManagedObjectContext在多線程中不安全,如果想要多線程訪問CoreData的話,最好的方法是一個線程一個NSManagedObjectContext,每個NSManagedObjectContext對象實例都可以使用同一個NSPersistentStoreCoordinator實例,這個實例可以很安全的順序訪_問永久存儲,這是因為NSManagedObjectContext會在便用NSPersistentStoreCoordinator前上鎖。ios5.0為NSManagedObjectContext提供了initWithConcurrentcyType方法,其中的一個NSPrivateQueueConcurrencyType,會自動的創建一個新線程來存放NSManagedObjectContext而且它還會自動創建NSPersistentStoreCoordinator,




CoreData里面還帶有一個通知NSManagedObjectContextDidSaveNotification,主要監聽NSManagedObjectContext的數據是否改變,并合并數據改變到相應context。


面試官問的Context是那兩種?這個面試官問的應該是用到的那兩個Type?
答:NSConfinementConcurrencyType   NSMainQueueConcurrencyType


//創建并行的NSManagedObjectContext對象
[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];

ps:NSConfinementConcurrencyType (或者不加參數,默認就是這個)NSMainQueueConcurrencyType (表示只會在主線程中執行)


接著談談數據庫的優化問題,可以通過以下幾點進行優化


  • FMDB事務批量更新數據庫速度問題。(親測可以呀---740條數據用和不用事務效率差別20倍+)

  • 寫同步(synchronous)
    在SQLite中,數據庫配置的參數都由編譯指示(pragma)來實現的,而其中synchronous選項有三種可選狀態,分別是full、normal、off
    設置為synchronous OFF (0)時,SQLite在傳遞數據給系統以后直接繼續而不暫停

  • 一條SQL語句插入多條數據

  • 在事務中進行插入處理。

  • 數據有序插入。




再說下什么是事務?\英語流利說總監面試問題//


事務:


  • 作為單個邏輯工作單元執行的一系列操作,而這些邏輯工作單元需要具有原子性,一致性,隔離性和持久性

  • 是并發控制的基本單元。所謂的事務,它是一個操作序列,這些操作要么都執行,要么都不執行,它是一個不可分割的工作單元。例如,銀行轉賬工作:從一個賬號扣款并使另一個賬號增款,這兩個操作要么都執行,要么都不執行。所以,應該把它們看成一個事務。

  • 事務是一種機制,用于維護數據庫的完整性




事務基本特征:


  • 原子性(Atomicity):事務的個元素是不可分的,事務是一個完整的操作,一個操作序列,要么都執行,要么都不執行

  • 一致性(Consistemcy):事務完成時,數據必須是一致的,保證數據的無損

  • 隔離性(Isolation):多個事務彼此隔離,事務必須是獨立的,任何事務都不應該受影響

  • 持久性(Durability):事務完成之后,它對于系統的影響是永久的,該修改即使出現系統故障也將一直保留,真實的修改了數據庫


五種 Mach-O 類型的淺要分析


這個面試題針對我自己的簡歷,可略過~


在制作Framework時,可以設置framework中的Mach-O Type,不手動修改的默認配置即為 Dynamic Library,在SDK中默認使用的是 Relocatable Object File



Executable: 可執行二進制文件

dynamic Library 動態庫
Bundle :非獨立二進制文件,顯示加載
static Library 靜態庫
Relocatable Object File: 可重定位的目標文件,中間結果




Relocatable Object File 是組裝靜態庫和動態庫的零件,而靜態庫和動態庫就是可執行二進制文件的組件。這里用了零件和組件的概念,零件是不可缺少的,組件則是可選的


Dynamic Library 更靈活;復用性更強;且就安全來說,統一放置在 Payload/Framework 目錄下的自建的動態庫,不參與應用的加殼操作,安全性稍遜一籌
Relocatable Object File 以及 Static Library 都是在編譯后直接合并到最后的可執行文件中的,缺點相對不夠靈活,但安全性稍強。


如果要偏向靜態的方案,應該選擇 Relocatable Object File 還是 Static Library?
使用 Relocatable Object File 可以減少二進制文件的大小


動態庫和靜態庫的區別:
如果使用動態庫,需要考慮的是:


  1. 對于啟動速度的影響。

  2. 對于保密要求高的線下渠道 SDK,可能會被從 .app/ 中單獨拿出來,反編譯研究具體實現。靜態庫則比較安全一點。


內存管理


Objective-C的內存管理主要有三種方式ARC(自動內存計數)、手動內存計數、內存池。


1). 自動內存計數ARC:由Xcode自動在App編譯階段,在代碼中添加內存管理代碼。
2). 手動內存計數MRC:遵循內存誰申請、誰釋放;誰添加,誰釋放的原則。
3). 內存釋放池Release Pool:把需要釋放的內存統一放在一個池子中,當池子被抽干后(drain),池子中所有的內存空間也被自動釋放掉。內存池的釋放操作分為自動和手動。自動釋放受runloop機制影響。


有一個很經典的面試題,考察自動釋放池的如下:


 for (int i = 0; i < MAXFLOAT; i++) {

        NSString *string = @"stdy";
        string = [string lowercaseString];
        string = [string stringByAppendingString:@"123"];
        NSLog(@"--%@"string);
    }


上述的這種寫法,會使內存慢慢增加,如何解決呢,面試官想要的答案就是用自動釋放池,你也可以改成其他的,但不是面試官要的,你懂的,修改如下:



for (int i = 0; i < MAXFLOAT; i++) {
        @autoreleasepool {
            NSString *string = @"stdy";
            string = [string lowercaseString];
            string = [string stringByAppendingString:@"123"];
            NSLog(@"--%@"string);
        }
    }


  • 什么時間會創建自動釋放池?*
    從程序啟動到加載完成是一個完整的運行循環,然后會停下來,等待用戶交互,用戶的每一次交互都會啟動一次運行循環,來處理用戶所有的點擊事件、觸摸事件,運行循環檢測到事件并啟動后,就會創建自動釋放池。
    子線程的 runloop 默認是不工作,無法主動創建,必須手動創建。
    自定義的 NSOperation 和 NSThread 需要手動創建自動釋放池。比如:自定義的 NSOperation 類中的 main 方法里就必須添加自動釋放池。否則出了作用域后,自動釋放對象會因為沒有自動釋放池去處理它,而造成內存泄露。


但對于 blockOperation 和 invocationOperation 這種默認的Operation ,系統已經幫我們封裝好了,不需要手動創建自動釋放池。
@autoreleasepool 當自動釋放池被銷毀或者耗盡時,會向自動釋放池中的所有對象發送 release 消息,釋放自動釋放池中的所有對象。
如果在一個vc的viewDidLoad中創建一個 Autorelease對象,那么該對象會在 viewDidAppear 方法執行前就被銷毀了。


什么會造成離屏渲染


GPU屏幕渲染有兩種方式:
(1)On-Screen Rendering (當前屏幕渲染)
指的是GPU的渲染操作是在當前用于顯示的屏幕緩沖區進行。
(2)Off-Screen Rendering (離屏渲染)
指的是在GPU在當前屏幕緩沖區以外開辟一個緩沖區進行渲染操作。


下面的情況或操作會引發離屏渲染:


  • 為圖層設置遮罩(layer.mask)

  • 將圖層的layer.masksToBounds / view.clipsToBounds屬性設置為true

  • 將圖層layer.allowsGroupOpacity屬性設置為YES和layer.opacity小于1.0

  • 為圖層設置陰影(layer.shadow *)。

  • 為圖層設置layer.shouldRasterize=true

  • 具有layer.cornerRadius,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing的圖層

  • 文本(任何種類,包括UILabel,CATextLayer,Core Text等)。

  • 使用CGContext在drawRect :方法中繪制大部分情況下會導致離屏渲染,甚至僅僅是一個空的實現。




優化:


1、圓角優化
方案1 :使用貝塞爾曲線UIBezierPath和Core Graphics框架畫出一個圓角

方案2 :使用CAShapeLayer和UIBezierPath設置圓角


2、shadow優化
對于shadow,如果圖層是個簡單的幾何圖形或者圓角圖形,我們可以通過設置shadowPath來優化性能,能大幅提高性能


其他優化:


當我們需要圓角效果時,可以使用一張中間透明圖片蒙上去使用ShadowPath指定layer陰影效果路徑
使用異步進行layer渲染(Facebook開源的異步繪制框架AsyncDisplayKit)
設置layer的opaque值為YES,
減少復雜圖層合成盡量使用不包含透明(alpha)通道的圖片資源
盡量設置layer的大小值為整形值
直接讓美工把圖片切成圓角進行顯示,這是效率最高的一種方案很多情況下用戶上傳圖片進行顯示,
可以讓服務端處理圓角使用代碼手動生成圓角Image設置到要顯示的View上,
利用UIBezierPath(CoreGraphics框架)畫出來圓角圖片


網絡通信


?   1、應用層 協議有:HTTP FTP TFTP SMTP SNMP DNS TELNET HTTPS POP3 DHCP
?   2、表示層 數據的表示、安全、壓縮,格式有:JPEG、ASCll、DECOIC、加密格式等(數據格式化,代碼轉換,數據加密),沒有協議
?   3、會話層 建立、管理、終止會話,沒有協議
?   4、傳輸層 定義傳輸數據的協議端口號,以及流控和差錯校驗。協議有:TCP UDP,數據包一旦離開網卡即進入網絡傳輸層
?   5、網絡層 進行邏輯地址尋址,實現不同網絡之間的路徑選擇。協議有:ICMP IGMP IP(IPV4 IPV6) ARP RARP
?   6、數據鏈路層 建立邏輯連接、進行硬件地址尋址、差錯校驗 等功能。(由底層網絡定義協議)將比特組合成字節進而組合成幀,用MAC地址訪問介質,錯誤發現但不能糾正。協議有:SLIP CSLIP PPP MTU ARP[鏈接:https://baike.baidu.com/item/A ... addin]RARP
?   7、物理層 建立、維護、斷開物理連接。以二進制數據形式在物理媒體上傳輸數據(由底層網絡定義協議)協議有:ISO2110 IEEE802 IEEE802.2


===


TPC/IP協議是傳輸層協議,主要解決數據如何在網絡中傳輸


HTTP是應用層協議,主要解決如何包裝數據


我們在傳輸數據時,可以只使用(傳輸層)TCP/IP協議,但是那樣的話,如果沒有應用層,便無法識別數據內容,如果想要使傳輸的數據有意義,則必須使用到應用層協議,應用層協議有很多,比如HTTP、FTP、TELNET等,也可以自己定義應用層協議


TCP和UDP使用該協議從一個網絡傳送數據包到另一個網絡。把IP想像成一種高速公路,它允許其它協議在上面行駛并找到到其它電腦的出口。TCP和UDP是高速公路上的“卡車”,它們攜帶的貨物就是像HTTP,文件傳輸協議FTP這樣的協議等。


===========


什么是Socket?

  1. Socket其實并不是一個協議 而是一個通信模型。它是為了方便大家直接使用更底層協議(TCP | UDP)而存在的抽象層

  2. Socket是對 TCP/IP協議的封裝,Socket本身并不是協議,而是一個調用的接口(API),主要用來一臺電腦的兩個進程通信,

  3. Socket在網絡通信中,它涵蓋了網絡層、傳輸層、會話層、表示層、應用層,因為其信時候用到了IP和端口,僅這兩個就表明了它用到了網絡層和傳輸層,而且它無視多臺電腦通信的系統差別,所以它涉及了表示層,一般Socket都是基于一個應用程序的,所以會涉及到會話層和應用層


什么是WebSocket,解決了什么問題?//英語流利說面\

  1. WebSocket是應用層第七層上的一個應用層協議,它必須依賴 HTTP 協議進行一次握手 ,握手成功后,數據就直接從 TCP 通道傳輸,與 HTTP 無關了

  2. Websocket的數據傳輸是frame形式傳輸的,比如會將一條消息分為幾個frame,按照先后順序傳輸出去。這樣做會有幾個好處:
    ?   1) 大數據的傳輸可以分片傳輸,不用考慮到數據大小導致的長度標志位不足夠的情況。
    ?   2 )和http的chunk一樣,可以邊生成數據邊傳遞消息,即提高傳輸效率。

  3. 總之:WebSocket 的實現分為握手,數據發送/讀取,關閉連接。


什么是心跳?
  1. 心跳就是用來檢測TCP連接的雙方是否可用

  2. 客戶端發起心跳Ping(一般都是客戶端),假如設置在10秒后如果沒有收到回調,那么說明服務器或者客戶端某一方出現問題,這時候我們需要主動斷開連接。


HTTP 的幾種請求方式?以及區別 \英語流利說//


英語流利說總監問了一個HTTP的PUT請求,下面看下各個請求的不同之處吧
HTTP1.0定義了三種請求方法:GET, POST 和 HEAD方法。
HTTP1.1新增了五種請求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。
HTTP協議使用的是URI,是一種表示資源標志,那么對應的HTTP Verb就是各種對資源的操作,GET,PUT,DELETE等,明確這些,再往下看。可參考
HTTP: Hyper Text Transfer Protocol,超文本傳輸協議URI: Universal Resource Identifier,統一資源標識符URL: Universal Reversource Locator,統一資源定位符
簡單地說,URI是在某一規則下能把資源獨一無二地標識出來,URL是特殊的URI,即用定位的方式實現URI


GET 請求指定的頁面信息,并返回實體主體。
HEAD 類似于get請求,只不過返回的響應中沒有具體的內容,用于獲取報頭。
PUT:從客戶端向服務器傳送的數據取代指定的文檔的內容, 用PUT來達到更改資源,需要client提交資源全部信息,如果只有部分信息,不應該使用PUT
DELETE:請求服務器刪除指定的頁面。
OPTIONS:允許客戶端查看服務器的性能。


HTTPS


一般面試官問了你HTTP之后就會問你HTTPS了,真是一個都不能少伐?


  • HTTPS(Hyper Text Transfer Protocol over Secure Socket Layer), 是以安全為目標的HTTP通道,簡單講是HTTP的安全版。即HTTP下加入SSL層,HTTPS的安全基礎是SSL,因此加密的詳細內容就需要SSL

  • HTTPS的通信過程,盜一張圖



HTTPS通信過程:

  1. 客戶端請求https鏈接,服務端返回公鑰

  2. 客戶端產生隨機對稱密鑰

  3. 客戶端用公鑰對對稱密鑰加密

  4. 客戶端發送加密后的對稱密鑰

  5. 客戶端發送通過對稱密鑰加密的密文通信


===
HTTPS與HTTP的區別:

  • 超文本傳輸協議HTTP協議被用于在Web瀏覽器和網站服務器之間傳遞信息。

  • HTTP協議以明文方式發送內容,不提供任何方式的數據加密

  • HTTPS:安全套接字層超文本傳輸協議HTTPS, 在HTTP的基礎上加入SSL協議,SSL依靠證書來驗證服務器的身份,并為瀏覽器和服務器之間的通信加密

  • https協議需要到ca申請證書,一般免費證書很少,需要交費。

  • http是超文本傳輸協議,信息是明文傳輸,https 則是具有安全性的ssl加密傳輸協議。

  • http和https使用的是完全不同的連接方式,用的端口也不一樣,前者是80,后者是443。

  • http的連接很簡單,是無狀態的;HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,比http協議安全。




兩個小問題
1)如何保證公鑰不被篡改?
解決方法:將公鑰放在數字證書中。只要證書是可信的,公鑰就是可信的。
(2)公鑰加密計算量太大,如何減少耗用的時間?
解決方法:每一次對話(session),客戶端和服務器端都生成一個"對話密鑰"(session key),用它來加密信息。由于"對話密鑰"是對稱加密,所以運算速度非???,而服務器公鑰(非對稱加密)只用于加密"對話密鑰"本身,這樣就減少了加密運算的消耗時間。


SSL協議


SSL: SSL協議的基本思路是采用公鑰加密法, 采就是客戶端先向服務器端索要公鑰,然后用公鑰加密信息,服務器收到密文后,用自己的私鑰解密。


SSL協議的基本過程如下:


  1. 客戶端向服務器端索要并驗證公鑰

  2. 雙方協商生成”對話密鑰”

  3. 雙方采用“ 對話密鑰”進行加密通信c


NSTimer面試考點


先來說一下NSTimer在使用的時候內存泄漏的分析



NSTimer必須與RunLoop搭配使用,因為其定時任務的觸發基于RunLoop,NSTimer使用常見的Target-Action模式。由于RunLoop會強引用timer,timer會強引用Target,容易造成循環引用、內存泄露等問題


loop 強引用timer, timer 強引用 target,如果不能釋放,會造成內存泄漏,有一個面試官問如果在target中傳入weak的self,那么可以解決循環引用問題嗎?答案是否,


Target強引用or弱引用Timer并不是問題的關鍵,問題的關鍵是:一定要在Timer使用完畢調用invalidate使之失效(手動調用or系統自動調用),Timer從RunLoop中被移除并清除強引用,這個操作可打破引用1、2,而引用3是強弱引用已經不重要了


NSTimer一共有三種初始化方案:init開頭的普通創建方法、timer開頭的類工廠方法、scheduled開頭的類工廠方法。前兩者需要手動加入RunLoop中,后者會自動加入當前RunLoop的DefaultMode中


以上我只是整理說了一些核心的點,其他部分可閱讀這里


對于NSTimer,面試官還會問,它是否是時間準確呢?大家可能都知道是時間不準確的,因為受RunLoop的影響,那么GCD中也有延時,如果用GCD來做延時,那時間準確嗎?


答案是GCD的time是準確的,GCD 的線程管理是通過系統來直接管理的。GCD Timer 是通過 dispatch port 給 RunLoop 發送消息,來使 RunLoop 執行相應的 block,如果所在線程沒有 RunLoop,那么 GCD 會臨時創建一個線程去執行 block,執行完之后再銷毀掉,因此 GCD 的 Timer 是不依賴 RunLoop 的。


KVC和KVO


在這里只說一個問題,kvo 里面什么時候修改屬性的stter方法的?
中間類在被觀察的屬性的setter方法中,在改變屬性值的前后分別添加了willChangeValueForKey:和didChangeValueForKey:。使其在通過KVC標準改變屬性值時可以被觀察到,并向觀察者發送消息。


AFNetworking的工作原理,2.0和3.0的線程區別?


AFNetworking 2.0 線程  使用的是常駐線程,自己創建線程并添加到runloop中,AFN每次進行的網絡操作,開始、暫停、取消操作時都將相應的執行任務扔進了自己創建的線程的 RunLoop 中進行處理,從而避免造成主線程的阻塞。



每一個請求對應一個AFHTTPRequestOperation實例對象(以下簡稱operation),每一個operation在初始化完成后都會被添加到一個NSOperationQueue中。由這個NSOperationQueue來控制并發,系統會根據當前可用的核心數以及負載情況動態地調整最大的并發 operation 數量,我們也可以通過setMaxConcurrentoperationCount:方法來設置最大并發數。注意:并發數并不等于所開辟的線程數。具體開辟幾條線程由系統決定。
也就是說此處執行operation是并發的、多線程的。




AF中常駐線程的實現


  1. 使用單例創建線程

  2. 添加到runloop中,且加了一個NSMachPort,來防止這個新建的線程由于沒有活動直接退出?!?使用MachPort配合RunLoop進行線程?;睢?/span>


AF3.x為什么不再需要常駐線程?
NSURLConnection的一大痛點就是:發起請求后,這條線程并不能隨風而去,而需要一直處于等待回調的狀態。
NSURLSession發起的請求,不再需要在當前線程進行代理方法的回調!可以指定回調的delegateQueue,這樣我們就不用為了等待代理回調方法而苦苦?;釹叱塘?。
同時還要注意一下,指定的用于接收回調的Queue的maxConcurrentOperationCount設為了1,這里目的是想要讓并發的請求串行的進行回調。
為什么要串行回調?


- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
    NSParameterAssert(task);
    AFURLSessionManagerTaskDelegate *delegate = nil;
    [self.lock lock];
    //給所要訪問的資源加鎖,防止造成數據混亂
    delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
    [self.lock unlock];
    return delegate;
}


這邊對 self.mutableTaskDelegatesKeyedByTaskIdentifier 的訪問進行了加鎖,目的是保證多線程環境下的數據安全
面試官可能會問你:為什么AF3.0中需要設置self.operationQueue.maxConcurrentOperationCount = 1;而AF2.0卻不需要?
--->>>
AF3.0的operationQueue是用來接收NSURLSessionDelegate回調的,鑒于一些多線程數據訪問的安全性考慮,設置了maxConcurrentOperationCount = 1來達到串行回調的效果


--->>>
AF2.0的operationQueue是用來添加operation并進行并發請求的,所以不要設置為1。


MRC環境下在assign、retain、copy下屬性的set方法


直接上代碼了-->>


//assign環境下
-(void)setName:(NSString *)name{

    _name = name;
}
//retain環境下
-(void)setName:(NSString *)name{

    if (_name != name) {
        [_name release];
        _name = [name retain];
    }
}
//copy環境下
-(void)setName:(NSString *)name{

    if (_name != name) {
        [_name release];
        _name = [name copy];
    }
}


深拷貝,淺拷貝


  • 淺copy,類似strong,持有原始對象的指針,會使retainCount加一。

  • 深copy,會創建一個新的對象,不會對原始對象的retainCount變化。
    面試官可能會問,如果對一個可變數組進行深拷貝,則會對可變數組里面的元素也會進行重新復制一份嗎?答:不會,深拷貝,可變數組就是一個箱子,如果進行深拷貝,則會再拷貝出一個新的箱子,但箱子里面的元素不會拷貝出新的。


iOS中的幾種鎖


互斥鎖
用于多線程編程,防止兩條線程同時對同一公共資源進行讀寫的機制。NSLock,pthread_mutex, @synchronized


遞歸鎖
遞歸鎖有一個特點,就是同一個線程可以加鎖N次而不會引發死鎖。
NSRecursiveLock, 2.pthread_mutex(recursive):


自旋鎖:
是用于多線程同步的一種鎖,線程反復檢查鎖變量是否可用。由于線程在這一過程中保持執行,因此是一種忙等待。一旦獲取了自旋鎖,線程會一直保持該鎖,直至顯式釋放自旋鎖。
OSSpinLock


信號量:一種同步方式
信號量可以有更多的取值空間,用來實現更加復雜的同步,而不單單是線程間互斥。
dispatch_semaphore:


條件鎖:
就是條件變量,當進程的某些資源要求不滿足時就進入休眠,也就是鎖住了。當資源被分配到了,條件鎖打開,進程繼續運行。
NSCondition, NSConditionLock


遵循NSLocking協議,使用的時候同樣是lock,unlock加解鎖,wait是傻等,waitUntilDate:方法是等一會,都會阻塞掉線程,signal是喚起一個在等待的線程,broadcast是廣播全部喚起。


讀寫鎖:


//加讀鎖
pthread_rwlock_rdlock(&rwlock);
//解鎖
pthread_rwlock_unlock(&rwlock);
//加寫鎖
pthread_rwlock_wrlock(&rwlock);
//解鎖
pthread_rwlock_unlock(&rwlock);



@synchronized結構在工作時為傳入的對象分配了一個遞歸鎖,其他內容可參閱文檔


SDWebImage 緩存原理


對于常用的三方庫,一般面試官都會問到,因為篇幅較長,我只說一些比較核心的點,


  • SDWebImage 使用的是NSCache進行緩存的,為什么用NSCache進行緩存呢,


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //創建一個NSCache緩存對象
        NSCache *cache = [[NSCache alloc] init];
        //設置緩存中的對象個數最大為5個
        [cache setCountLimit:5];
        //創建一個CacheTest類作為NSCache對象的代理
        CacheTest *ct = [[CacheTest alloc] init];
        //設置代理
        cache.delegate = ct;

        //創建一個字符串類型的對象添加進緩存中,其中key為Test
        NSString *test = @"Hello, World";
        [cache setObject:test forKey:@"Test"];

        //遍歷十次用于添加
        for (int i = 0; i < 10; i++)
        {
            [cache setObject:[NSString stringWithFormat:@"Hello%d", i] forKey:[NSString stringWithFormat:@"World%d", i]];
            NSLog(@"Add key:%@  value:%@ to Cache", [NSString stringWithFormat:@"Hello%d", i], [NSString stringWithFormat:@"World%d", i]);
        }

        for (int i = 0; i < 10; i++)
        {
            NSLog(@"Get value:%@ for key:%@", [cache objectForKey:[NSString stringWithFormat:@"World%d", i]], [NSString stringWithFormat:@"World%d", i]);
        }

        [cache removeAllObjects];

        for (int i = 0; i < 10; i++)
        {
            NSLog(@"Get value:%@ for key:%@", [cache objectForKey:[NSString stringWithFormat:@"World%d", i]], [NSString stringWithFormat:@"World%d", i]);
        }

        NSLog(@"Test %@", test);
    }

    return 0;
}


上面的代碼創建了一個NSCache對象,設置了其最大可緩存對象的個數為5個,當我們要添加第六個對象時NSCache自動刪除了我們添加的第一個對象并觸發了NSCacheDelegate的回調方法,

添加第七個時也是同樣的,刪除了緩存中的一個對象才能添加進去,一下情況NSCache會刪除緩存:
?   NSCache緩存對象自身被釋放
?   手動調用removeObjectForKey:方法
?   手動調用removeAllObjects
?   緩存中對象的個數大于countLimit,或,緩存中對象的總cost值大于totalCostLimit
?   程序進入后臺后
?   收到系統的內存警告




異步方式在ioQueue上執行刪除操作,所有IO操作使用一個串行隊列來執行,避免加鎖釋放鎖的復雜,還有就是使用NSOperation作為一個標識用來取消耗時的磁盤查詢任務。內存緩存就直接刪除NSCache對象的數據,磁盤緩存就直接獲取文件的絕對路徑后刪除即可


if (fromDisk) {
        //異步方式在ioQueue上執行刪除操作
        dispatch_async(self.ioQueue, ^{
            //使用key構造一個默認路徑下的文件存儲的絕對路徑
            //調用NSFileManager刪除該路徑的文件
            [_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];
            //有回調塊就在主線程中執行
            if (completion) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completion();
                });
            }
        });
     //不需要刪除磁盤數據并且有回調塊就直接執行
    } else if (completion){
        completion();
    }


刪除磁盤中過期的圖片,以及當緩存大小大于配置的值時,進行緩存清理



- (void)backgroundDeleteOldFiles {
    Class UIApplicationClass = NSClassFromString(@"UIApplication");
    if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
        return;
    }
    UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
    __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
        // Clean up any unfinished task business by marking where you
        // stopped or ending the task outright.
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];

    // Start the long-running task and return immediately.
    [self deleteOldFilesWithCompletionBlock:^{
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
}


多線程


iOS中有哪些多線程方案?


常用的有三種: NSThread NSOperationQueue GCD


1、NSThread 是這三種范式里面相對輕量級的,但也是使用起來最負責的,
你需要自己管理thread的生命周期,線程之間的同步。線程共享同一應用程序的部分內存空間,
它們擁有對數據相同的訪問權限。你得協調多個線程對同一數據的訪問,
一般做法是在訪問之前加鎖,這會導致一定的性能開銷。

2、NSOperationQueue 以面向對象的方式封裝了用戶需要執行的操作,
我們只要聚焦于我們需要做的事情,而不必太操心線程的管理,同步等事情,
因為NSOperation已經為我們封裝了這些事情。
NSOperation 是一個抽象基類,我們必須使用它的子類。

3、 GCD: iOS4 才開始支持,它提供了一些新的特性,以及運行庫來支持多核并行編程,
它的關注點更高:如何在多個cpu上提升效率。

總結:
NSThread是早期的多線程解決方案,實際上是把C語言的PThread線程管理代碼封裝成OC代碼。
- GCD是取代NSThread的多線程技術,C語法+block。功能強大。
NSOperationQueue是把GCD封裝為OC語法,額外比GCD增加了幾項新功能。
    * 最大線程并發數
    * 取消隊列中的任務
    * 暫停隊列中的任務
    * 可以調整隊列中的任務執行順序,通過優先級
    * 線程依賴
    * NSOperationQueue支持KVO。這就意味著你可以觀察任務的狀態屬性。
但是NSOperationQueue的執行效率沒有GCD高,所以一半情況下,我們使用GCD來完成多線程操作。


面試題:多個網絡請求完成后執行下一步?

第一種方式:使用dispatch_group


-(void)Btn2{
    NSString *str = @"//www.jianshu.com/p/6930f335adba";
    NSURL *url = [NSURL URLWithString:str];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];

    dispatch_group_t downloadGroup = dispatch_group_create();
    for (int i=0; i<10; i++) {
        dispatch_group_enter(downloadGroup);

        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

            NSLog(@"%d---%d",i,i);
            dispatch_group_leave(downloadGroup);

        }];

        [task resume];
    }

    dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
        NSLog(@"end");
    });
}


創建一個dispatch_group_t, 每次網絡請求前先dispatch_group_enter,請求回調后再dispatch_group_leave,對于enter和leave必須配合使用,有幾次enter就要有幾次leave,否則group會一直存在。當所有enter的block都leave后,會執行dispatch_group_notify的block。


第二種方式可以采用信號量dispatch_semaphore_t


-(void)Btn3{
    NSString *str = @"//www.jianshu.com/p/6930f335adba";
    NSURL *url = [NSURL URLWithString:str];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];

    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    for (int i=0; i<10; i++) {

        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

            NSLog(@"%d---%d",i,i);
            count++;
            if (count==10) {
                dispatch_semaphore_signal(sem);
                count = 0;
            }

        }];

        [task resume];
    }
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"end");
    });
}


dispatch_semaphore信號量為基于計數器的一種多線程同步機制。如果semaphore計數大于等于1,計數-1,返回,程序繼續運行。如果計數為0,則等待。dispatch_semaphore_signal(semaphore)為計數+1操作,dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)為設置等待時間,這里設置的等待時間是一直等待。
對于以上代碼通俗一點就是,開始為0,等待,等10個網絡請求都完成了,dispatch_semaphore_signal(semaphore)為計數+1,然后計數-1返回,程序繼續執行。(這里也就是為什么有個count變量的原因,記錄網絡回調的次數,回調10次之后再發信號量,使后面程序繼續運行)。


什么是dispatch_barrier_async(柵欄函數)?


dispatch_barrier_sync(dispatch_queue_t   queue, ^{

    })


  • 在它前面的任務執行結束后它才執行,它后面的任務要等它執行完成后才會開始執行,

  • 避免數據競爭


sync和async
sync:同于當前線程, 可以是主線程也可以是子線程
async:就是不同于當前線程, 可以是主線程也可以是子線程


XMPP是什么?XMPP進行傳輸時,需要傳大量的數據,如何減少數據?

XMPP:
1)XMPP 是一種基于XML的協議,XMPP是一個分散型通信網絡
2)XMPP是一種基于標準通用標記語言的子集XML的協議,它繼承了在XML環境中靈活的發展性,XMPP有超強的擴展性。XMPP中定義了三個角色,客戶端,服務端,網關。通信能夠在這個三者的任意兩個之間雙向發生,而他們的傳輸是XML流
3)XMPP工作原理:所有從一個客戶端到另一個客戶端的消息和數據都要通過服務端
4)XMPP允許建立并行的TCP套接字鏈接對所有連接上的客戶端和服務器端。持久的套接字的連接使得XMPP能夠更有效的支持高級的具有存在能力的應用在帶寬和處理資源的使用中。


小結:
而XMPP的核心部分就是一個在網絡上分片斷發送XML的流協議。這個流協議是XMPP的即時通訊指令的傳遞基礎,也是一個非常重要的可以被進一步利用的網絡基礎協議。所以可以說,XMPP用TCP傳的是XML流。


=======
如何減少數據?


  1. 如果是大量的數據,對于XML,需要對傳的信息進行簡化,比如command, message中的信息要簡化,

  2. 使用別的數據傳輸協議,比如protocol Buff(可以傳輸binary 二進制數據),格式可以用json


Swift問題


swift語言和OC語言的本質區別是什么?

答:本質區別是Swift是靜態語言,而OC是動態語言,面試回去路上,才想到問題的最好的答案----


問題:子類不能重寫父類的extension的方法?怎么解決呢?

解決方法如下:


//父類中
@objc extension MOBBaseViewController {
    //要重寫的方法
    public func testExt() {

        print("----------");

    }

}


-----
//子類中
import UIKit


class MOBClassifyViewController: MOBBaseViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

    //重寫父類extension方法
     override func testExt(){

        print(">>>>>>>>>>>>")
    }

}


因為extension中的方法是私有的,so子類訪問不到,因此要用public修飾下,@objc有以下兩點說明:
?   fileprivate 或者 private 保證方法私有 能在同一個類 或者 同一個文件(extension)中訪問這個方法 如果定義為private 那么只能在一個類中訪問 不能在類擴展中訪問
?   允許這個函數在“運行時”通過oc的消息機制調用


NSString跟Swift String的區別和使用場景


NSString和String的共同點
  • String保留了大部分NSString的api比如
    .hasPrefix
    .lowercaseString
    .componentsSeparatedByString
    .substringWithRange 等等
    所以很多常規操作在開發中使用兩者之一都是可以的,


NSString和String的不同點
  • NSString是引用類型。Swift String是值類型


var nsString: NSString = NSString()
  var swiftString:String = String()        
  var nsString: NSString = "dsx"
  var swiftString:String = "dsx"


兩者都可以使用自己的類名來直接進行初始化,下面的方法也是初始化,雖然寫法相同,但是NSString的意思是初始化了一個指針指向了這個字符串,但Swift String的意思則是把字符串字面量賦值給變量


  • NSString需要用append或者stringWithFormat將兩個字符串拼接,Swift String只需要用 + 即可

  • Swift String 可以實現字符串遍歷


for character in "My name is dsx".characters {
  print(character)
}


  • 計算字符串長度,NSString直接使用 字符串.length 就可以獲得字符串的長度,swift真正的類似于.length的方法就是取出characters屬性(數組)然后.count

  • 比較字符串相等的方式

et strA: NSString = ""
let strB: NSString = ""
let strC: NSString = "dsx"
let strD: NSString = "dsx"

// NSString 字符串相等
if(strA.isEqualToString(strB as String)){
  print("yes");
}

// String的相等   
if (strC == strD){
  print("yes");
}


  • NSString可以同基本數據類型見轉化


var strA: NSString = "12306"
var strB: NSString = "0.618"  
var numOfInt       = strA.integerValue;
var numOfDouble    = strB.doubleValue;


  • String可以通過isEmpty屬性來判斷該字符串是否為空,是string獨有的

  • String獨有的字符串插入字符功能

var strA:String = "My name is dx"
strA.insert("s", atIndex: strA.characters.indexOf("x")!);
print(strA) // My name is dsx


僅僅可以插入單個字符不能插字符串,如果里面寫成ss 就會報錯Cannot convert value of type 'String' to expected argument type 'Character'


作者:天下林子
鏈接:https://www.jianshu.com/p/1e752f5678f1
世界杯足球直播 27477六肖精准网站 七乐彩中奖查询 11选5手机计划软件 麻将游戏旧版本 北京pk10技巧高手赚钱 黑马计划软件官网 百人龙虎怎么玩才能赢 中网内蒙古时时开奖结果查询 印尼分分彩计划app 老时时彩开奖走势图 3d技巧规律准确率高 欢乐炸金花 北京pk10怎么研究走势 吉林快3彩票站走势图 河北时时玩法介绍