前言:
我是一名開發(fā)者,一個(gè)iOS交流圈子的維護(hù)者,對于程序員來說,要學(xué)習(xí)的知識(shí)內(nèi)容、技術(shù)有太多太多,要想不被環(huán)境淘汰就只有不斷提升自己,從來都是我們?nèi)ミm應(yīng)環(huán)境,而不是環(huán)境來適應(yīng)我們!
標(biāo)語:不要浪費(fèi)美好的年華,做自己覺得對的事情!
點(diǎn)贊的都今年發(fā)財(cái)啦
Object-C系列面試題
基礎(chǔ)題:
1.Objective-C的類可以多重繼承么?可以實(shí)現(xiàn)多個(gè)接口么?Category是什么?重寫一個(gè)類的方式用繼承好還是分類好?為什么?
Objective-C的類不可以多重繼承;
可以實(shí)現(xiàn)多個(gè)接口(協(xié)議);
Category是類別;
一般情況用分類好,用Category去重寫類的方法,僅對本Category有效,不會(huì)影響到其他類與原有類的關(guān)系。
2.請說明并比較以下關(guān)鍵詞:strong, weak, assign, copy。
* strong表示指向并擁有該對象。其修飾的對象引用計(jì)數(shù)會(huì)增加1。該對象只要引用計(jì)數(shù)不為0則不會(huì)被銷毀。當(dāng)然強(qiáng)行將其設(shè)為nil可以銷毀它。
* weak表示指向但不擁有該對象。其修飾的對象引用計(jì)數(shù)不會(huì)增加。無需手動(dòng)設(shè)置,該對象會(huì)自行在內(nèi)存中銷毀。
* assign主要用于修飾基本數(shù)據(jù)類型,如NSInteger和CGFloat,這些數(shù)值主要存在于棧上。
* weak 一般用來修飾對象,assign一般用來修飾基本數(shù)據(jù)類型。原因是assign修飾的對象被釋放后,指針的地址依然存在,造成野指針,在堆上容易造成崩潰。而棧上的內(nèi)存系統(tǒng)會(huì)自動(dòng)處理,不會(huì)造成野指針。
* copy與strong類似。不同之處是strong的復(fù)制是多個(gè)指針指向同一個(gè)地址,而copy的復(fù)制每次會(huì)在內(nèi)存中拷貝一份對象,指針指向不同地址。copy一般用在修飾有可變對應(yīng)類型的不可變對象上,如NSString, NSArray, NSDictionary。
* Objective-C 中,基本數(shù)據(jù)類型的默認(rèn)關(guān)鍵字是atomic, readwrite, assign;普通屬性的默認(rèn)關(guān)鍵字是atomic, readwrite, strong。
3.用@property聲明的 NSString / NSArray / NSDictionary 經(jīng)常使用 copy 關(guān)鍵字,為什么?如果改用strong關(guān)鍵字,可能造成什么問題?
用 @property 聲明 NSString、NSArray、NSDictionary 經(jīng)常使用 copy 關(guān)鍵字,是因?yàn)樗麄冇袑?yīng)的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進(jìn)行賦值操作(就是把可變的賦值給不可變的),為確保對象中的字符串值不會(huì)無意間變動(dòng),應(yīng)該在設(shè)置新屬性值時(shí)拷貝一份。
1. 因?yàn)楦割愔羔樋梢灾赶蜃宇悓ο?使用 copy 的目的是為了讓本對象的屬性不受外界影響,使用 copy 無論給我傳入是一個(gè)可變對象還是不可對象,我本身持有的就是一個(gè)不可變的副本。
2. 如果我們使用是 strong ,那么這個(gè)屬性就有可能指向一個(gè)可變對象,如果這個(gè)可變對象在外部被修改了,那么會(huì)影響該屬性。
//總結(jié):使用copy的目的是,防止把可變類型的對象賦值給不可變類型的對象時(shí),可變類型對象的值發(fā)送變化會(huì)無意間篡改不可變類型對象原來的值。
4.淺拷貝和深拷貝的區(qū)別?
淺拷貝:只復(fù)制指向?qū)ο蟮闹羔?,而不?fù)制引用對象本身。
深拷貝:復(fù)制引用對象本身。內(nèi)存中存在了兩份獨(dú)立對象本身,當(dāng)修改A時(shí),A_copy不變。
5.Objective-C 如何對內(nèi)存管理的,說說你的看法和解決方法?
Objective-C的內(nèi)存管理主要有三種方式ARC(自動(dòng)內(nèi)存計(jì)數(shù))、手動(dòng)內(nèi)存計(jì)數(shù)、內(nèi)存池。
1). 自動(dòng)內(nèi)存計(jì)數(shù)ARC:由Xcode自動(dòng)在App編譯階段,在代碼中添加內(nèi)存管理代碼。
2). 手動(dòng)內(nèi)存計(jì)數(shù)MRC:遵循內(nèi)存誰申請、誰釋放;誰添加,誰釋放的原則。
3). 內(nèi)存釋放池Release Pool:把需要釋放的內(nèi)存統(tǒng)一放在一個(gè)池子中,當(dāng)池子被抽干后(drain),池子中所有的內(nèi)存空間也被自動(dòng)釋放掉。內(nèi)存池的釋放操作分為自動(dòng)和手動(dòng)。自動(dòng)釋放受runloop機(jī)制影響。
6.繼承、分類和類擴(kuò)展
1\. 分類有名字,類擴(kuò)展沒有分類名字,是一種特殊的分類。
2\. 分類只能擴(kuò)展方法(屬性僅僅是聲明,并沒真正實(shí)現(xiàn)),類擴(kuò)展可以擴(kuò)展屬性、成員變量和方法。
3\. 繼承可以增加,修改或者刪除方法,并且可以增加屬性。
7.我們說的OC是動(dòng)態(tài)運(yùn)行時(shí)語言是什么意思?
主要是將數(shù)據(jù)類型的確定由編譯時(shí),推遲到了運(yùn)行時(shí)。簡單來說, 運(yùn)行時(shí)機(jī)制使我們直到運(yùn)行時(shí)才去決定一個(gè)對象的類別,以及調(diào)用該類別對象指定方法。
8.什么是 KVO 和 KVC?
1.KVC(Key-Value-Coding):鍵值編碼 是一種通過字符串間接訪問對象的方式(即給屬性賦值)
2.鍵值觀察機(jī)制 他提供了觀察某一屬性變化的方法,極大的簡化了代碼。KVO只能被KVC觸發(fā),包括使用setValue:forKey:方法和點(diǎn)語法。
9.block的注意點(diǎn)
1. 在block內(nèi)部使用外部指針且會(huì)造成循環(huán)引用情況下,需要用__week修飾外部指針:
__weak typeof(self) weakSelf = self;
2. 在block內(nèi)部如果調(diào)用了延時(shí)函數(shù)還使用弱指針會(huì)取不到該指針,因?yàn)橐呀?jīng)被銷毀了,需要在block內(nèi)部再將弱指針重新強(qiáng)引用一下。
__strong typeof(self) strongSelf = weakSelf;
3. 如果需要在block內(nèi)部改變外部棧區(qū)變量的話,需要在用__block修飾外部變量。
10.堆、棧和隊(duì)列
1.從管理方式來講
對于棧來講,是由編譯器自動(dòng)管理,無需我們手工控制;
對于堆來說,釋放工作由程序員控制,容易產(chǎn)生內(nèi)存泄露(memory leak)
2.從申請大小大小方面講
??臻g比較小
堆控件比較大
3.從數(shù)據(jù)存儲(chǔ)方面來講
??臻g中一般存儲(chǔ)基本類型,對象的地址
堆空間一般存放對象本身,block的copy等
# 堆
堆是一種經(jīng)過排序的樹形數(shù)據(jù)結(jié)構(gòu),每個(gè)節(jié)點(diǎn)都有一個(gè)值,通常我們所說的堆的數(shù)據(jù)結(jié)構(gòu)是指二叉樹。所以堆在數(shù)據(jù)結(jié)構(gòu)中通??梢员豢醋鍪且豢脴涞臄?shù)組對象。而且堆需要滿足一下兩個(gè)性質(zhì):
1)堆中某個(gè)節(jié)點(diǎn)的值總是不大于或不小于其父節(jié)點(diǎn)的值;
2)堆總是一棵完全二叉樹。
堆分為兩種情況,有最大堆和最小堆。將根節(jié)點(diǎn)最大的堆叫做最大堆或大根堆,根節(jié)點(diǎn)最小的堆叫做最小堆或小根堆,在一個(gè)擺放好元素的最小堆中,父結(jié)點(diǎn)中的元素一定比子結(jié)點(diǎn)的元素要小,但對于左右結(jié)點(diǎn)的大小則沒有規(guī)定誰大誰小。
堆常用來實(shí)現(xiàn)優(yōu)先隊(duì)列,堆的存取是隨意的,這就如同我們在圖書館的書架上取書,雖然書的擺放是有順序的,但是我們想取任意一本時(shí)不必像棧一樣,先取出前面所有的書,書架這種機(jī)制不同于箱子,我們可以直接取出我們想要的書。
# 棧
棧是限定僅在表尾進(jìn)行插入和刪除操作的線性表。我們把允許插入和刪除的一端稱為棧頂,另一端稱為棧底,不含任何數(shù)據(jù)元素的棧稱為空棧。棧的特殊之處在于它限制了這個(gè)線性表的插入和刪除位置,它始終只在棧頂進(jìn)行。
棧是一種具有后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),又稱為后進(jìn)先出的線性表,簡稱 LIFO(Last In First Out)結(jié)構(gòu)。也就是說后存放的先取,先存放的后取,這就類似于我們要在取放在箱子底部的東西(放進(jìn)去比較早的物體),我們首先要移開壓在它上面的物體(放進(jìn)去比較晚的物體)。
堆棧中定義了一些操作。兩個(gè)最重要的是PUSH和POP。PUSH操作在堆棧的頂部加入一個(gè)元素。POP操作相反,在堆棧頂部移去一個(gè)元素,并將堆棧的大小減一。
棧的應(yīng)用—遞歸
# 隊(duì)列
隊(duì)列是只允許在一端進(jìn)行插入操作、而在另一端進(jìn)行刪除操作的線性表。允許插入的一端稱為隊(duì)尾,允許刪除的一端稱為隊(duì)頭。它是一種特殊的線性表,特殊之處在于它只允許在表的前端進(jìn)行刪除操作,而在表的后端進(jìn)行插入操作,和棧一樣,隊(duì)列是一種操作受限制的線性表。
隊(duì)列是一種先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),又稱為先進(jìn)先出的線性表,簡稱 FIFO(First In First Out)結(jié)構(gòu)。也就是說先放的先取,后放的后取,就如同行李過安檢的時(shí)候,先放進(jìn)去的行李在另一端總是先出來,后放入的行李會(huì)在最后面出來。
11.常用的排序算法
選擇排序、冒泡排序、插入排序三種排序算法可以總結(jié)為如下:
都將數(shù)組分為已排序部分和未排序部分。
選擇排序?qū)⒁雅判虿糠侄x在左端,然后選擇未排序部分的最小元素和未排序部分的第一個(gè)元素交換。
冒泡排序?qū)⒁雅判虿糠侄x在右端,在遍歷未排序部分的過程執(zhí)行交換,將最大元素交換到最右端。
插入排序?qū)⒁雅判虿糠侄x在左端,將未排序部分元的第一個(gè)元素插入到已排序部分合適的位置。
/**
* 【選擇排序】:最值出現(xiàn)在起始端
*
* 第1趟:在n個(gè)數(shù)中找到最小(大)數(shù)與第一個(gè)數(shù)交換位置
* 第2趟:在剩下n-1個(gè)數(shù)中找到最小(大)數(shù)與第二個(gè)數(shù)交換位置
* 重復(fù)這樣的操作...依次與第三個(gè)、第四個(gè)...數(shù)交換位置
* 第n-1趟,最終可實(shí)現(xiàn)數(shù)據(jù)的升序(降序)排列。
*
*/
void selectSort(int *arr, int length) {
for (int i = 0; i < length - 1; i++) { //趟數(shù)
for (int j = i + 1; j < length; j++) { //比較次數(shù)
if (arr[i] > arr[j]) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
/**
* 【冒泡排序】:相鄰元素兩兩比較,比較完一趟,最值出現(xiàn)在末尾
* 第1趟:依次比較相鄰的兩個(gè)數(shù),不斷交換(小數(shù)放前,大數(shù)放后)逐個(gè)推進(jìn),最值最后出現(xiàn)在第n個(gè)元素位置
* 第2趟:依次比較相鄰的兩個(gè)數(shù),不斷交換(小數(shù)放前,大數(shù)放后)逐個(gè)推進(jìn),最值最后出現(xiàn)在第n-1個(gè)元素位置
* …… ……
* 第n-1趟:依次比較相鄰的兩個(gè)數(shù),不斷交換(小數(shù)放前,大數(shù)放后)逐個(gè)推進(jìn),最值最后出現(xiàn)在第2個(gè)元素位置
*/
void bublleSort(int *arr, int length) {
for(int i = 0; i < length - 1; i++) { //趟數(shù)
for(int j = 0; j < length - i - 1; j++) { //比較次數(shù)
if(arr[j] > arr[j+1]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
/**
* 折半查找:優(yōu)化查找時(shí)間(不用遍歷全部數(shù)據(jù))
*
* 折半查找的原理:
* 1> 數(shù)組必須是有序的
* 2> 必須已知min和max(知道范圍)
* 3> 動(dòng)態(tài)計(jì)算mid的值,取出mid對應(yīng)的值進(jìn)行比較
* 4> 如果mid對應(yīng)的值大于要查找的值,那么max要變小為mid-1
* 5> 如果mid對應(yīng)的值小于要查找的值,那么min要變大為mid+1
*
*/
// 已知一個(gè)有序數(shù)組, 和一個(gè)key, 要求從數(shù)組中找到key對應(yīng)的索引位置
int findKey(int *arr, int length, int key) {
int min = 0, max = length - 1, mid;
while (min <= max) {
mid = (min + max) / 2; //計(jì)算中間值
if (key > arr[mid]) {
min = mid + 1;
} else if (key < arr[mid]) {
max = mid - 1;
} else {
return mid;
}
}
return -1;
}
12.UIView 和 CALayer 是什么關(guān)系?
UIView 繼承 UIResponder,而 UIResponder 是響應(yīng)者對象,可以對iOS 中的事件響應(yīng)及傳遞,CALayer 沒有繼承自 UIResponder,所以 CALayer 不具備響應(yīng)處理事件的能力。CALayer 是 QuartzCore 中的類,是一個(gè)比較底層的用來繪制內(nèi)容的類,用來繪制UI
UIView 對 CALayer 封裝屬性,對 UIView 設(shè)置 frame、center、bounds 等位置信息時(shí),其實(shí)都是UIView 對 CALayer 進(jìn)一層封裝,使得我們可以很方便地設(shè)置控件的位置;例如圓角、陰影等屬性, UIView 就沒有進(jìn)一步封裝,所以我們還是需要去設(shè)置 Layer 的屬性來實(shí)現(xiàn)功能。
UIView 是 CALayer 的代理,UIView 持有一個(gè) CALayer 的屬性,并且是該屬性的代理,用來提供一些 CALayer 行的數(shù)據(jù),例如動(dòng)畫和繪制。
13.說一下 JS 和 OC 互相調(diào)用的幾種方式?
js調(diào)用oc的三種方式:
根據(jù)網(wǎng)頁重定向截取字符串通過url scheme判斷
替換方法.context[@"copyText"]
注入對象:遵守協(xié)議NSExport,設(shè)置context[@
oc調(diào)用js代碼兩種方式
通過webVIew調(diào)用 webView stringByEvaluatingJavaScriptFromString: 調(diào)用
通過JSContext調(diào)用[context evaluateScript:];
14.Http 和 Https 的區(qū)別?Https為什么更加安全?
區(qū)別
1.HTTPS 需要向機(jī)構(gòu)申請 CA 證書,極少免費(fèi)。
2.HTTP 屬于明文傳輸,HTTPS基于 SSL 進(jìn)行加密傳輸。
3.HTTP 端口號(hào)為 80,HTTPS 端口號(hào)為 443 。
4.HTTPS 是加密傳輸,有身份驗(yàn)證的環(huán)節(jié),更加安全。
安全
SSL(安全套接層) TLS(傳輸層安全)
以上兩者在傳輸層之上,對網(wǎng)絡(luò)連接進(jìn)行加密處理,保障數(shù)據(jù)的完整性,更加的安全。
15.編程中的六大設(shè)計(jì)原則?
1.單一職責(zé)原則
通俗地講就是一個(gè)類只做一件事
CALayer:動(dòng)畫和視圖的顯示。
UIView:只負(fù)責(zé)事件傳遞、事件響應(yīng)。
2.開閉原則
對修改關(guān)閉,對擴(kuò)展開放。 要考慮到后續(xù)的擴(kuò)展性,而不是在原有的基礎(chǔ)上來回修改
3.接口隔離原則
使用多個(gè)專門的協(xié)議、而不是一個(gè)龐大臃腫的協(xié)議,如 UITableviewDelegate + UITableViewDataSource
4.依賴倒置原則
抽象不應(yīng)該依賴于具體實(shí)現(xiàn)、具體實(shí)現(xiàn)可以依賴于抽象。 調(diào)用接口感覺不到內(nèi)部是如何操作的
5.里氏替換原則
父類可以被子類無縫替換,且原有的功能不受任何影響 如:KVO
6.迪米特法則
一個(gè)對象應(yīng)當(dāng)對其他對象盡可能少的了解,實(shí)現(xiàn)高聚合、低耦合
16.Objective-C與Swift的異同?
1.1、swift和OC的共同點(diǎn):
- OC出現(xiàn)過的絕大多數(shù)概念,比如引用計(jì)數(shù)、ARC(自動(dòng)引用計(jì)數(shù))、屬性、協(xié)議、接口、初始化、擴(kuò)展類、命名參數(shù)、匿名函數(shù)等,在Swift中繼續(xù)有效(可能最多換個(gè)術(shù)語)。
- Swift和Objective-C共用一套運(yùn)行時(shí)環(huán)境,Swift的類型可以橋接到Objective-C(下面我簡稱OC),反之亦然
1.2、swift的優(yōu)點(diǎn):
- swift注重安全,OC注重靈活
- swift注重面向協(xié)議編程、函數(shù)式編程、面向?qū)ο缶幊蹋琌C注重面向?qū)ο缶幊?
- swift注重值類型,OC注重指針和引用
- swift是靜態(tài)類型語言,OC是動(dòng)態(tài)類型語言
- swift容易閱讀,文件結(jié)構(gòu)和大部分語法簡易化,只有.swift文件,結(jié)尾不需要分號(hào)
- swift中的可選類型,是用于所有數(shù)據(jù)類型,而不僅僅局限于類。相比于OC中的nil更加安全和簡明
- swift中的泛型類型更加方便和通用,而非OC中只能為集合類型添加泛型
- swift中各種方便快捷的高階函數(shù)(函數(shù)式編程) (Swift的標(biāo)準(zhǔn)數(shù)組支持三個(gè)高階函數(shù):map,filter和reduce,以及map的擴(kuò)展flatMap)
- swift新增了兩種權(quán)限,細(xì)化權(quán)限。open > public > internal(默認(rèn)) > fileprivate > private
- swift中獨(dú)有的元組類型(tuples),把多個(gè)值組合成復(fù)合值。元組內(nèi)的值可以是任何類型,并不要求是相同類型的。
17.沙盒目錄結(jié)構(gòu)是怎樣的?各自用于那些場景?
Application:存放程序源文件,上架前經(jīng)過數(shù)字簽名,上架后不可修改
Documents:常用目錄,iCloud備份目錄,存放數(shù)據(jù)
Library
Caches:存放體積大又不需要備份的數(shù)據(jù)
Preference:設(shè)置目錄,iCloud會(huì)備份設(shè)置信息
tmp:存放臨時(shí)文件,不會(huì)被備份,而且這個(gè)文件下的數(shù)據(jù)有可能隨時(shí)被清除的可能
SDWebImage加載圖片過程
0、首先顯示占位圖
1、在webimagecache中尋找圖片對應(yīng)的緩存,它是以u(píng)rl為數(shù)據(jù)索引先在內(nèi)存中查找是否有緩存;
2、如果沒有緩存,就通過md5處理過的key來在磁盤中查找對應(yīng)的數(shù)據(jù),如果找到就會(huì)把磁盤中的數(shù)據(jù)加到內(nèi)存中,并顯示出來;
3、如果內(nèi)存和磁盤中都沒有找到,就會(huì)向遠(yuǎn)程服務(wù)器發(fā)送請求,開始下載圖片;
4、下載完的圖片加入緩存中,并寫入到磁盤中;
5、整個(gè)獲取圖片的過程是在子線程中進(jìn)行,在主線程中顯示。
AFNetworking 底層原理分析
AFNetworking是封裝的NSURLSession的網(wǎng)絡(luò)請求,由五個(gè)模塊組成:分別由NSURLSession,Security,Reachability,Serialization,UIKit五部分組成
NSURLSession:網(wǎng)絡(luò)通信模塊(核心模塊) 對應(yīng) AFNetworking中的 AFURLSessionManager和對HTTP協(xié)議進(jìn)行特化處理的AFHTTPSessionManager,AFHTTPSessionManager是繼承于AFURLSessionmanager的
Security:網(wǎng)絡(luò)通訊安全策略模塊 對應(yīng) AFSecurityPolicy
Reachability:網(wǎng)絡(luò)狀態(tài)監(jiān)聽模塊 對應(yīng)AFNetworkReachabilityManager
Seriaalization:網(wǎng)絡(luò)通信信息序列化、反序列化模塊 對應(yīng) AFURLResponseSerialization
UIKit:對于iOS UIKit的擴(kuò)展庫
進(jìn)階題:
1.KVC的底層實(shí)現(xiàn)?
當(dāng)一個(gè)對象調(diào)用setValue方法時(shí),方法內(nèi)部會(huì)做以下操作:
1). 檢查是否存在相應(yīng)的key的set方法,如果存在,就調(diào)用set方法。
2). 如果set方法不存在,就會(huì)查找與key相同名稱并且?guī)聞澗€的成員變量,如果有,則直接給成員變量屬性賦值。
3). 如果沒有找到_key,就會(huì)查找相同名稱的屬性key,如果有就直接賦值。
4). 如果還沒有找到,則調(diào)用valueForUndefinedKey:和setValue:forUndefinedKey:方法。
這些方法的默認(rèn)實(shí)現(xiàn)都是拋出異常,我們可以根據(jù)需要重寫它們。
2.KVO的底層實(shí)現(xiàn)?

KVO-鍵值觀察機(jī)制,原理如下:
1.當(dāng)給A類添加KVO的時(shí)候,runtime動(dòng)態(tài)的生成了一個(gè)子類NSKVONotifying_A,讓A類的isa指針指向NSKVONotifying_A類,重寫class方法,隱藏對象真實(shí)類信息
2.重寫監(jiān)聽屬性的setter方法,在setter方法內(nèi)部調(diào)用了Foundation 的 _NSSetObjectValueAndNotify 函數(shù)
3._NSSetObjectValueAndNotify函數(shù)內(nèi)部
a) 首先會(huì)調(diào)用 willChangeValueForKey
b) 然后給屬性賦值
c) 最后調(diào)用 didChangeValueForKey
d) 最后調(diào)用 observer 的 observeValueForKeyPath 去告訴監(jiān)聽器屬性值發(fā)生了改變 .
4.重寫了dealloc做一些 KVO 內(nèi)存釋放
3.說一下工作中你怎么做性能優(yōu)化的
一般都是說關(guān)于tableView的優(yōu)化處理,
造成tableView卡頓的原因
1.沒有使用cell的重用標(biāo)識(shí)符,導(dǎo)致一直創(chuàng)建新的cell
2.cell的重新布局
3.沒有提前計(jì)算并緩存cell的屬性及內(nèi)容
4.cell中控件的數(shù)量過多
5.使用了ClearColor,無背景色,透明度為0
6.更新只使用tableView.reloadData()(如果只是更新某組的話,使用reloadSection進(jìn)行局部更新)
7.加載網(wǎng)絡(luò)數(shù)據(jù),下載圖片,沒有使用異步加載,并緩存
8.使用addView 給cell動(dòng)態(tài)添加view
9.沒有按需加載cell(cell滾動(dòng)很快時(shí),只加載范圍內(nèi)的cell)
10.實(shí)現(xiàn)無用的代理方法(tableView只遵守兩個(gè)協(xié)議)
11.沒有做緩存行高(estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同時(shí)存在,這兩者同時(shí)存在才會(huì)出現(xiàn)“竄動(dòng)”的bug。
建議是:只要是固定行高就寫預(yù)估行高來減少行高調(diào)用次數(shù)提升性能。如果是動(dòng)態(tài)行高就不要寫預(yù)估方法了,用一個(gè)行高的緩存字典來減少代碼的調(diào)用次數(shù)即可)
12.做了多余的繪制工作(在實(shí)現(xiàn)drawRect:的時(shí)候,它的rect參數(shù)就是需要繪制的區(qū)域,這個(gè)區(qū)域之外的不需要進(jìn)行繪制)
13.沒有預(yù)渲染圖像。(當(dāng)新的圖像出現(xiàn)時(shí),仍然會(huì)有短暫的停頓現(xiàn)象。解決的辦法就是在bitmap context里先將其畫一遍,導(dǎo)出成UIImage對象,然后再繪制到屏幕)
提升tableView的流暢度
*本質(zhì)上是降低 CPU、GPU 的工作,從這兩個(gè)大的方面去提升性能。
1.CPU:對象的創(chuàng)建和銷毀、對象屬性的調(diào)整、布局計(jì)算、文本的計(jì)算和排版、圖片的格式轉(zhuǎn)換和解碼、圖像的繪制
2.GPU:紋理的渲染
卡頓優(yōu)化在 CPU 層面
1.盡量用輕量級(jí)的對象,比如用不到事件處理的地方,可以考慮使用 CALayer 取代 UIView
2.不要頻繁地調(diào)用 UIView 的相關(guān)屬性,比如 frame、bounds、transform 等屬性,盡量減少不必要的修改
3.盡量提前計(jì)算好布局,在有需要時(shí)一次性調(diào)整對應(yīng)的屬性,不要多次修改屬性
4.Autolayout 會(huì)比直接設(shè)置 frame 消耗更多的 CPU 資源
5.圖片的 size 最好剛好跟 UIImageView 的 size 保持一致
6.控制一下線程的最大并發(fā)數(shù)量
7.盡量把耗時(shí)的操作放到子線程
8.文本處理(尺寸計(jì)算、繪制)
9.圖片處理(解碼、繪制)
卡頓優(yōu)化在 GPU層面
1.盡量避免短時(shí)間內(nèi)大量圖片的顯示,盡可能將多張圖片合成一張進(jìn)行顯示
2.GPU能處理的最大紋理尺寸是 4096x4096,一旦超過這個(gè)尺寸,就會(huì)占用 CPU 資源進(jìn)行處理,所以紋理盡量不要超過這個(gè)尺寸
3.盡量減少視圖數(shù)量和層次
4.減少透明的視圖(alpha<1),不透明的就設(shè)置 opaque 為 YES
5.盡量避免出現(xiàn)離屏渲染
5.Runtime實(shí)現(xiàn)的機(jī)制是什么?能做什么事情呢?
runtime簡稱運(yùn)行時(shí)。OC是運(yùn)行時(shí)機(jī)制,也就是在運(yùn)行時(shí)才做一些處理。例如:C語言在編譯的時(shí)候就知道要調(diào)用哪個(gè)方法函數(shù),而OC在編譯的時(shí)候并不知道要調(diào)用哪個(gè)方法函數(shù),只有在運(yùn)行的時(shí)候才知道調(diào)用的方法函數(shù)名稱,來找到對應(yīng)的方法函數(shù)進(jìn)行調(diào)用。
1.發(fā)送消息
【場景:方法調(diào)用】
2.交換方法實(shí)現(xiàn)(交換系統(tǒng)的方法)
【場景:當(dāng)?shù)谌娇蚣芑蛘呦到y(tǒng)原生方法功能不能滿足我們的時(shí)候,我們可以在保持系統(tǒng)原有方法功能的基礎(chǔ)上,添加額外的功能?!?
3.動(dòng)態(tài)添加方法
【場景:如果一個(gè)類方法非常多,加載類到內(nèi)存的時(shí)候也比較耗費(fèi)資源,需要給每個(gè)方法生成映射表,可以使用動(dòng)態(tài)給某個(gè)類,添加方法解決。】
4.利用關(guān)聯(lián)對象(AssociatedObject)給分類添加屬性
【
場景:分類是不能自定義屬性和變量的,這時(shí)候可以使用runtime動(dòng)態(tài)添加屬性方法;
原理:給一個(gè)類聲明屬性,其實(shí)本質(zhì)就是給這個(gè)類添加關(guān)聯(lián),并不是直接把這個(gè)值的內(nèi)存空間添加到類存空間。
】
5.遍歷類的所有成員變量
【
1.NSCoding自動(dòng)歸檔解檔
場景:如果一個(gè)模型有許多個(gè)屬性,實(shí)現(xiàn)自定義模型數(shù)據(jù)持久化時(shí),需要對每個(gè)屬性都實(shí)現(xiàn)一遍encodeObject 和 decodeObjectForKey方法,比較麻煩。我們可以使用Runtime來解決。
原理:用runtime提供的函數(shù)遍歷Model自身所有屬性,并對屬性進(jìn)行encode和decode操作。
2.字典轉(zhuǎn)模型
原理:利用Runtime,遍歷模型中所有屬性,根據(jù)模型的屬性名,去字典中查找key,取出對應(yīng)的值,給模型的屬性賦值。
3.修改textfield的占位文字顏色
】
6.利用消息轉(zhuǎn)發(fā)機(jī)制解決方法找不到的異常問題
6.iOS圖片設(shè)置圓角性能問題
1.直接使用setCornerRadius
【這樣設(shè)置會(huì)觸發(fā)離屏渲染,比較消耗性能。比如當(dāng)一個(gè)頁面上有十幾頭像這樣設(shè)置了圓角會(huì)明顯感覺到卡頓。
注意:png圖片UIImageView處理圓角是不會(huì)產(chǎn)生離屏渲染的。(ios9.0之后不會(huì)離屏渲染,ios9.0之前還是會(huì)離屏渲染)
】
2.setCornerRadius設(shè)置圓角之后,shouldRasterize=YES光柵化
【avatarImageView.layer.shouldRasterize = YES;
avatarImageViewUrl.layer.rasterizationScale=[UIScreen mainScreen].scale; //UIImageView不加這句會(huì)產(chǎn)生一點(diǎn)模糊
shouldRasterize=YES設(shè)置光柵化,可以使離屏渲染的結(jié)果緩存到內(nèi)存中存為位圖,
使用的時(shí)候直接使用緩存,節(jié)省了一直離屏渲染損耗的性能。
但是如果layer及sublayers常常改變的話,它就會(huì)一直不停的渲染及刪除緩存重新
創(chuàng)建緩存,所以這種情況下建議不要使用光柵化,這樣也是比較損耗性能的。
】
3.直接覆蓋一張中間為圓形透明的圖片(推薦使用)
4.UIImage drawInRect繪制圓角
【這種方式GPU損耗低內(nèi)存占用大,而且UIButton上不知道怎么繪制,可以用
UIimageView添加個(gè)點(diǎn)擊手勢當(dāng)做UIButton使用?!?
5.SDWebImage處理圖片時(shí)Core Graphics繪制圓角(暫時(shí)感覺是最優(yōu)方法)
7.什么是 RunLoop?
從字面上講就是運(yùn)行循環(huán),它內(nèi)部就是do-while循環(huán),在這個(gè)循環(huán)內(nèi)部不斷地處理各種任務(wù)。
一個(gè)線程對應(yīng)一個(gè)RunLoop,基本作用就是保持程序的持續(xù)運(yùn)行,處理app中的各種事件。通過runloop,有事運(yùn)行,沒事就休息,可以節(jié)省cpu資源,提高程序性能。
主線程的run loop默認(rèn)是啟動(dòng)的。iOS的應(yīng)用程序里面,程序啟動(dòng)后會(huì)有一個(gè)如下的main()函數(shù)
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
RunLoop,是多線程的法寶,即一個(gè)線程一次只能執(zhí)行一個(gè)任務(wù),執(zhí)行完任務(wù)后就會(huì)退出線程。主線程執(zhí)行完即時(shí)任務(wù)時(shí)會(huì)繼續(xù)等待接收事件而不退出。非主線程通常來說就是為了執(zhí)行某一任務(wù)的,執(zhí)行完畢就需要?dú)w還資源,因此默認(rèn)是不運(yùn)行RunLoop的;
每一個(gè)線程都有其對應(yīng)的RunLoop,只是默認(rèn)只有主線程的RunLoop是啟動(dòng)的,其它子線程的RunLoop默認(rèn)是不啟動(dòng)的,若要啟動(dòng)則需要手動(dòng)啟動(dòng);
在一個(gè)單獨(dú)的線程中,如果需要在處理完某個(gè)任務(wù)后不退出,繼續(xù)等待接收事件,則需要啟用RunLoop;
NSRunLoop提供了一個(gè)添加NSTimer的方法,可以指定Mode,如果要讓任何情況下都回調(diào),則需要設(shè)置Mode為Common模式;
實(shí)質(zhì)上,對于子線程的runloop默認(rèn)是不存在的,因?yàn)樘O果采用了懶加載的方式。如果我們沒有手動(dòng)調(diào)用[NSRunLoop currentRunLoop]的話,就不會(huì)去查詢是否存在當(dāng)前線程的RunLoop,也就不會(huì)去加載,更不會(huì)創(chuàng)建。
8.以scheduledTimerWithTimeInterval的方式觸發(fā)的timer,在滑動(dòng)頁面上的列表時(shí),timer會(huì)暫停,為什么?該如何解決?
原因在于滑動(dòng)時(shí)當(dāng)前線程的runloop切換了mode用于列表滑動(dòng),導(dǎo)致timer暫停。
runloop中的mode主要用來指定事件在runloop中的優(yōu)先級(jí),有以下幾種:
* Default(NSDefaultRunLoopMode):默認(rèn),一般情況下使用;
* Connection(NSConnectionReplyMode):一般系統(tǒng)用來處理NSConnection相關(guān)事件,開發(fā)者一般用不到;
* Modal(NSModalPanelRunLoopMode):處理modal panels事件;
* Event Tracking(NSEventTrackingRunLoopMode):用于處理拖拽和用戶交互的模式。
* Common(NSRunloopCommonModes):模式合集。默認(rèn)包括Default,Modal,Event Tracking三大模式,可以處理幾乎所有事件。
回到題中的情境。滑動(dòng)列表時(shí),runloop的mode由原來的Default模式切換到了Event Tracking模式,timer原來好好的運(yùn)行在Default模式中,被關(guān)閉后自然就停止工作了。
解決方法其一是將timer加入到NSRunloopCommonModes中。其二是將timer放到另一個(gè)線程中,然后開啟另一個(gè)線程的runloop,這樣可以保證與主線程互不干擾,而現(xiàn)在主線程正在處理頁面滑動(dòng)。
方法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.進(jìn)程與線程
進(jìn)程:
1.進(jìn)程是一個(gè)具有一定獨(dú)立功能的程序關(guān)于某次數(shù)據(jù)集合的一次運(yùn)行活動(dòng),它是操作系統(tǒng)分配資源的基本單元.
2.進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序,就是一段程序的執(zhí)行過程,我們可以理解為手機(jī)上的一個(gè)app.
3.每個(gè)進(jìn)程之間是獨(dú)立的,每個(gè)進(jìn)程均運(yùn)行在其專用且受保護(hù)的內(nèi)存空間內(nèi),擁有獨(dú)立運(yùn)行所需的全部資源
線程
1.程序執(zhí)行流的最小單元,線程是進(jìn)程中的一個(gè)實(shí)體.
2.一個(gè)進(jìn)程要想執(zhí)行任務(wù),必須至少有一條線程.應(yīng)用程序啟動(dòng)的時(shí)候,系統(tǒng)會(huì)默認(rèn)開啟一條線程,也就是主線程
進(jìn)程和線程的關(guān)系
1.線程是進(jìn)程的執(zhí)行單元,進(jìn)程的所有任務(wù)都在線程中執(zhí)行
2.線程是 CPU 分配資源和調(diào)度的最小單位
3.一個(gè)程序可以對應(yīng)多個(gè)進(jìn)程(多進(jìn)程),一個(gè)進(jìn)程中可有多個(gè)線程,但至少要有一條線程
4.同一個(gè)進(jìn)程內(nèi)的線程共享進(jìn)程資源
10.iOS中實(shí)現(xiàn)多線程的幾種方案,各自有什么特點(diǎn)?講一下具體使用場景
NSThread 面向?qū)ο蟮模枰绦騿T手動(dòng)創(chuàng)建線程,但不需要手動(dòng)銷毀。子線程間通信很難。
GCD c語言,充分利用了設(shè)備的多核,自動(dòng)管理線程生命周期。比NSOperation效率更高。
NSOperation 基于gcd封裝,更加面向?qū)ο螅萭cd多了一些功能。
【場景:1.多個(gè)網(wǎng)絡(luò)請求完成后執(zhí)行下一步 2.多個(gè)網(wǎng)絡(luò)請求順序執(zhí)行后執(zhí)行下一步 3.異步操作兩組數(shù)據(jù)時(shí), 執(zhí)行完第一組之后, 才能執(zhí)行第二組】
11.對稱加密和非對稱加密的區(qū)別?
1、對稱加密又稱公開密鑰加密,加密和解密都會(huì)用到同一個(gè)密鑰,如果密鑰被攻擊者獲得,此時(shí)加密就失去了意義。常見的對稱加密算法有DES、3DES、AES、Blowfish、IDEA、RC5、RC6。
2、非對稱加密又稱共享密鑰加密,使用一對非對稱的密鑰,一把叫做私有密鑰,另一把叫做公有密鑰;公鑰加密只能用私鑰來解密,私鑰加密只能用公鑰來解密。常見的公鑰加密算法有:RSA、ElGamal、背包算法、Rabin(RSA的特例)、迪菲-赫爾曼密鑰交換協(xié)議中的公鑰加密算法、橢圓曲線加密算法)。
12.組件化有什么好處?
業(yè)務(wù)分層、解耦,使代碼變得可維護(hù);
有效的拆分、組織日益龐大的工程代碼,使工程目錄變得可維護(hù);
便于各業(yè)務(wù)功能拆分、抽離,實(shí)現(xiàn)真正的功能復(fù)用;
業(yè)務(wù)隔離,跨團(tuán)隊(duì)開發(fā)代碼控制和版本風(fēng)險(xiǎn)控制的實(shí)現(xiàn);
模塊化對代碼的封裝性、合理性都有一定的要求,提升開發(fā)同學(xué)的設(shè)計(jì)能力;
在維護(hù)好各級(jí)組件的情況下,隨意組合滿足不同客戶需求;(只需要將之前的多個(gè)業(yè)務(wù)組件模塊在新的主App中進(jìn)行組裝即可快速迭代出下一個(gè)全新App)
13.你是如何組件化解耦的?
分層
基礎(chǔ)功能組件:按功能分庫,不涉及產(chǎn)品業(yè)務(wù)需求,跟庫Library類似,通過良好的接口拱上層業(yè)務(wù)組件調(diào)用;不寫入產(chǎn)品定制邏輯,通過擴(kuò)展接口完成定制;
基礎(chǔ)UI組件:各個(gè)業(yè)務(wù)模塊依賴使用,但需要保持好定制擴(kuò)展的設(shè)計(jì)
業(yè)務(wù)組件:業(yè)務(wù)功能間相對獨(dú)立,相互間沒有Model共享的依賴;業(yè)務(wù)之間的頁面調(diào)用只能通過UIBus進(jìn)行跳轉(zhuǎn);業(yè)務(wù)之間的邏輯Action調(diào)用只能通過服務(wù)提供;
中間件:target-action,url-block,protocol-class
14.APP啟動(dòng)時(shí)間應(yīng)從哪些方面優(yōu)化?
App啟動(dòng)時(shí)間可以通過xcode提供的工具來度量,在Xcode的Product->Scheme-->Edit Scheme->Run->Auguments中,將環(huán)境變量DYLD_PRINT_STATISTICS設(shè)為YES,優(yōu)化需以下方面入手
dylib loading time
核心思想是減少dylibs的引用
合并現(xiàn)有的dylibs(最好是6個(gè)以內(nèi))
使用靜態(tài)庫
rebase/binding time
核心思想是減少DATA塊內(nèi)的指針
減少Object C元數(shù)據(jù)量,減少Objc類數(shù)量,減少實(shí)例變量和函數(shù)(與面向?qū)ο笤O(shè)計(jì)思想沖突)
減少c++虛函數(shù)
多使用Swift結(jié)構(gòu)體(推薦使用swift)
ObjC setup time
核心思想同上,這部分內(nèi)容基本上在上一階段優(yōu)化過后就不會(huì)太過耗時(shí)
initializer time
使用initialize替代load方法
減少使用c/c++的attribute((constructor));推薦使用dispatch_once() pthread_once() std:once()等方法
推薦使用swift
不要在初始化中調(diào)用dlopen()方法,因?yàn)榧虞d過程是單線程,無鎖,如果調(diào)用dlopen則會(huì)變成多線程,會(huì)開啟鎖的消耗,同時(shí)有可能死鎖
不要在初始化中創(chuàng)建線程
Swift系列面試題總結(jié)
基礎(chǔ)題:
1.class 和 struct 的區(qū)別
struct是值類型,class是引用類型。
值類型的變量直接包含它們的數(shù)據(jù),對于值類型都有它們自己的數(shù)據(jù)副本,因此對一個(gè)變量操作不可能影響另一個(gè)變量。
引用類型的變量存儲(chǔ)對他們的數(shù)據(jù)引用,因此后者稱為對象,因此對一個(gè)變量操作可能影響另一個(gè)變量所引用的對象。
二者的本質(zhì)區(qū)別:struct是深拷貝,拷貝的是內(nèi)容;class是淺拷貝,拷貝的是指針。
property的初始化不同:class 在初始化時(shí)不能直接把 property 放在 默認(rèn)的constructor 的參數(shù)里,而是需要自己創(chuàng)建一個(gè)帶參數(shù)的constructor;而struct可以,把屬性放在默認(rèn)的constructor 的參數(shù)里。
變量賦值方式不同:struct是值拷貝;class是引用拷貝。
immutable變量:swift的可變內(nèi)容和不可變內(nèi)容用var和let來甄別,如果初始為let的變量再去修改會(huì)發(fā)生編譯錯(cuò)誤。struct遵循這一特性;class不存在這樣的問題。
mutating function: struct 和 class 的差別是 struct 的 function 要去改變 property 的值的時(shí)候要加上 mutating,而 class 不用。
繼承: struct不可以繼承,class可以繼承。
struct比class更輕量:struct分配在棧中,class分配在堆中。
2.Swift 是面向?qū)ο筮€是函數(shù)式的編程語言?
Swift 既是面向?qū)ο蟮?,又是函?shù)式的編程語言。
說 Swift 是面向?qū)ο蟮恼Z言,是因?yàn)?Swift 支持類的封裝、繼承、和多態(tài),從這點(diǎn)上來看與 Java 這類純面向?qū)ο蟮恼Z言幾乎毫無差別。
說 Swift 是函數(shù)式編程語言,是因?yàn)?Swift 支持 map, reduce, filter, flatmap 這類去除中間狀態(tài)、數(shù)學(xué)函數(shù)式的方法,更加強(qiáng)調(diào)運(yùn)算結(jié)果而不是中間過程。
3.什么是泛型,swift哪些地方使用了泛型?
泛型(generic)可以使我們在程序代碼中定義一些可變的部分,在運(yùn)行的時(shí)候指定。使用泛型可以最大限度地重用代碼、保護(hù)類型的安全以及提高性能。
例如 optional 中的 map、flatMap 、?? (泛型加逃逸閉包的方式,做三目運(yùn)算)
4.swift 語法糖 ? !的本質(zhì)(實(shí)現(xiàn)原理)
?為optional的語法糖
optional<T> 是一個(gè)包含了nil 和普通類型的枚舉,確保使用者在變量為nil的情況下處理
!為optional 強(qiáng)制解包的語法糖
5.Optional(可選型) 是用什么實(shí)現(xiàn)的
Optional 是一個(gè)泛型枚舉
大致定義如下:
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
除了使用 let someValue: Int? = nil 之外, 還可以使用let optional1: Optional<Int> = nil 來定義
6.什么是高階函數(shù)
一個(gè)函數(shù)如果可以以某一個(gè)函數(shù)作為參數(shù), 或者是返回值, 那么這個(gè)函數(shù)就稱之為高階函數(shù), 如 map, reduce, filter
7.如何解決引用循環(huán)
轉(zhuǎn)換為值類型, 只有類會(huì)存在引用循環(huán), 所以如果能不用類, 是可以解引用循環(huán)的,
delegate 使用 weak 屬性.
閉包中, 對有可能發(fā)生循環(huán)引用的對象, 使用 weak 或者 unowned, 修飾
8.定義靜態(tài)方法時(shí)關(guān)鍵字 static 和 class 有什么區(qū)別
static 定義的方法不可以被子類繼承, class 則可以
class AnotherClass {
static func staticMethod(){}
class func classMethod(){}
}
class ChildOfAnotherClass: AnotherClass {
override class func classMethod(){}
//override static func staticMethod(){}// error
}
9.請說明并比較以下關(guān)鍵詞:Open, Public, Internal, File-private, Private
Swift 有五個(gè)級(jí)別的訪問控制權(quán)限,從高到底依次為比如 Open, Public, Internal, File-private, Private。
他們遵循的基本原則是:高級(jí)別的變量不允許被定義為低級(jí)別變量的成員變量。比如一個(gè) private 的 class 中不能含有 public 的 String。反之,低級(jí)別的變量卻可以定義在高級(jí)別的變量中。比如 public 的 class 中可以含有 private 的 Int。
Open 具備最高的訪問權(quán)限。其修飾的類和方法可以在任意 Module 中被訪問和重寫;它是 Swift 3 中新添加的訪問權(quán)限。
Public 的權(quán)限僅次于 Open。與 Open 唯一的區(qū)別在于它修飾的對象可以在任意 Module 中被訪問,但不能重寫。
Internal 是默認(rèn)的權(quán)限。它表示只能在當(dāng)前定義的 Module 中訪問和重寫,它可以被一個(gè) Module 中的多個(gè)文件訪問,但不可以被其他的 Module 中被訪問。
File-private 也是 Swift 3 新添加的權(quán)限。其被修飾的對象只能在當(dāng)前文件中被使用。例如它可以被一個(gè)文件中的 class,extension,struct 共同使用。
Private 是最低的訪問權(quán)限。它的對象只能在定義的作用域內(nèi)使用。離開了這個(gè)作用域,即使是同一個(gè)文件中的其他作用域,也無法訪問。
10.swift中,關(guān)鍵字 guard 和 defer 的用法 guard也是基于一個(gè)表達(dá)式的布爾值去判斷一段代碼是否該被執(zhí)行。與if語句不同的是,guard只有在條件不滿足的時(shí)候才會(huì)執(zhí)行這段代碼。
guard let name = self.text else { return }
defer的用法是,這條語句并不會(huì)馬上執(zhí)行,而是被推入棧中,直到函數(shù)結(jié)束時(shí)才再次被調(diào)用。
defer {
//函數(shù)結(jié)束才調(diào)用
}
這里也推薦一些面試相關(guān)的內(nèi)容,祝各位網(wǎng)友都能拿到滿意offer!
GCD面試要點(diǎn)
block面試要點(diǎn)
Runtime面試要點(diǎn)
RunLoop面試要點(diǎn)
內(nèi)存管理面試要點(diǎn)
MVC、MVVM面試要點(diǎn)
網(wǎng)絡(luò)性能優(yōu)化面試要點(diǎn)
網(wǎng)絡(luò)編程面試要點(diǎn)
KVC&KVO面試要點(diǎn)
數(shù)據(jù)存儲(chǔ)面試要點(diǎn)
混編技術(shù)面試要點(diǎn)
設(shè)計(jì)模式面試要點(diǎn)
UI面試要點(diǎn)