前言:
我是一名開發者,一個iOS交流圈子的維護者,對于程序員來說,要學習的知識內容、技術有太多太多,要想不被環境淘汰就只有不斷提升自己,從來都是我們去適應環境,而不是環境來適應我們!
標語:不要浪費美好的年華,做自己覺得對的事情!
點贊的都今年發財啦
Object-C系列面試題
基礎題:
1.Objective-C的類可以多重繼承么?可以實現多個接口么?Category是什么?重寫一個類的方式用繼承好還是分類好?為什么?
Objective-C的類不可以多重繼承;
可以實現多個接口(協議);
Category是類別;
一般情況用分類好,用Category去重寫類的方法,僅對本Category有效,不會影響到其他類與原有類的關系。
2.請說明并比較以下關鍵詞:strong, weak, assign, copy。
* strong表示指向并擁有該對象。其修飾的對象引用計數會增加1。該對象只要引用計數不為0則不會被銷毀。當然強行將其設為nil可以銷毀它。
* weak表示指向但不擁有該對象。其修飾的對象引用計數不會增加。無需手動設置,該對象會自行在內存中銷毀。
* assign主要用于修飾基本數據類型,如NSInteger和CGFloat,這些數值主要存在于棧上。
* weak 一般用來修飾對象,assign一般用來修飾基本數據類型。原因是assign修飾的對象被釋放后,指針的地址依然存在,造成野指針,在堆上容易造成崩潰。而棧上的內存系統會自動處理,不會造成野指針。
* copy與strong類似。不同之處是strong的復制是多個指針指向同一個地址,而copy的復制每次會在內存中拷貝一份對象,指針指向不同地址。copy一般用在修飾有可變對應類型的不可變對象上,如NSString, NSArray, NSDictionary。
* Objective-C 中,基本數據類型的默認關鍵字是atomic, readwrite, assign;普通屬性的默認關鍵字是atomic, readwrite, strong。
3.用@property聲明的 NSString / NSArray / NSDictionary 經常使用 copy 關鍵字,為什么?如果改用strong關鍵字,可能造成什么問題?
用 @property 聲明 NSString、NSArray、NSDictionary 經常使用 copy 關鍵字,是因為他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進行賦值操作(就是把可變的賦值給不可變的),為確保對象中的字符串值不會無意間變動,應該在設置新屬性值時拷貝一份。
1. 因為父類指針可以指向子類對象,使用 copy 的目的是為了讓本對象的屬性不受外界影響,使用 copy 無論給我傳入是一個可變對象還是不可對象,我本身持有的就是一個不可變的副本。
2. 如果我們使用是 strong ,那么這個屬性就有可能指向一個可變對象,如果這個可變對象在外部被修改了,那么會影響該屬性。
//總結:使用copy的目的是,防止把可變類型的對象賦值給不可變類型的對象時,可變類型對象的值發送變化會無意間篡改不可變類型對象原來的值。
4.淺拷貝和深拷貝的區別?
淺拷貝:只復制指向對象的指針,而不復制引用對象本身。
深拷貝:復制引用對象本身。內存中存在了兩份獨立對象本身,當修改A時,A_copy不變。
5.Objective-C 如何對內存管理的,說說你的看法和解決方法?
Objective-C的內存管理主要有三種方式ARC(自動內存計數)、手動內存計數、內存池。
1). 自動內存計數ARC:由Xcode自動在App編譯階段,在代碼中添加內存管理代碼。
2). 手動內存計數MRC:遵循內存誰申請、誰釋放;誰添加,誰釋放的原則。
3). 內存釋放池Release Pool:把需要釋放的內存統一放在一個池子中,當池子被抽干后(drain),池子中所有的內存空間也被自動釋放掉。內存池的釋放操作分為自動和手動。自動釋放受runloop機制影響。
6.繼承、分類和類擴展
1\. 分類有名字,類擴展沒有分類名字,是一種特殊的分類。
2\. 分類只能擴展方法(屬性僅僅是聲明,并沒真正實現),類擴展可以擴展屬性、成員變量和方法。
3\. 繼承可以增加,修改或者刪除方法,并且可以增加屬性。
7.我們說的OC是動態運行時語言是什么意思?
主要是將數據類型的確定由編譯時,推遲到了運行時。簡單來說, 運行時機制使我們直到運行時才去決定一個對象的類別,以及調用該類別對象指定方法。
8.什么是 KVO 和 KVC?
1.KVC(Key-Value-Coding):鍵值編碼 是一種通過字符串間接訪問對象的方式(即給屬性賦值)
2.鍵值觀察機制 他提供了觀察某一屬性變化的方法,極大的簡化了代碼。KVO只能被KVC觸發,包括使用setValue:forKey:方法和點語法。
9.block的注意點
1. 在block內部使用外部指針且會造成循環引用情況下,需要用__week修飾外部指針:
__weak typeof(self) weakSelf = self;
2. 在block內部如果調用了延時函數還使用弱指針會取不到該指針,因為已經被銷毀了,需要在block內部再將弱指針重新強引用一下。
__strong typeof(self) strongSelf = weakSelf;
3. 如果需要在block內部改變外部棧區變量的話,需要在用__block修飾外部變量。
10.堆、棧和隊列
1.從管理方式來講
對于棧來講,是由編譯器自動管理,無需我們手工控制;
對于堆來說,釋放工作由程序員控制,容易產生內存泄露(memory leak)
2.從申請大小大小方面講
棧空間比較小
堆控件比較大
3.從數據存儲方面來講
棧空間中一般存儲基本類型,對象的地址
堆空間一般存放對象本身,block的copy等
# 堆
堆是一種經過排序的樹形數據結構,每個節點都有一個值,通常我們所說的堆的數據結構是指二叉樹。所以堆在數據結構中通常可以被看做是一棵樹的數組對象。而且堆需要滿足一下兩個性質:
1)堆中某個節點的值總是不大于或不小于其父節點的值;
2)堆總是一棵完全二叉樹。
堆分為兩種情況,有最大堆和最小堆。將根節點最大的堆叫做最大堆或大根堆,根節點最小的堆叫做最小堆或小根堆,在一個擺放好元素的最小堆中,父結點中的元素一定比子結點的元素要小,但對于左右結點的大小則沒有規定誰大誰小。
堆常用來實現優先隊列,堆的存取是隨意的,這就如同我們在圖書館的書架上取書,雖然書的擺放是有順序的,但是我們想取任意一本時不必像棧一樣,先取出前面所有的書,書架這種機制不同于箱子,我們可以直接取出我們想要的書。
# 棧
棧是限定僅在表尾進行插入和刪除操作的線性表。我們把允許插入和刪除的一端稱為棧頂,另一端稱為棧底,不含任何數據元素的棧稱為空棧。棧的特殊之處在于它限制了這個線性表的插入和刪除位置,它始終只在棧頂進行。
棧是一種具有后進先出的數據結構,又稱為后進先出的線性表,簡稱 LIFO(Last In First Out)結構。也就是說后存放的先取,先存放的后取,這就類似于我們要在取放在箱子底部的東西(放進去比較早的物體),我們首先要移開壓在它上面的物體(放進去比較晚的物體)。
堆棧中定義了一些操作。兩個最重要的是PUSH和POP。PUSH操作在堆棧的頂部加入一個元素。POP操作相反,在堆棧頂部移去一個元素,并將堆棧的大小減一。
棧的應用—遞歸
# 隊列
隊列是只允許在一端進行插入操作、而在另一端進行刪除操作的線性表。允許插入的一端稱為隊尾,允許刪除的一端稱為隊頭。它是一種特殊的線性表,特殊之處在于它只允許在表的前端進行刪除操作,而在表的后端進行插入操作,和棧一樣,隊列是一種操作受限制的線性表。
隊列是一種先進先出的數據結構,又稱為先進先出的線性表,簡稱 FIFO(First In First Out)結構。也就是說先放的先取,后放的后取,就如同行李過安檢的時候,先放進去的行李在另一端總是先出來,后放入的行李會在最后面出來。
11.常用的排序算法
選擇排序、冒泡排序、插入排序三種排序算法可以總結為如下:
都將數組分為已排序部分和未排序部分。
選擇排序將已排序部分定義在左端,然后選擇未排序部分的最小元素和未排序部分的第一個元素交換。
冒泡排序將已排序部分定義在右端,在遍歷未排序部分的過程執行交換,將最大元素交換到最右端。
插入排序將已排序部分定義在左端,將未排序部分元的第一個元素插入到已排序部分合適的位置。
/**
* 【選擇排序】:最值出現在起始端
*
* 第1趟:在n個數中找到最小(大)數與第一個數交換位置
* 第2趟:在剩下n-1個數中找到最小(大)數與第二個數交換位置
* 重復這樣的操作...依次與第三個、第四個...數交換位置
* 第n-1趟,最終可實現數據的升序(降序)排列。
*
*/
void selectSort(int *arr, int length) {
for (int i = 0; i < length - 1; i++) { //趟數
for (int j = i + 1; j < length; j++) { //比較次數
if (arr[i] > arr[j]) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
/**
* 【冒泡排序】:相鄰元素兩兩比較,比較完一趟,最值出現在末尾
* 第1趟:依次比較相鄰的兩個數,不斷交換(小數放前,大數放后)逐個推進,最值最后出現在第n個元素位置
* 第2趟:依次比較相鄰的兩個數,不斷交換(小數放前,大數放后)逐個推進,最值最后出現在第n-1個元素位置
* …… ……
* 第n-1趟:依次比較相鄰的兩個數,不斷交換(小數放前,大數放后)逐個推進,最值最后出現在第2個元素位置
*/
void bublleSort(int *arr, int length) {
for(int i = 0; i < length - 1; i++) { //趟數
for(int j = 0; j < length - i - 1; j++) { //比較次數
if(arr[j] > arr[j+1]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
/**
* 折半查找:優化查找時間(不用遍歷全部數據)
*
* 折半查找的原理:
* 1> 數組必須是有序的
* 2> 必須已知min和max(知道范圍)
* 3> 動態計算mid的值,取出mid對應的值進行比較
* 4> 如果mid對應的值大于要查找的值,那么max要變小為mid-1
* 5> 如果mid對應的值小于要查找的值,那么min要變大為mid+1
*
*/
// 已知一個有序數組, 和一個key, 要求從數組中找到key對應的索引位置
int findKey(int *arr, int length, int key) {
int min = 0, max = length - 1, mid;
while (min <= max) {
mid = (min + max) / 2; //計算中間值
if (key > arr[mid]) {
min = mid + 1;
} else if (key < arr[mid]) {
max = mid - 1;
} else {
return mid;
}
}
return -1;
}
12.UIView 和 CALayer 是什么關系?
UIView 繼承 UIResponder,而 UIResponder 是響應者對象,可以對iOS 中的事件響應及傳遞,CALayer 沒有繼承自 UIResponder,所以 CALayer 不具備響應處理事件的能力。CALayer 是 QuartzCore 中的類,是一個比較底層的用來繪制內容的類,用來繪制UI
UIView 對 CALayer 封裝屬性,對 UIView 設置 frame、center、bounds 等位置信息時,其實都是UIView 對 CALayer 進一層封裝,使得我們可以很方便地設置控件的位置;例如圓角、陰影等屬性, UIView 就沒有進一步封裝,所以我們還是需要去設置 Layer 的屬性來實現功能。
UIView 是 CALayer 的代理,UIView 持有一個 CALayer 的屬性,并且是該屬性的代理,用來提供一些 CALayer 行的數據,例如動畫和繪制。
13.說一下 JS 和 OC 互相調用的幾種方式?
js調用oc的三種方式:
根據網頁重定向截取字符串通過url scheme判斷
替換方法.context[@"copyText"]
注入對象:遵守協議NSExport,設置context[@
oc調用js代碼兩種方式
通過webVIew調用 webView stringByEvaluatingJavaScriptFromString: 調用
通過JSContext調用[context evaluateScript:];
14.Http 和 Https 的區別?Https為什么更加安全?
區別
1.HTTPS 需要向機構申請 CA 證書,極少免費。
2.HTTP 屬于明文傳輸,HTTPS基于 SSL 進行加密傳輸。
3.HTTP 端口號為 80,HTTPS 端口號為 443 。
4.HTTPS 是加密傳輸,有身份驗證的環節,更加安全。
安全
SSL(安全套接層) TLS(傳輸層安全)
以上兩者在傳輸層之上,對網絡連接進行加密處理,保障數據的完整性,更加的安全。
15.編程中的六大設計原則?
1.單一職責原則
通俗地講就是一個類只做一件事
CALayer:動畫和視圖的顯示。
UIView:只負責事件傳遞、事件響應。
2.開閉原則
對修改關閉,對擴展開放。 要考慮到后續的擴展性,而不是在原有的基礎上來回修改
3.接口隔離原則
使用多個專門的協議、而不是一個龐大臃腫的協議,如 UITableviewDelegate + UITableViewDataSource
4.依賴倒置原則
抽象不應該依賴于具體實現、具體實現可以依賴于抽象。 調用接口感覺不到內部是如何操作的
5.里氏替換原則
父類可以被子類無縫替換,且原有的功能不受任何影響 如:KVO
6.迪米特法則
一個對象應當對其他對象盡可能少的了解,實現高聚合、低耦合
16.Objective-C與Swift的異同?
1.1、swift和OC的共同點:
- OC出現過的絕大多數概念,比如引用計數、ARC(自動引用計數)、屬性、協議、接口、初始化、擴展類、命名參數、匿名函數等,在Swift中繼續有效(可能最多換個術語)。
- Swift和Objective-C共用一套運行時環境,Swift的類型可以橋接到Objective-C(下面我簡稱OC),反之亦然
1.2、swift的優點:
- swift注重安全,OC注重靈活
- swift注重面向協議編程、函數式編程、面向對象編程,OC注重面向對象編程
- swift注重值類型,OC注重指針和引用
- swift是靜態類型語言,OC是動態類型語言
- swift容易閱讀,文件結構和大部分語法簡易化,只有.swift文件,結尾不需要分號
- swift中的可選類型,是用于所有數據類型,而不僅僅局限于類。相比于OC中的nil更加安全和簡明
- swift中的泛型類型更加方便和通用,而非OC中只能為集合類型添加泛型
- swift中各種方便快捷的高階函數(函數式編程) (Swift的標準數組支持三個高階函數:map,filter和reduce,以及map的擴展flatMap)
- swift新增了兩種權限,細化權限。open > public > internal(默認) > fileprivate > private
- swift中獨有的元組類型(tuples),把多個值組合成復合值。元組內的值可以是任何類型,并不要求是相同類型的。
17.沙盒目錄結構是怎樣的?各自用于那些場景?
Application:存放程序源文件,上架前經過數字簽名,上架后不可修改
Documents:常用目錄,iCloud備份目錄,存放數據
Library
Caches:存放體積大又不需要備份的數據
Preference:設置目錄,iCloud會備份設置信息
tmp:存放臨時文件,不會被備份,而且這個文件下的數據有可能隨時被清除的可能
SDWebImage加載圖片過程
0、首先顯示占位圖
1、在webimagecache中尋找圖片對應的緩存,它是以url為數據索引先在內存中查找是否有緩存;
2、如果沒有緩存,就通過md5處理過的key來在磁盤中查找對應的數據,如果找到就會把磁盤中的數據加到內存中,并顯示出來;
3、如果內存和磁盤中都沒有找到,就會向遠程服務器發送請求,開始下載圖片;
4、下載完的圖片加入緩存中,并寫入到磁盤中;
5、整個獲取圖片的過程是在子線程中進行,在主線程中顯示。
AFNetworking 底層原理分析
AFNetworking是封裝的NSURLSession的網絡請求,由五個模塊組成:分別由NSURLSession,Security,Reachability,Serialization,UIKit五部分組成
NSURLSession:網絡通信模塊(核心模塊) 對應 AFNetworking中的 AFURLSessionManager和對HTTP協議進行特化處理的AFHTTPSessionManager,AFHTTPSessionManager是繼承于AFURLSessionmanager的
Security:網絡通訊安全策略模塊 對應 AFSecurityPolicy
Reachability:網絡狀態監聽模塊 對應AFNetworkReachabilityManager
Seriaalization:網絡通信信息序列化、反序列化模塊 對應 AFURLResponseSerialization
UIKit:對于iOS UIKit的擴展庫
進階題:
1.KVC的底層實現?
當一個對象調用setValue方法時,方法內部會做以下操作:
1). 檢查是否存在相應的key的set方法,如果存在,就調用set方法。
2). 如果set方法不存在,就會查找與key相同名稱并且帶下劃線的成員變量,如果有,則直接給成員變量屬性賦值。
3). 如果沒有找到_key,就會查找相同名稱的屬性key,如果有就直接賦值。
4). 如果還沒有找到,則調用valueForUndefinedKey:和setValue:forUndefinedKey:方法。
這些方法的默認實現都是拋出異常,我們可以根據需要重寫它們。
2.KVO的底層實現?
image
KVO-鍵值觀察機制,原理如下:
1.當給A類添加KVO的時候,runtime動態的生成了一個子類NSKVONotifying_A,讓A類的isa指針指向NSKVONotifying_A類,重寫class方法,隱藏對象真實類信息
2.重寫監聽屬性的setter方法,在setter方法內部調用了Foundation 的 _NSSetObjectValueAndNotify 函數
3._NSSetObjectValueAndNotify函數內部
a) 首先會調用 willChangeValueForKey
b) 然后給屬性賦值
c) 最后調用 didChangeValueForKey
d) 最后調用 observer 的 observeValueForKeyPath 去告訴監聽器屬性值發生了改變 .
4.重寫了dealloc做一些 KVO 內存釋放
3.說一下工作中你怎么做性能優化的
一般都是說關于tableView的優化處理,
造成tableView卡頓的原因
1.沒有使用cell的重用標識符,導致一直創建新的cell
2.cell的重新布局
3.沒有提前計算并緩存cell的屬性及內容
4.cell中控件的數量過多
5.使用了ClearColor,無背景色,透明度為0
6.更新只使用tableView.reloadData()(如果只是更新某組的話,使用reloadSection進行局部更新)
7.加載網絡數據,下載圖片,沒有使用異步加載,并緩存
8.使用addView 給cell動態添加view
9.沒有按需加載cell(cell滾動很快時,只加載范圍內的cell)
10.實現無用的代理方法(tableView只遵守兩個協議)
11.沒有做緩存行高(estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同時存在,這兩者同時存在才會出現“竄動”的bug。
建議是:只要是固定行高就寫預估行高來減少行高調用次數提升性能。如果是動態行高就不要寫預估方法了,用一個行高的緩存字典來減少代碼的調用次數即可)
12.做了多余的繪制工作(在實現drawRect:的時候,它的rect參數就是需要繪制的區域,這個區域之外的不需要進行繪制)
13.沒有預渲染圖像。(當新的圖像出現時,仍然會有短暫的停頓現象。解決的辦法就是在bitmap context里先將其畫一遍,導出成UIImage對象,然后再繪制到屏幕)
提升tableView的流暢度
*本質上是降低 CPU、GPU 的工作,從這兩個大的方面去提升性能。
1.CPU:對象的創建和銷毀、對象屬性的調整、布局計算、文本的計算和排版、圖片的格式轉換和解碼、圖像的繪制
2.GPU:紋理的渲染
卡頓優化在 CPU 層面
1.盡量用輕量級的對象,比如用不到事件處理的地方,可以考慮使用 CALayer 取代 UIView
2.不要頻繁地調用 UIView 的相關屬性,比如 frame、bounds、transform 等屬性,盡量減少不必要的修改
3.盡量提前計算好布局,在有需要時一次性調整對應的屬性,不要多次修改屬性
4.Autolayout 會比直接設置 frame 消耗更多的 CPU 資源
5.圖片的 size 最好剛好跟 UIImageView 的 size 保持一致
6.控制一下線程的最大并發數量
7.盡量把耗時的操作放到子線程
8.文本處理(尺寸計算、繪制)
9.圖片處理(解碼、繪制)
卡頓優化在 GPU層面
1.盡量避免短時間內大量圖片的顯示,盡可能將多張圖片合成一張進行顯示
2.GPU能處理的最大紋理尺寸是 4096x4096,一旦超過這個尺寸,就會占用 CPU 資源進行處理,所以紋理盡量不要超過這個尺寸
3.盡量減少視圖數量和層次
4.減少透明的視圖(alpha<1),不透明的就設置 opaque 為 YES
5.盡量避免出現離屏渲染
5.Runtime實現的機制是什么?能做什么事情呢?
runtime簡稱運行時。OC是運行時機制,也就是在運行時才做一些處理。例如:C語言在編譯的時候就知道要調用哪個方法函數,而OC在編譯的時候并不知道要調用哪個方法函數,只有在運行的時候才知道調用的方法函數名稱,來找到對應的方法函數進行調用。
1.發送消息
【場景:方法調用】
2.交換方法實現(交換系統的方法)
【場景:當第三方框架或者系統原生方法功能不能滿足我們的時候,我們可以在保持系統原有方法功能的基礎上,添加額外的功能。】
3.動態添加方法
【場景:如果一個類方法非常多,加載類到內存的時候也比較耗費資源,需要給每個方法生成映射表,可以使用動態給某個類,添加方法解決。】
4.利用關聯對象(AssociatedObject)給分類添加屬性
【
場景:分類是不能自定義屬性和變量的,這時候可以使用runtime動態添加屬性方法;
原理:給一個類聲明屬性,其實本質就是給這個類添加關聯,并不是直接把這個值的內存空間添加到類存空間。
】
5.遍歷類的所有成員變量
【
1.NSCoding自動歸檔解檔
場景:如果一個模型有許多個屬性,實現自定義模型數據持久化時,需要對每個屬性都實現一遍encodeObject 和 decodeObjectForKey方法,比較麻煩。我們可以使用Runtime來解決。
原理:用runtime提供的函數遍歷Model自身所有屬性,并對屬性進行encode和decode操作。
2.字典轉模型
原理:利用Runtime,遍歷模型中所有屬性,根據模型的屬性名,去字典中查找key,取出對應的值,給模型的屬性賦值。
3.修改textfield的占位文字顏色
】
6.利用消息轉發機制解決方法找不到的異常問題
6.iOS圖片設置圓角性能問題
1.直接使用setCornerRadius
【這樣設置會觸發離屏渲染,比較消耗性能。比如當一個頁面上有十幾頭像這樣設置了圓角會明顯感覺到卡頓。
注意:png圖片UIImageView處理圓角是不會產生離屏渲染的。(ios9.0之后不會離屏渲染,ios9.0之前還是會離屏渲染)
】
2.setCornerRadius設置圓角之后,shouldRasterize=YES光柵化
【avatarImageView.layer.shouldRasterize = YES;
avatarImageViewUrl.layer.rasterizationScale=[UIScreen mainScreen].scale; //UIImageView不加這句會產生一點模糊
shouldRasterize=YES設置光柵化,可以使離屏渲染的結果緩存到內存中存為位圖,
使用的時候直接使用緩存,節省了一直離屏渲染損耗的性能。
但是如果layer及sublayers常常改變的話,它就會一直不停的渲染及刪除緩存重新
創建緩存,所以這種情況下建議不要使用光柵化,這樣也是比較損耗性能的。
】
3.直接覆蓋一張中間為圓形透明的圖片(推薦使用)
4.UIImage drawInRect繪制圓角
【這種方式GPU損耗低內存占用大,而且UIButton上不知道怎么繪制,可以用
UIimageView添加個點擊手勢當做UIButton使用。】
5.SDWebImage處理圖片時Core Graphics繪制圓角(暫時感覺是最優方法)
7.什么是 RunLoop?
從字面上講就是運行循環,它內部就是do-while循環,在這個循環內部不斷地處理各種任務。
一個線程對應一個RunLoop,基本作用就是保持程序的持續運行,處理app中的各種事件。通過runloop,有事運行,沒事就休息,可以節省cpu資源,提高程序性能。
主線程的run loop默認是啟動的。iOS的應用程序里面,程序啟動后會有一個如下的main()函數
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
RunLoop,是多線程的法寶,即一個線程一次只能執行一個任務,執行完任務后就會退出線程。主線程執行完即時任務時會繼續等待接收事件而不退出。非主線程通常來說就是為了執行某一任務的,執行完畢就需要歸還資源,因此默認是不運行RunLoop的;
每一個線程都有其對應的RunLoop,只是默認只有主線程的RunLoop是啟動的,其它子線程的RunLoop默認是不啟動的,若要啟動則需要手動啟動;
在一個單獨的線程中,如果需要在處理完某個任務后不退出,繼續等待接收事件,則需要啟用RunLoop;
NSRunLoop提供了一個添加NSTimer的方法,可以指定Mode,如果要讓任何情況下都回調,則需要設置Mode為Common模式;
實質上,對于子線程的runloop默認是不存在的,因為蘋果采用了懶加載的方式。如果我們沒有手動調用[NSRunLoop currentRunLoop]的話,就不會去查詢是否存在當前線程的RunLoop,也就不會去加載,更不會創建。
8.以scheduledTimerWithTimeInterval的方式觸發的timer,在滑動頁面上的列表時,timer會暫停,為什么?該如何解決?
原因在于滑動時當前線程的runloop切換了mode用于列表滑動,導致timer暫停。
runloop中的mode主要用來指定事件在runloop中的優先級,有以下幾種:
* Default(NSDefaultRunLoopMode):默認,一般情況下使用;
* Connection(NSConnectionReplyMode):一般系統用來處理NSConnection相關事件,開發者一般用不到;
* Modal(NSModalPanelRunLoopMode):處理modal panels事件;
* Event Tracking(NSEventTrackingRunLoopMode):用于處理拖拽和用戶交互的模式。
* Common(NSRunloopCommonModes):模式合集。默認包括Default,Modal,Event Tracking三大模式,可以處理幾乎所有事件。
回到題中的情境。滑動列表時,runloop的mode由原來的Default模式切換到了Event Tracking模式,timer原來好好的運行在Default模式中,被關閉后自然就停止工作了。
解決方法其一是將timer加入到NSRunloopCommonModes中。其二是將timer放到另一個線程中,然后開啟另一個線程的runloop,這樣可以保證與主線程互不干擾,而現在主線程正在處理頁面滑動。
方法1
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
方法2
dispatch_async(dispatch_get_global_queue(0, 0), ^{
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(repeat:) userInfo:nil repeats:true];
[[NSRunLoop currentRunLoop] run];
});
9.進程與線程
進程:
1.進程是一個具有一定獨立功能的程序關于某次數據集合的一次運行活動,它是操作系統分配資源的基本單元.
2.進程是指在系統中正在運行的一個應用程序,就是一段程序的執行過程,我們可以理解為手機上的一個app.
3.每個進程之間是獨立的,每個進程均運行在其專用且受保護的內存空間內,擁有獨立運行所需的全部資源
線程
1.程序執行流的最小單元,線程是進程中的一個實體.
2.一個進程要想執行任務,必須至少有一條線程.應用程序啟動的時候,系統會默認開啟一條線程,也就是主線程
進程和線程的關系
1.線程是進程的執行單元,進程的所有任務都在線程中執行
2.線程是 CPU 分配資源和調度的最小單位
3.一個程序可以對應多個進程(多進程),一個進程中可有多個線程,但至少要有一條線程
4.同一個進程內的線程共享進程資源
10.iOS中實現多線程的幾種方案,各自有什么特點?講一下具體使用場景
NSThread 面向對象的,需要程序員手動創建線程,但不需要手動銷毀。子線程間通信很難。
GCD c語言,充分利用了設備的多核,自動管理線程生命周期。比NSOperation效率更高。
NSOperation 基于gcd封裝,更加面向對象,比gcd多了一些功能。
【場景:1.多個網絡請求完成后執行下一步 2.多個網絡請求順序執行后執行下一步 3.異步操作兩組數據時, 執行完第一組之后, 才能執行第二組】
11.對稱加密和非對稱加密的區別?
1、對稱加密又稱公開密鑰加密,加密和解密都會用到同一個密鑰,如果密鑰被攻擊者獲得,此時加密就失去了意義。常見的對稱加密算法有DES、3DES、AES、Blowfish、IDEA、RC5、RC6。
2、非對稱加密又稱共享密鑰加密,使用一對非對稱的密鑰,一把叫做私有密鑰,另一把叫做公有密鑰;公鑰加密只能用私鑰來解密,私鑰加密只能用公鑰來解密。常見的公鑰加密算法有:RSA、ElGamal、背包算法、Rabin(RSA的特例)、迪菲-赫爾曼密鑰交換協議中的公鑰加密算法、橢圓曲線加密算法)。
12.組件化有什么好處?
業務分層、解耦,使代碼變得可維護;
有效的拆分、組織日益龐大的工程代碼,使工程目錄變得可維護;
便于各業務功能拆分、抽離,實現真正的功能復用;
業務隔離,跨團隊開發代碼控制和版本風險控制的實現;
模塊化對代碼的封裝性、合理性都有一定的要求,提升開發同學的設計能力;
在維護好各級組件的情況下,隨意組合滿足不同客戶需求;(只需要將之前的多個業務組件模塊在新的主App中進行組裝即可快速迭代出下一個全新App)
13.你是如何組件化解耦的?
分層
基礎功能組件:按功能分庫,不涉及產品業務需求,跟庫Library類似,通過良好的接口拱上層業務組件調用;不寫入產品定制邏輯,通過擴展接口完成定制;
基礎UI組件:各個業務模塊依賴使用,但需要保持好定制擴展的設計
業務組件:業務功能間相對獨立,相互間沒有Model共享的依賴;業務之間的頁面調用只能通過UIBus進行跳轉;業務之間的邏輯Action調用只能通過服務提供;
中間件:target-action,url-block,protocol-class
14.APP啟動時間應從哪些方面優化?
App啟動時間可以通過xcode提供的工具來度量,在Xcode的Product->Scheme-->Edit Scheme->Run->Auguments中,將環境變量DYLD_PRINT_STATISTICS設為YES,優化需以下方面入手
dylib loading time
核心思想是減少dylibs的引用
合并現有的dylibs(最好是6個以內)
使用靜態庫
rebase/binding time
核心思想是減少DATA塊內的指針
減少Object C元數據量,減少Objc類數量,減少實例變量和函數(與面向對象設計思想沖突)
減少c++虛函數
多使用Swift結構體(推薦使用swift)
ObjC setup time
核心思想同上,這部分內容基本上在上一階段優化過后就不會太過耗時
initializer time
使用initialize替代load方法
減少使用c/c++的attribute((constructor));推薦使用dispatch_once() pthread_once() std:once()等方法
推薦使用swift
不要在初始化中調用dlopen()方法,因為加載過程是單線程,無鎖,如果調用dlopen則會變成多線程,會開啟鎖的消耗,同時有可能死鎖
不要在初始化中創建線程
Swift系列面試題總結
基礎題:
1.class 和 struct 的區別
struct是值類型,class是引用類型。
值類型的變量直接包含它們的數據,對于值類型都有它們自己的數據副本,因此對一個變量操作不可能影響另一個變量。
引用類型的變量存儲對他們的數據引用,因此后者稱為對象,因此對一個變量操作可能影響另一個變量所引用的對象。
二者的本質區別:struct是深拷貝,拷貝的是內容;class是淺拷貝,拷貝的是指針。
property的初始化不同:class 在初始化時不能直接把 property 放在 默認的constructor 的參數里,而是需要自己創建一個帶參數的constructor;而struct可以,把屬性放在默認的constructor 的參數里。
變量賦值方式不同:struct是值拷貝;class是引用拷貝。
immutable變量:swift的可變內容和不可變內容用var和let來甄別,如果初始為let的變量再去修改會發生編譯錯誤。struct遵循這一特性;class不存在這樣的問題。
mutating function: struct 和 class 的差別是 struct 的 function 要去改變 property 的值的時候要加上 mutating,而 class 不用。
繼承: struct不可以繼承,class可以繼承。
struct比class更輕量:struct分配在棧中,class分配在堆中。
2.Swift 是面向對象還是函數式的編程語言?
Swift 既是面向對象的,又是函數式的編程語言。
說 Swift 是面向對象的語言,是因為 Swift 支持類的封裝、繼承、和多態,從這點上來看與 Java 這類純面向對象的語言幾乎毫無差別。
說 Swift 是函數式編程語言,是因為 Swift 支持 map, reduce, filter, flatmap 這類去除中間狀態、數學函數式的方法,更加強調運算結果而不是中間過程。
3.什么是泛型,swift哪些地方使用了泛型?
泛型(generic)可以使我們在程序代碼中定義一些可變的部分,在運行的時候指定。使用泛型可以最大限度地重用代碼、保護類型的安全以及提高性能。
例如 optional 中的 map、flatMap 、?? (泛型加逃逸閉包的方式,做三目運算)
4.swift 語法糖 ? !的本質(實現原理)
?為optional的語法糖
optional<T> 是一個包含了nil 和普通類型的枚舉,確保使用者在變量為nil的情況下處理
!為optional 強制解包的語法糖
5.Optional(可選型) 是用什么實現的
Optional 是一個泛型枚舉
大致定義如下:
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
除了使用 let someValue: Int? = nil 之外, 還可以使用let optional1: Optional<Int> = nil 來定義
6.什么是高階函數
一個函數如果可以以某一個函數作為參數, 或者是返回值, 那么這個函數就稱之為高階函數, 如 map, reduce, filter
7.如何解決引用循環
轉換為值類型, 只有類會存在引用循環, 所以如果能不用類, 是可以解引用循環的,
delegate 使用 weak 屬性.
閉包中, 對有可能發生循環引用的對象, 使用 weak 或者 unowned, 修飾
8.定義靜態方法時關鍵字 static 和 class 有什么區別
static 定義的方法不可以被子類繼承, class 則可以
class AnotherClass {
static func staticMethod(){}
class func classMethod(){}
}
class ChildOfAnotherClass: AnotherClass {
override class func classMethod(){}
//override static func staticMethod(){}// error
}
9.請說明并比較以下關鍵詞:Open, Public, Internal, File-private, Private
Swift 有五個級別的訪問控制權限,從高到底依次為比如 Open, Public, Internal, File-private, Private。
他們遵循的基本原則是:高級別的變量不允許被定義為低級別變量的成員變量。比如一個 private 的 class 中不能含有 public 的 String。反之,低級別的變量卻可以定義在高級別的變量中。比如 public 的 class 中可以含有 private 的 Int。
Open 具備最高的訪問權限。其修飾的類和方法可以在任意 Module 中被訪問和重寫;它是 Swift 3 中新添加的訪問權限。
Public 的權限僅次于 Open。與 Open 唯一的區別在于它修飾的對象可以在任意 Module 中被訪問,但不能重寫。
Internal 是默認的權限。它表示只能在當前定義的 Module 中訪問和重寫,它可以被一個 Module 中的多個文件訪問,但不可以被其他的 Module 中被訪問。
File-private 也是 Swift 3 新添加的權限。其被修飾的對象只能在當前文件中被使用。例如它可以被一個文件中的 class,extension,struct 共同使用。
Private 是最低的訪問權限。它的對象只能在定義的作用域內使用。離開了這個作用域,即使是同一個文件中的其他作用域,也無法訪問。
10.swift中,關鍵字 guard 和 defer 的用法 guard也是基于一個表達式的布爾值去判斷一段代碼是否該被執行。與if語句不同的是,guard只有在條件不滿足的時候才會執行這段代碼。
guard let name = self.text else { return }
defer的用法是,這條語句并不會馬上執行,而是被推入棧中,直到函數結束時才再次被調用。
defer {
//函數結束才調用
}
這里也推薦一些面試相關的內容,祝各位網友都能拿到滿意offer!
GCD面試要點
block面試要點
Runtime面試要點
RunLoop面試要點
內存管理面試要點
MVC、MVVM面試要點
網絡性能優化面試要點
網絡編程面試要點
KVC&KVO面試要點
數據存儲面試要點
混編技術面試要點
設計模式面試要點
UI面試要點