歡迎光臨
每天分享高質量文章

iOS 開發者 2019 面試總結

作者:天下林子
連結:https://www.jianshu.com/p/1e752f5678f1

前言

在投遞簡歷之前,就是所謂的寒冬將至,開個年會都是守望寒冬,然後我身邊的準備跳槽的大佬們,都是有幾分涼意,不過我還好,總感覺一個人吃飽,全家不餓,?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 裡寫更好,因為它是懶呼叫的,也有可能完全不被呼叫。類第一次被載入時,

  1. 類接收訊息時,執行時會先檢查 + 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 
        NSString *string = @"stdy";
        string = [string lowercaseString];
        string = [string stringByAppendingString:@"123"];
        NSLog(@"--%@"string);
    }

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

 

for (int i = 0; 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進行執行緒保活】

 

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

 

1NSThread 是這三種正規化裡面相對輕量級的,但也是使用起來最負責的,
你需要自己管理thread的生命週期,執行緒之間的同步。執行緒共享同一應用程式的部分記憶體空間,
它們擁有對資料相同的訪問許可權。你得協調多個執行緒對同一資料的訪問,
一般做法是在訪問之前加鎖,這會導致一定的效能開銷。

2NSOperationQueue 以面向物件的方式封裝了使用者需要執行的操作,
我們只要聚焦於我們需要做的事情,而不必太操心執行緒的管理,同步等事情,
因為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 = @"http://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 = @"http://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’

已同步到看一看
贊(0)

分享創造快樂