最近準(zhǔn)備復(fù)習(xí)一下面試題虽抄,看到了J_Knight_在18年的出一套 iOS 高級面試題嘗試著回答一下題目走搁,由于水平有限,如有錯誤的地方迈窟,請大家多多指教私植。
目錄
iOS 基礎(chǔ)題
1. 分類和擴展有什么區(qū)別?可以分別用來做什么车酣?分類有哪些局限性曲稼?分類的結(jié)構(gòu)體里面有哪些成員?
2.講一下atomic的實現(xiàn)機制湖员;為什么不能保證絕對的線程安全(最好可以結(jié)合場景來說)贫悄?
3. 被weak修飾的對象在被釋放的時候會發(fā)生什么?是如何實現(xiàn)的破衔?知道sideTable么清女?里面的結(jié)構(gòu)可以畫出來么?
5. KVO的底層實現(xiàn)读第?如何取消系統(tǒng)默認(rèn)的KVO并手動觸發(fā)(給KVO的觸發(fā)設(shè)定條件:改變的值符合某個條件時再觸發(fā)KVO)曙博?
6. Autoreleasepool所使用的數(shù)據(jù)結(jié)構(gòu)是什么?AutoreleasePoolPage結(jié)構(gòu)體了解么怜瞒?
8. class_ro_t和class_rw_t的區(qū)別杆融?
9. iOS中內(nèi)省的幾個方法?class方法和objc_getClass方法有什么區(qū)別?
10. 在運行時創(chuàng)建類的方法objc_allocateClassPair的方法名尾部為什么是pair(成對的意思)霜运?
11. 一個int變量被__block修飾與否的區(qū)別脾歇?
12. 為什么在block外部使用__weak修飾的同時需要在內(nèi)部使用__strong修飾蒋腮?
13. RunLoop的作用是什么?它的內(nèi)部工作機制了解么藕各?(最好結(jié)合線程和內(nèi)存管理來說)
14. 哪些場景可以觸發(fā)離屏渲染池摧?(知道多少說多少)
iOS實戰(zhàn)題
16. 反射是什么激况?可以舉出幾個應(yīng)用場景么作彤?(知道多少說多少)
17. 有哪些場景是NSOperation比GCD更容易實現(xiàn)的?(或是NSOperation優(yōu)于GCD的幾點誉碴,知道多少說多少)
18. App 啟動優(yōu)化策略宦棺?最好結(jié)合啟動流程來說(main()函數(shù)的執(zhí)行前后都分別說一下,知道多少說多少)
19. App 無痕埋點的思路了解么黔帕?你認(rèn)為理想的無痕埋點系統(tǒng)應(yīng)該具備哪些特點代咸?(知道多少說多少)
20. 你知道有哪些情況會導(dǎo)致app崩潰,分別可以用什么方法攔截并化解成黄?(知道多少說多少)
21. 你知道有哪些情況會導(dǎo)致app卡頓呐芥,分別可以用什么方法來避免?(知道多少說多少)
網(wǎng)絡(luò)題
22. App 網(wǎng)絡(luò)層有哪些優(yōu)化策略奋岁?
24. 對稱加密和非對稱加密的區(qū)別闻伶?分別有哪些算法的實現(xiàn)滨攻?
25. HTTPS的握手流程?為什么密鑰的傳遞需要使用非對稱加密蓝翰?雙向認(rèn)證了解么光绕?
26. HTTPS是如何實現(xiàn)驗證身份和驗證完整性的?
27. 如何用Charles抓HTTPS的包畜份?其中原理和流程是什么诞帐?
計算機系統(tǒng)題
30. 靜態(tài)鏈接了解么钙态?靜態(tài)庫和動態(tài)庫的區(qū)別慧起?
31. 內(nèi)存的幾大區(qū)域,各自的職能分別是什么册倒?
37. 有哪幾種鎖脾歧?各自的原理?它們之間的區(qū)別是什么演熟?最好可以結(jié)合使用場景來說
設(shè)計模式題和架構(gòu) & 設(shè)計題(由于水平有限鞭执,就不誤人子弟了)
數(shù)據(jù)結(jié)構(gòu)&算法題
38. 鏈表和數(shù)組的區(qū)別是什么?插入和查詢的時間復(fù)雜度分別是多少芒粹?
39. 哈希表是如何實現(xiàn)的兄纺?如何解決地址沖突?
40. 排序題:冒泡排序化漆,選擇排序估脆,插入排序,快速排序(二路座云,三路)能寫出那些疙赠?
41. 鏈表題:如何檢測鏈表中是否有環(huán)?如何刪除鏈表中等于某個值的所有節(jié)點朦拖?
42. 數(shù)組題:如何在有序數(shù)組中找出和等于給定值的兩個元素圃阳?如何合并兩個有序的數(shù)組之后保持有序?
43. 二叉樹題:如何反轉(zhuǎn)二叉樹璧帝?如何驗證兩個二叉樹是完全相等的捍岳?
1.分類和擴展有什么區(qū)別?可以分別用來做什么睬隶?分類有哪些局限性锣夹?分類的結(jié)構(gòu)體里面有哪些成員?<span id="1.1"></span>
- 分類主要用來為某個類添加方法理疙,屬性晕城,協(xié)議(我一般用來為系統(tǒng)的類擴展方法或者把某個復(fù)雜的類的按照功能拆到不同的文件里)
- 擴展主要用來為某個類原來沒有的成員變量、屬性窖贤、方法砖顷。注:方法只是聲明(我一般用擴展來聲明私有屬性,或者把.h的只讀屬性重寫成可讀寫的)
分類和擴展的區(qū)別:
- 分類是在運行時把分類信息合并到類信息中赃梧,而擴展是在編譯時滤蝠,就把信息合并到類中的
- 分類聲明的屬性,只會生成getter/setter方法的聲明授嘀,不會自動生成成員變量和getter/setter方法的實現(xiàn)物咳,而擴展會
- 分類不可用為類添加實例變量,而擴展可以
- 分類可以為類添加方法的實現(xiàn)蹄皱,而擴展只能聲明方法览闰,而不能實現(xiàn)
分類的局限性:
- 無法為類添加實例變量芯肤,但可通過關(guān)聯(lián)對象進行實現(xiàn),注:關(guān)聯(lián)對象中內(nèi)存管理沒有weak压鉴,用時需要注意野指針的問題崖咨,可通過其他辦法來實現(xiàn),具體可參考iOS weak 關(guān)鍵字漫談
- 分類的方法若和類中原本的實現(xiàn)重名油吭,會覆蓋原本方法的實現(xiàn)击蹲,注:并不是真正的覆蓋
- 多個分類的方法重名,會調(diào)用最后編譯的那個分類的實現(xiàn)
分類的結(jié)構(gòu)體里有哪些成員
struct category_t {
const char *name; //名字
classref_t cls; //類的引用
struct method_list_t *instanceMethods;//實例方法列表
struct method_list_t *classMethods;//類方法列表
struct protocol_list_t *protocols;//協(xié)議列表
struct property_list_t *instanceProperties;//實例屬性列表
// 此屬性不一定真正的存在
struct property_list_t *_classProperties;//類屬性列表
};
2.講一下atomic的實現(xiàn)機制婉宰;為什么不能保證絕對的線程安全(最好可以結(jié)合場景來說)歌豺?<span id="1.2"></span>
- atomic的實現(xiàn)機制
atomic是property的修飾詞之一,表示是原子性的心包,使用方式為@property(atomic)int age;
,此時編譯器會自動生成getter/setter方法类咧,最終會調(diào)用objc_getProperty
和objc_setProperty
方法來進行存取屬性绑嘹。若此時屬性用atomic
修飾的話糠聪,在這兩個方法內(nèi)部使用os_unfair_lock
來進行加鎖齐佳,來保證讀寫的原子性轨蛤。鎖都在PropertyLocks中保存著(在iOS平臺會初始化8個抡诞,mac平臺64個)淹父,在用之前萧落,會把鎖都初始化好端逼,在需要用到時珊随,用對象的地址加上成員變量的偏移量為key述寡,去PropertyLocks中去取。因此存取時用的是同一個鎖叶洞,所以atomic能保證屬性的存取時是線程安全的鲫凶。注:由于鎖是有限的,不用對象衩辟,不同屬性的讀取用的也可能是同一個鎖 - atomic為什么不能保證絕對的線程安全螟炫?
- atomic在getter/setter方法中加鎖,僅保證了存取時的線程安全艺晴,假設(shè)我們的屬性是
@property(atomic)NSMutableArray *array;
可變的容器時,無法保證對容器的修改是線程安全的 - 在編譯器自動生產(chǎn)的getter/setter方法昼钻,最終會調(diào)用
objc_getProperty
和objc_setProperty
方法存取屬性,在此方法內(nèi)部保證了讀寫時的線程安全的封寞,當(dāng)我們重寫getter/setter方法時然评,就只能依靠自己在getter/setter中保證線程安全
- atomic在getter/setter方法中加鎖,僅保證了存取時的線程安全艺晴,假設(shè)我們的屬性是
3. 被weak修飾的對象在被釋放的時候會發(fā)生什么?是如何實現(xiàn)的狈究?知道sideTable么碗淌?里面的結(jié)構(gòu)可以畫出來么?<span id="1.3"></span>
被weak修飾的對象在被釋放的時候會發(fā)生什么?
被weak修飾的對象在被釋放的時候亿眠,會把weak指針自動置位nilweak是如何實現(xiàn)的碎罚?
runTime會把對weak修飾的對象放到一個全局的哈希表中,用weak修飾的對象的內(nèi)存地址為key缕探,weak指針為值魂莫,在對象進行銷毀時,用通過自身地址去哈希表中查找到所有指向此對象的weak指針爹耗,并把所有的weak指針置位nilsideTable的結(jié)構(gòu)
struct SideTable {
spinlock_t slock;//操作SideTable時用到的鎖
RefcountMap refcnts;//引用計數(shù)器的值
weak_table_t weak_table;//存放weak指針的哈希表
};
4. 關(guān)聯(lián)對象有什么應(yīng)用,系統(tǒng)如何管理關(guān)聯(lián)對象谜喊?其被釋放的時候需要手動將所有的關(guān)聯(lián)對象的指針置空么潭兽?<span id="1.4"></span>
- 關(guān)聯(lián)對象有什么應(yīng)用?
一般用于在分類中給類添加實例變量 - 系統(tǒng)如何管理關(guān)聯(lián)對象斗遏?
首先系統(tǒng)中有一個全局AssociationsManager
,里面有個AssociationsHashMap
哈希表山卦,哈希表中的key是對象的內(nèi)存地址,value是ObjectAssociationMap
,也是一個哈希表诵次,其中key是我們設(shè)置關(guān)聯(lián)對象所設(shè)置的key账蓉,value是ObjcAssociation
,里面存放著關(guān)聯(lián)對象設(shè)置的值和內(nèi)存管理的策略。
已void objc_setAssociatedObject(id object, const void * key,id value, objc_AssociationPolicy policy)
為例逾一,首先會通過AssociationsManager
獲取AssociationsHashMap
铸本,然后以object
的內(nèi)存地址為key,從AssociationsHashMap
中取出ObjectAssociationMap
遵堵,若沒有箱玷,則新創(chuàng)建一個ObjectAssociationMap
,然后通過key獲取舊值陌宿,以及通過key和policy生成新值ObjcAssociation(policy, new_value)
锡足,把新值存放到ObjectAssociationMap
中,若新值不為nil壳坪,并且內(nèi)存管理策略為retain
舶得,則會對新值進行一次retain
,若新值為nil爽蝴,則會刪除舊值沐批,若舊值不為空并且內(nèi)存管理的策略是retain
,則對舊值進行一次release
- 其被釋放的時候需要手動將所有的關(guān)聯(lián)對象的指針置空么霜瘪?
注:對這個問題我的理解是:當(dāng)對象被釋放時珠插,需要手動移除該對象所設(shè)置的關(guān)聯(lián)對象嗎?
不需要颖对,因為在對象的dealloc中捻撑,若發(fā)現(xiàn)對象有關(guān)聯(lián)對象時,會調(diào)用_object_remove_assocations
方法來移除所有的關(guān)聯(lián)對象,并根據(jù)內(nèi)存策略顾患,來判斷是否需要對關(guān)聯(lián)對象的值進行release
5. KVO的底層實現(xiàn)番捂?如何取消系統(tǒng)默認(rèn)的KVO并手動觸發(fā)(給KVO的觸發(fā)設(shè)定條件:改變的值符合某個條件時再觸發(fā)KVO)?<span id="1.5"></span>
-
KVO的底層實現(xiàn)江解?
當(dāng)某個類的屬性被觀察時设预,系統(tǒng)會在運行時動態(tài)的創(chuàng)建一個該類的子類。并且把改對象的isa指向這個子類
假設(shè)被觀察的屬性名是
name
犁河,若父類里有setName:
或這_setName:
,那么在子類里重寫這2個方法鳖枕,若2個方法同時存在,則只會重寫setName:
一個(這里和KVCset時的搜索順序是一樣的)若被觀察的類型是NSString,那么重寫的方法的實現(xiàn)會指向
_NSSetObjectValueAndNotify
這個函數(shù)桨螺,若是Bool類型宾符,那么重寫的方法的實現(xiàn)會指向_NSSetBoolValueAndNotify
這個函數(shù),這個函數(shù)里會調(diào)用willChangeValueForKey:
和didChangevlueForKey:
,并且會在這2個方法調(diào)用之間灭翔,調(diào)用父類set方法的實現(xiàn)系統(tǒng)會在
willChangeValueForKey:
對observe里的change[old]賦值魏烫,取值是用valueForKey:
取值的,didChangevlueForKey:
對observe里的change[new]賦值,然后調(diào)用observe的這個方法- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
當(dāng)使用KVC賦值的時候,在NSObject里的
setValue:forKey:
方法里,若父類不存在setName:
或這_setName:
這些方法,會調(diào)用_NSSetValueAndNotifyForKeyInIvar
這個函數(shù)肝箱,這個函數(shù)里同樣也會調(diào)用willChangeValueForKey:
和didChangevlueForKey:
,若存在則調(diào)用
如何取消系統(tǒng)默認(rèn)的KVO并手動觸發(fā)(給KVO的觸發(fā)設(shè)定條件:改變的值符合某個條件時再觸發(fā)KVO)哄褒?
舉例:取消Person類age屬性的默認(rèn)KVO,設(shè)置age大于18時煌张,手動觸發(fā)KVO
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"age"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
- (void)setAge:(NSInteger)age {
if (age > 18 ) {
[self willChangeValueForKey:@"age"];
_age = age;
[self didChangeValueForKey:@"age"];
}else {
_age = age;
}
}
6. Autoreleasepool所使用的數(shù)據(jù)結(jié)構(gòu)是什么呐赡?AutoreleasePoolPage結(jié)構(gòu)體了解么?<span id="1.6"></span>
Autoreleasepool是由多個AutoreleasePoolPage以雙向鏈表的形式連接起來的唱矛,
Autoreleasepool的基本原理:在每個自動釋放池創(chuàng)建的時候罚舱,會在當(dāng)前的AutoreleasePoolPage中設(shè)置一個標(biāo)記位,在此期間绎谦,當(dāng)有對象調(diào)用autorelsease時管闷,會把對象添加到AutoreleasePoolPage中,若當(dāng)前頁添加滿了窃肠,會初始化一個新頁包个,然后用雙向量表鏈接起來,并把新初始化的這一頁設(shè)置為hotPage,當(dāng)自動釋放池pop時冤留,從最下面依次往上pop碧囊,調(diào)用每個對象的release方法,直到遇到標(biāo)志位纤怒。
AutoreleasePoolPage結(jié)構(gòu)如下
class AutoreleasePoolPage {
magic_t const magic;
id *next;//下一個存放autorelease對象的地址
pthread_t const thread; //AutoreleasePoolPage 所在的線程
AutoreleasePoolPage * const parent;//父節(jié)點
AutoreleasePoolPage *child;//子節(jié)點
uint32_t const depth;//深度,也可以理解為當(dāng)前page在鏈表中的位置
uint32_t hiwat;
}
7. 講一下對象糯而,類對象,元類泊窘,跟元類結(jié)構(gòu)體的組成以及他們是如何相關(guān)聯(lián)的熄驼?為什么對象方法沒有保存的對象結(jié)構(gòu)體里像寒,而是保存在類對象的結(jié)構(gòu)體里?<span id="1.7"></span>
- 講一下對象瓜贾,類對象诺祸,元類,跟元類結(jié)構(gòu)體的組成以及他們是如何相關(guān)聯(lián)的祭芦?
對象的結(jié)構(gòu)體里存放著isa和成員變量筷笨,isa指向類對象。
類對象的isa指向元類龟劲,元類的isa指向NSObject的元類胃夏。
類對象和元類的結(jié)構(gòu)體有isa、superclass昌跌、cache构订、bits,bits里存放著class_rw_t的指針避矢。
放一張經(jīng)典的圖
isa - 為什么對象方法沒有保存的對象結(jié)構(gòu)體里,而是保存在類對象的結(jié)構(gòu)體里囊榜?
方法是每個對象互相可以共用的审胸,如果每個對象都存儲一份方法列表太浪費內(nèi)存,由于對象的isa是指向類對象的卸勺,當(dāng)調(diào)用的時候砂沛,直接去類對象中查找就行了∈锴螅可以節(jié)約很多內(nèi)存空間的
8. class_ro_t和class_rw_t的區(qū)別碍庵?
class_rw_t提供了運行時對類拓展的能力,而class_ro_t存儲的大多是類在編譯時就已經(jīng)確定的信息悟狱。二者都存有類的方法静浴、屬性(成員變量)、協(xié)議等信息挤渐,不過存儲它們的列表實現(xiàn)方式不同苹享。簡單的說class_rw_t存儲列表使用的二維數(shù)組,class_ro_t使用的一維數(shù)組浴麻。
class_ro_t存儲于class_rw_t結(jié)構(gòu)體中得问,是不可改變的。保存著類的在編譯時就已經(jīng)確定的信息软免。而運行時修改類的方法宫纬,屬性,協(xié)議等都存儲于class_rw_t中
9. iOS中內(nèi)省的幾個方法膏萧?class方法和objc_getClass方法有什么區(qū)別?
- 什么是內(nèi)世焐А蝌衔?
在計算機科學(xué)中,內(nèi)省是指計算機程序在運行時(Run time)檢查對象(Object)類型的一種能力认境,通常也可以稱作運行時類型檢查胚委。
不應(yīng)該將內(nèi)省和反射混淆。相對于內(nèi)省叉信,反射更進一步亩冬,是指計算機程序在運行時(Run time)可以訪問、檢測和修改它本身狀態(tài)或行為的一種能力硼身。
-
iOS中內(nèi)省的幾個方法硅急?
- isMemberOfClass //對象是否是某個類型的對象
- isKindOfClass //對象是否是某個類型或某個類型子類的對象
- isSubclassOfClass //某個類對象是否是另一個類型的子類
- isAncestorOfObject //某個類對象是否是另一個類型的父類
- respondsToSelector //是否能響應(yīng)某個方法
- conformsToProtocol //是否遵循某個協(xié)議
class方法和object_getClass方法有什么區(qū)別?
實例class方法就直接返回object_getClass(self),類class方法直接返回self,而object_getClass(類對象)佳遂,則返回的是元類
10. 在運行時創(chuàng)建類的方法objc_allocateClassPair的方法名尾部為什么是pair(成對的意思)营袜?
因為此方法會創(chuàng)建一個類對象以及元類,正好組成一隊
Class objc_allocateClassPair(Class superclass, const char *name,
size_t extraBytes){
...省略了部分代碼
//生成一個類對象
cls = alloc_class_for_subclass(superclass, extraBytes);
//生成一個類對象元類對象
meta = alloc_class_for_subclass(superclass, extraBytes);
objc_initializeClassPair_internal(superclass, name, cls, meta);
return cls;
}
11. 一個int變量被__block修飾與否的區(qū)別丑罪?
int變量被__block修飾之后會被包裝成一個對象,如__block int age
會被包裝成下面這樣
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding; //指向自己
int __flags;
int __size;
int age;//包裝的具體的值
};
// age = 20;會被編譯成下面這樣
(age.__forwarding->age) = 20;
12. 為什么在block外部使用__weak修飾的同時需要在內(nèi)部使用__strong修飾荚板?
用__weak修飾之后block不會對該對象進行retain,只是持有了weak指針吩屹,在block執(zhí)行之前或執(zhí)行的過程時跪另,隨時都有可能被釋放,將weak指針置位nil煤搜,產(chǎn)生一些未知的錯誤免绿。在內(nèi)部用__strong修飾,會在block執(zhí)行時擦盾,對該對象進行一次retain嘲驾,保證在執(zhí)行時若該指針不指向nil,則在執(zhí)行過程中不會指向nil迹卢。但有可能在執(zhí)行執(zhí)行之前已經(jīng)為nil了
13. RunLoop的作用是什么辽故?它的內(nèi)部工作機制了解么?(最好結(jié)合線程和內(nèi)存管理來說)
- 什么是RunLoop
一般來講婶希,一個線程一次只能執(zhí)行一個任務(wù)榕暇,執(zhí)行完成后線程就會退出。如果我們需要一個機制喻杈,讓線程能隨時處理事件但并不退出彤枢。這種模型通常被稱作 Event Loop。 Event Loop 在很多系統(tǒng)和框架里都有實現(xiàn)筒饰,比如 Node.js 的事件處理缴啡,比如 Windows 程序的消息循環(huán),再比如 OSX/iOS 里的 RunLoop瓷们。實現(xiàn)這種模型的關(guān)鍵點在于:如何管理事件/消息业栅,如何讓線程在沒有處理消息時休眠以避免資源占用秒咐、在有消息到來時立刻被喚醒。 - RunLoop的作用是什么碘裕?(由于水平有限携取,不是很理解作者的本意,我對題目的理解是帮孔,利用RunLoop可以做哪些事情雷滋?)
- 保持程序的持續(xù)運行,在iOS線程中文兢,會在main方法給主線程創(chuàng)建一個RunLoop晤斩,保證主線程不被銷毀
- 處理APP中的各種事件(如touch,timer姆坚,performSelector等)
- 界面更新
- 手勢識別
- AutoreleasePool
- 系統(tǒng)在主線程RunLoop注冊了2個observer
- 第一個observe監(jiān)聽即將進入RunLoop澳泵,調(diào)用_objc_autoreleasePoolPush()創(chuàng)建自動釋放池
- 第二個observe監(jiān)聽兩個事件,進入休眠之前和即將退出RunLoop
- 在進入休眠之前的回調(diào)里兼呵,會先釋放自動釋放池兔辅,然后在創(chuàng)建一個自動釋放池
- 在即將退出的回調(diào)里,會釋放自動釋放池
- 線程被魑梗活
- 監(jiān)測卡頓
- RunLoop的內(nèi)部邏輯(下圖來自于YY大神的博客)
14. 哪些場景可以觸發(fā)離屏渲染幢妄?(知道多少說多少)
- 添加遮罩mask
- 添加陰影shadow
- 設(shè)置圓角并且設(shè)置masksToBounds為true
- 設(shè)置allowsGroupOpacity為true并且layer.opacity小于1.0和有子layer或者背景不為空
- 開啟光柵化shouldRasterize=true
15.AppDelegate如何瘦身?<span id="15"></span>
- AppDelegate為什么會那么臃腫茫负?
AppDelegate是一個項目的入口,承擔(dān)了太多的功能乎赴,如初始化根控制器忍法,管理應(yīng)用的狀態(tài),管理推送榕吼,和其他APP交互饿序,初始化第三方SDK,獲取權(quán)限等等 - 如何瘦身
瘦身的方案有很多羹蚣,比如說把某些方法放到swift擴展或者OC的分類中原探,抽取中間類,利用通知監(jiān)聽等等顽素,不過我比較喜歡的是使用命令設(shè)計模式進行瘦身咽弦。
命令模式是描述對象被稱作命令相當(dāng)于是一個簡單的方法或者事件。因為對象封裝了觸發(fā)自身所需的所有參數(shù)胁出,因此命令的調(diào)用者不知道該命令做了什么以及響應(yīng)者是誰
可以為APPdelegate的每一個職責(zé)定義一個命令型型,這個命令的名字有他們自己指定
protocol Command {
func execute()
}
struct InitializeThirdPartiesCommand: Command {
func execute() {
// 初始化第三方庫
}
}
struct InitialViewControllerCommand: Command {
let keyWindow: UIWindow
func execute() {
// 設(shè)置根控制器
keyWindow.rootViewController = UIViewController()
}
}
struct RegisterToRemoteNotificationsCommand: Command {
func execute() {
// 注冊遠(yuǎn)程推送
}
}
然后我們定義StartupCommandsBuilder
來封裝如何創(chuàng)建命令的詳細(xì)信息。APPdelegate調(diào)用這個builder去初始化命令并執(zhí)行這些命令
// MARK: - Builder
final class StartupCommandsBuilder {
private var window: UIWindow!
func setKeyWindow(_ window: UIWindow) -> StartupCommandsBuilder {
self.window = window
return self
}
func build() -> [Command] {
return [
InitializeThirdPartiesCommand(),
InitialViewControllerCommand(keyWindow: window),
RegisterToRemoteNotificationsCommand()
]
}
}
// MARK: - App Delegate
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
StartupCommandsBuilder()
.setKeyWindow(window!)
.build()
.forEach { $0.execute() }
return true
}
}
如果APPdelegate需要添加新的職責(zé)全蝶,則可以創(chuàng)建新的命令闹蒜,然后把命令添加到builder里去而無需去改變APPdelegate寺枉。而且使用命令模式有以下好處
- 每個命令都有單一的職責(zé)
- 無需更改APPdelegate就可以很容易的添加新的命令
- 每個命令可以很容易的被單獨測試
16. 反射是什么?可以舉出幾個應(yīng)用場景么绷落?(知道多少說多少)<span id="16"></span>
- 什么是反射
反射是指計算機程序在運行時(runtime)可以訪問姥闪、檢測和修改它本身狀態(tài)或行為的一種能力。用比喻來說砌烁,反射就是程序在運行的時候能夠“觀察”并且修改自己的行為筐喳。
在OC中,反射是指程序在運行時往弓,獲取和修改類的信息疏唾。
- 應(yīng)用場景
- JSON與模型之間的相互轉(zhuǎn)換
- Method Swizzling
- KVO的實現(xiàn)原理
- 實現(xiàn)NSCoding的自動歸檔和自動解檔
- 探索系統(tǒng)某些類的具體實現(xiàn)
17. 有哪些場景是NSOperation比GCD更容易實現(xiàn)的?(或是NSOperation優(yōu)于GCD的幾點函似,知道多少說多少)<span id="17"></span>
- NSOperation可以設(shè)置依賴
- NSOperation可以進行暫停槐脏,繼續(xù)等操作
- NSOperation可以監(jiān)測當(dāng)前隊列運行的狀態(tài)
- NSOperationQueue可以取消隊列里的所有操作
- NSOperationQueue很方便的設(shè)置最大并發(fā)數(shù)
18. App啟動優(yōu)化策略?最好結(jié)合啟動流程來說(main()函數(shù)的執(zhí)行前后都分別說一下撇寞,知道多少說多少)<span id="18"></span>
- iOS的啟動流程
- 根據(jù) info.plist 里的設(shè)置加載閃屏顿天,建立沙箱,對權(quán)限進行檢查等
- 加載可執(zhí)行文件
- 加載動態(tài)鏈接庫蔑担,進行 rebase 指針調(diào)整和 bind 符號綁定
- Objc 運行時的初始處理牌废,包括 Objc 相關(guān)類的注冊、category 注冊啤握、selector 唯一性檢查等鸟缕;
- 初始化,包括了執(zhí)行 +load() 方法排抬、attribute((constructor)) 修飾的函數(shù)的調(diào)用懂从、創(chuàng)建 C++ 靜態(tài)全局變量。
- 執(zhí)行 main 函數(shù)
- Application 初始化蹲蒲,到 applicationDidFinishLaunchingWithOptions 執(zhí)行完
- 初始化幀渲染番甩,到 viewDidAppear 執(zhí)行完,用戶可見可操作届搁。
- 啟動優(yōu)化
- 減少動態(tài)庫的加載
- 去除掉無用的類和C++全局變量的數(shù)量
- 盡量讓load方法中的內(nèi)容放到首屏渲染之后再去執(zhí)行缘薛,或者使用initialize替換
- 去除在首屏展現(xiàn)之前非必要的功能
- 檢查首屏展現(xiàn)之前主線程的耗時方法,將沒必要的耗時方法滯后或者延遲執(zhí)行
19. App無痕埋點的思路了解么卡睦?你認(rèn)為理想的無痕埋點系統(tǒng)應(yīng)該具備哪些特點宴胧?(知道多少說多少))<span id="19"></span>
App無痕埋點的思路是利用AOP來攔截用戶的操作并進行標(biāo)記記錄然后進行上傳
我認(rèn)為理想的無痕埋點系統(tǒng)應(yīng)該具備以下特點
- 不侵入業(yè)務(wù)代碼
- 統(tǒng)計盡可能多的事件
- 自動生成唯一標(biāo)識
- 要能統(tǒng)計到控件在但不同狀態(tài)意義不同的情況
- 需要某些機制能夠提供業(yè)務(wù)數(shù)據(jù)
- 在合適的時機去上傳數(shù)據(jù)
20. 你知道有哪些情況會導(dǎo)致app崩潰,分別可以用什么方法攔截并化解表锻?(知道多少說多少))<span id="20"></span>
- unrecognized selector sent to instance 方法找不到
- 數(shù)組越界牺汤,插入空值
-
[NSDictionary initWithObjects:forKeys:]
使用此方法初始化字典時,objects和keys的數(shù)量不一致時 - NSMutableDictionary浩嫌,
setObject:forKey:
或者removeObjectForKey:
時檐迟,key為nil -
setValue:forUndefinedKey:
补胚,使用KVC對對象進行存取值時傳入錯誤的key或者對不可變字典進行賦值 - NSUserDefaults 存儲時key為nil
- 對字符串操作時,傳遞的下標(biāo)超出范圍追迟,判斷是否存在前綴溶其,后綴子串時,子串為空
- 使用C字符串初始化字符串時敦间,傳入null
- 對可變集合或字符串使用copy修飾并進行修改操作
- 在空間未添加到父元素上之前瓶逃,就使用autoLayout進行布局
- KVO在對象銷毀時,沒有移除KVO或者多次移除KVO
- 野指針訪問
- 死鎖
- 除0
1-9都可以利用Runtime進行攔截廓块,然后進行一些邏輯處理厢绝,防止crash
21. 你知道有哪些情況會導(dǎo)致app卡頓,分別可以用什么方法來避免带猴?(知道多少說多少))<span id="21"></span>
- 主線程中進化IO或其他耗時操作昔汉,解決:把耗時操作放到子線程中操作
- GCD并發(fā)隊列短時間內(nèi)創(chuàng)建大量任務(wù),解決:使用線程池
- 文本計算拴清,解決:把計算放在子線程中避免阻塞主線程
- 大量圖像的繪制靶病,解決:在子線程中對圖片進行解碼之后再展示
- 高清圖片的展示,解法:可在子線程中進行下采樣處理之后再展示
22. App網(wǎng)絡(luò)層有哪些優(yōu)化策略口予?<span id="22"></span>
- 優(yōu)化DNS解析和緩存
- 對傳輸?shù)臄?shù)據(jù)進行壓縮娄周,減少傳輸?shù)臄?shù)據(jù)
- 使用緩存手段減少請求的發(fā)起次數(shù)
- 使用策略來減少請求的發(fā)起次數(shù),比如在上一個請求未著地之前沪停,不進行新的請求
- 避免網(wǎng)絡(luò)抖動煤辨,提供重發(fā)機制
23. TCP為什么要三次握手,四次揮手木张?<span id="23"></span>
三次握手:
- 客戶端向服務(wù)端發(fā)起請求鏈接掷酗,首先發(fā)送SYN報文,SYN=1窟哺,seq=x,并且客戶端進入SYN_SENT狀態(tài)
- 服務(wù)端收到請求鏈接,服務(wù)端向客戶端進行回復(fù)技肩,并發(fā)送響應(yīng)報文且轨,SYN=1,seq=y,ACK=1,ack=x+1,并且服務(wù)端進入到SYN_RCVD狀態(tài)
- 客戶端收到確認(rèn)報文后虚婿,向服務(wù)端發(fā)送確認(rèn)報文旋奢,ACK=1,ack=y+1然痊,此時客戶端進入到ESTABLISHED至朗,服務(wù)端收到用戶端發(fā)送過來的確認(rèn)報文后,也進入到ESTABLISHED狀態(tài)剧浸,此時鏈接創(chuàng)建成功
四次揮手:
- 客戶端向服務(wù)端發(fā)起關(guān)閉鏈接锹引,并停止發(fā)送數(shù)據(jù)
- 服務(wù)端收到關(guān)閉鏈接的請求時矗钟,向客戶端發(fā)送回應(yīng),我知道了嫌变,然后停止接收數(shù)據(jù)
- 當(dāng)服務(wù)端發(fā)送數(shù)據(jù)結(jié)束之后吨艇,向客戶端發(fā)起關(guān)閉鏈接,并停止發(fā)送數(shù)據(jù)
- 客戶端收到關(guān)閉鏈接的請求時腾啥,向服務(wù)端發(fā)送回應(yīng)东涡,我知道了,然后停止接收數(shù)據(jù)
為什么需要三次握手:
為了防止已失效的連接請求報文段突然又傳送到了服務(wù)端倘待,因而產(chǎn)生錯誤疮跑,假設(shè)這是一個早已失效的報文段。但server收到此失效的連接請求報文段后凸舵,就誤認(rèn)為是client再次發(fā)出的一個新的連接請求祖娘。于是就向client發(fā)出確認(rèn)報文段,同意建立連接贞间。假設(shè)不采用“三次握手”贿条,那么只要server發(fā)出確認(rèn),新的連接就建立了增热。由于現(xiàn)在client并沒有發(fā)出建立連接的請求整以,因此不會理睬server的確認(rèn),也不會向server發(fā)送數(shù)據(jù)峻仇。但server卻以為新的運輸連接已經(jīng)建立公黑,并一直等待client發(fā)來數(shù)據(jù)。這樣摄咆,server的很多資源就白白浪費掉了凡蚜。
為什么需要四次揮手:
因為TCP是全雙工通信的,在接收到客戶端的關(guān)閉請求時吭从,還可能在向客戶端發(fā)送著數(shù)據(jù)朝蜘,因此不能再回應(yīng)關(guān)閉鏈接的請求時,同時發(fā)送關(guān)閉鏈接的請求
24. 對稱加密和非對稱加密的區(qū)別涩金?分別有哪些算法的實現(xiàn)谱醇?<span id="24"></span>
對稱加密,加密的加密和解密使用同一密鑰步做。
非對稱加密副渴,使用一對密鑰用于加密和解密,分別為公開密鑰和私有密鑰全度。公開密鑰所有人都可以獲得煮剧,通信發(fā)送方獲得接收方的公開密鑰之后,就可以使用公開密鑰進行加密,接收方收到通信內(nèi)容后使用私有密鑰解密勉盅。
對稱加密常用的算法實現(xiàn)有AES,ChaCha20,DES,不過DES被認(rèn)為是不安全的;非對稱加密用的算法實現(xiàn)有RSA佑颇,ECC
25. HTTPS的握手流程?為什么密鑰的傳遞需要使用非對稱加密菇篡?雙向認(rèn)證了解么漩符?<span id="25"></span>
HTTPS的握手流程,如下圖驱还,摘自圖解HTTP
- 客戶端發(fā)送Client Hello 報文開始SSL通信嗜暴。報文中包含客戶端支持的SSL的版本,加密組件列表议蟆。
- 服務(wù)器收到之后闷沥,會以Server Hello 報文作為應(yīng)答。和客戶端一樣咐容,報文中包含客戶端支持的SSL的版本舆逃,加密組件列表。服務(wù)器的加密組件內(nèi)容是從接收到的客戶端加密組件內(nèi)篩選出來的
- 服務(wù)器發(fā)送Certificate報文戳粒。報文中包含公開密鑰證書路狮。
- 然后服務(wù)器發(fā)送Server Hello Done報文通知客戶端,最初階段的SSL握手協(xié)商部分結(jié)束
- SSL第一次握手結(jié)束之后蔚约,客戶端以Client Key Exchange報文作為會議奄妨。報文中包含通信加密中使用的一種被稱為Pre-master secret的隨機密碼串
- 接著客戶端發(fā)送Change Cipher Space報文。該報文會提示服務(wù)器苹祟,在次報文之后的通信會采用Pre-master secret密鑰加密
- 客戶端發(fā)送Finished 報文砸抛。該報文包含鏈接至今全部報文的整體校驗值。這次握手協(xié)商是否能夠成功树枫,要以服務(wù)器是否能夠正確揭秘該報文作為判定標(biāo)準(zhǔn)
- 服務(wù)器同樣發(fā)送Change Cipher Space報文直焙。
- 服務(wù)器同樣發(fā)送Finished報文。
- 服務(wù)器和客戶端的Finished報文交換完畢之后砂轻,SSL連接建立完成奔誓,從此開始HTTP通信,通信的內(nèi)容都使用Pre-master secret加密搔涝。然后開始發(fā)送HTTP請求
- 應(yīng)用層收到HTTP請求之后汗盘,發(fā)送HTTP響應(yīng)
- 最后有客戶端斷開連接
為什么密鑰的傳遞需要使用非對稱加密改执?
答:使用非對稱加密是為了后面客戶端生成的Pre-master secret密鑰的安全校辩,通過上面的步驟能得知翻默,服務(wù)器向客戶端發(fā)送公鑰證書這一步是有可能被別人攔截的臼婆,如果使用對稱加密的話抒痒,在客戶端向服務(wù)端發(fā)送Pre-master secret密鑰的時候,被黑客攔截的話颁褂,就能夠使用公鑰進行解碼故响,就無法保證Pre-master secret密鑰的安全了
雙向認(rèn)證了解么傀广?(這里我真想說一句不了解)
答:上面的HTTPS的通信流程只驗證了服務(wù)端的身份,而服務(wù)端沒有驗證客戶端的身份彩届,雙向認(rèn)證是服務(wù)端也要確蔽北客戶端的身份,大概流程是客戶端在校驗完服務(wù)器的證書之后樟蠕,會向服務(wù)器發(fā)送自己的公鑰贮聂,然后服務(wù)端用公鑰加密產(chǎn)生一個新的密鑰,傳給客戶端寨辩,客戶端再用私鑰解密吓懈,以后就用此密鑰進行對稱加密的通信
26. HTTPS是如何實現(xiàn)驗證身份和驗證完整性的?<span id="26"></span>
使用數(shù)字證書和CA來驗證身份,首先服務(wù)端先向CA機構(gòu)去申請證書靡狞,CA審核之后會給一個數(shù)字證書耻警,里面包裹公鑰甸怕、簽名、有效期温兼,用戶信息等各種信息式曲,在客戶端發(fā)送請求時吝羞,服務(wù)端會把數(shù)字證書發(fā)給客戶端,然后客戶端會通過信任鏈來驗證數(shù)字證書是否是有效的敦腔,來驗證服務(wù)端的身份符衔。
使用摘要算法來驗證完整性糟袁,也就是說在發(fā)送消息時判族,會對消息的內(nèi)容通過摘要算法生成一段摘要,在收到收到消息時也使用同樣的算法生成摘要项戴,來判斷摘要是否一致形帮。
27. 如何用Charles抓HTTPS的包?其中原理和流程是什么?<span id="27"></span>
流程:
- 首先在手機上安裝Charles證書
- 在代理設(shè)置中開啟Enable SSL Proxying
- 之后添加需要抓取服務(wù)端的地址
原理:
Charles作為中間人辩撑,對客戶端偽裝成服務(wù)端界斜,對服務(wù)端偽裝成客戶端。簡單來說:
- 截獲客戶端的HTTPS請求合冀,偽裝成中間人客戶端去向服務(wù)端發(fā)送HTTPS請求
- 接受服務(wù)端返回各薇,用自己的證書偽裝成中間人服務(wù)端向客戶端發(fā)送數(shù)據(jù)內(nèi)容。
具體流程如下圖,圖片來自扯一扯HTTPS單向認(rèn)證君躺、雙向認(rèn)證峭判、抓包原理、反抓包策略
28. 什么是中間人攻擊晰洒?如何避免朝抖?<span id="28"></span>
中間人攻擊就是截獲到客戶端的請求以及服務(wù)器的響應(yīng),比如Charles抓取HTTPS的包就屬于中間人攻擊谍珊。
避免的方式:客戶端可以預(yù)埋證書在本地治宣,然后進行證書的比較是否是匹配的
29. 了解編譯的過程么?分為哪幾個步驟砌滞?<span id="29"></span>
- 預(yù)編譯:主要處理以“#”開始的預(yù)編譯指令侮邀。
- 編譯:
- 詞法分析:將字符序列分割成一系列的記號。
- 語法分析:根據(jù)產(chǎn)生的記號進行語法分析生成語法樹。
- 語義分析:分析語法樹的語義亡笑,進行類型的匹配、轉(zhuǎn)換晰甚、標(biāo)識等地回。
- 中間代碼生成:源碼級優(yōu)化器將語法樹轉(zhuǎn)換成中間代碼穿香,然后進行源碼級優(yōu)化,比如把 1+2 優(yōu)化為 3。中間代碼使得編譯器被分為前端和后端雁歌,不同的平臺可以利用不同的編譯器后端將中間代碼轉(zhuǎn)換為機器代碼,實現(xiàn)跨平臺乏盐。
- 目標(biāo)代碼生成:此后的過程屬于編譯器后端净神,代碼生成器將中間代碼轉(zhuǎn)換成目標(biāo)代碼(匯編代碼)爱榕,其后目標(biāo)代碼優(yōu)化器對目標(biāo)代碼進行優(yōu)化,比如調(diào)整尋址方式、使用位移代替乘法坑夯、刪除多余指令仗谆、調(diào)整指令順序等。
- 匯編:匯編器將匯編代碼轉(zhuǎn)變成機器指令狸吞。
- 靜態(tài)鏈接:鏈接器將各個已經(jīng)編譯成機器指令的目標(biāo)文件鏈接起來,經(jīng)過重定位過后輸出一個可執(zhí)行文件威始。
- 裝載:裝載可執(zhí)行文件、裝載其依賴的共享對象葫掉。
- 動態(tài)鏈接:動態(tài)鏈接器將可執(zhí)行文件和共享對象中需要重定位的位置進行修正。
- 最后,進程的控制權(quán)轉(zhuǎn)交給程序入口扛门,程序終于運行起來了。
30. 靜態(tài)鏈接了解么葬凳?靜態(tài)庫和動態(tài)庫的區(qū)別?<span id="30"></span>
靜態(tài)鏈接是指將多個目標(biāo)文件合并為一個可執(zhí)行文件占业,直觀感覺就是將所有目標(biāo)文件的段合并谦疾。需要注意的是可執(zhí)行文件與目標(biāo)文件的結(jié)構(gòu)基本一致佑附,不同的是是否“可執(zhí)行”。
靜態(tài)庫:鏈接時完整地拷貝至可執(zhí)行文件中权均,被多次使用就有多份冗余拷貝。
動態(tài)庫:鏈接時不復(fù)制,程序運行時由系統(tǒng)動態(tài)加載到內(nèi)存塔橡,供程序調(diào)用,系統(tǒng)只加載一次,多個程序共用刃榨,節(jié)省內(nèi)存迅栅。
31. 內(nèi)存的幾大區(qū)域为流,各自的職能分別是什么?<span id="31"></span>
- 棧區(qū):有系統(tǒng)自動分配并釋放,一般存放函數(shù)的參數(shù)值,局部變量等
- 堆區(qū):有程序員分配和釋放缴阎,若程序員未釋放,則在程序結(jié)束時有系統(tǒng)釋放,在iOS里創(chuàng)建出來的對象會放在堆區(qū)
- 數(shù)據(jù)段:字符串常量肛跌,全局變量西饵,靜態(tài)變量
- 代碼段:編譯之后的代碼
32. static和const有什么區(qū)別?<span id="32"></span>
const是指聲明一個常量
static修飾全局變量時驯嘱,表示此全局變量只在當(dāng)前文件可見
static修飾局部變量時壕鹉,表示每次調(diào)用的初始值為上一次調(diào)用的值剃幌,調(diào)用結(jié)束后存儲空間不釋放
33. 了解內(nèi)聯(lián)函數(shù)么聋涨?<span id="33"></span>
內(nèi)聯(lián)函數(shù)是為了減少函數(shù)調(diào)用的開銷,編譯器在編譯階段把函數(shù)體內(nèi)的代碼復(fù)制到函數(shù)調(diào)用處
34. 什么時候會出現(xiàn)死鎖负乡?如何避免牍白?<span id="34"></span>
死鎖是指兩個或兩個以上的線程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象抖棘,若無外力作用,它們都將無法推進下去。
發(fā)生死鎖的四個必要條件:
- 互斥條件:一個資源每次只能被一個線程使用豹储。
- 請求與保持條件:一個線程因請求資源而阻塞時鞠鲜,對已獲得的資源保持不放碧信。
- 不剝奪條件:線程已獲得的資源,在未使用完之前荧飞,不能強行剝奪睛藻。
- 循環(huán)等待條件:若干線程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。
只要上面四個條件有一個條件不被滿足就能避免死鎖
35. 說一說你對線程安全的理解汰寓?<span id="35"></span>
在并發(fā)執(zhí)行的環(huán)境中艇劫,對于共享數(shù)據(jù)通過同步機制保證各個線程都可以正確的執(zhí)行,不會出現(xiàn)數(shù)據(jù)污染的情況吹榴,或者對于某個資源,在被多個線程訪問時娃胆,不管運行時執(zhí)行這些線程有什么樣的順序或者交錯丧蘸,不會出現(xiàn)錯誤的行為眯娱,就認(rèn)為這個資源是線程安全的,一般來說单寂,對于某個資源如果只有讀操作精盅,則這個資源無需同步就是線程安全的帽哑,若有多個線程進行讀寫操作,則需要線程同步來保證線程安全叹俏。
36. 列舉你知道的線程同步策略妻枕?<span id="36"></span>
- OSSpinLock 自旋鎖,已不再安全,除了這個鎖之外屡谐,下面寫的鎖述么,在等待時,都會進入線程休眠狀態(tài)愕掏,而非忙等
- os_unfair_lock atomic就是使用此鎖來保證原子性的
- pthread_mutex_t 互斥鎖度秘,并且支持遞歸實現(xiàn)和條件實現(xiàn)
- NSLock,NSRecursiveLock,基本的互斥鎖,NSRecursiveLock支持遞歸調(diào)用饵撑,都是對pthread_mutex_t的封裝
- NSCondition,NSConditionLock剑梳,條件鎖,也都是對pthread_mutex_t的封裝
- dispatch_semaphore_t 信號量
- @synchronized 也是pthread_mutex_t的封裝
37. 有哪幾種鎖滑潘?各自的原理垢乙?它們之間的區(qū)別是什么?最好可以結(jié)合使用場景來說<span id="37"></span>
- 自旋鎖:自旋鎖在無法進行加鎖時语卤,會不斷的進行嘗試追逮,一般用于臨界區(qū)的執(zhí)行時間較短的場景,不過iOS的自旋鎖OSSpinLock不再安全粹舵,主要原因發(fā)生在低優(yōu)先級線程拿到鎖時羊壹,高優(yōu)先級線程進入忙等(busy-wait)狀態(tài),消耗大量 CPU 時間齐婴,從而導(dǎo)致低優(yōu)先級線程拿不到 CPU 時間油猫,也就無法完成任務(wù)并釋放鎖。這種問題被稱為優(yōu)先級反轉(zhuǎn)柠偶。
- 互斥鎖:對于某一資源同時只允許有一個訪問情妖,無論讀寫,平常使用的NSLock就屬于互斥鎖
- 讀寫鎖:對于某一資源同時只允許有一個寫訪問或者多個讀訪問诱担,iOS中pthread_rwlock就是讀寫鎖
- 條件鎖:在滿足某個條件的時候進行加鎖或者解鎖毡证,iOS中可使用NSConditionLock來實現(xiàn)
- 遞歸鎖:可以被一個線程多次獲得,而不會引起死鎖蔫仙。它記錄了成功獲得鎖的次數(shù)料睛,每一次成功的獲得鎖,必須有一個配套的釋放鎖和其對應(yīng)摇邦,這樣才不會引起死鎖恤煞。只有當(dāng)所有的鎖被釋放之后,其他線程才可以獲得鎖施籍,iOS可使用NSRecursiveLock來實現(xiàn)
38. 鏈表和數(shù)組的區(qū)別是什么居扒?插入和查詢的時間復(fù)雜度分別是多少?<span id="38"></span>
鏈表和數(shù)組都是一個有序的集合丑慎,數(shù)組需要連續(xù)的內(nèi)存空間喜喂,而鏈表不需要瓤摧,鏈表的插入刪除的時間復(fù)雜度是O(1),數(shù)組是O(n)玉吁,根據(jù)下標(biāo)查詢的時間復(fù)雜度數(shù)組是O(1)照弥,鏈表是O(n),根據(jù)值查詢的時間復(fù)雜度,鏈表和數(shù)組都是O(n)
39. 哈希表是如何實現(xiàn)的进副?如何解決地址沖突产喉?<span id="39"></span>
哈希表是也是通過數(shù)組來實現(xiàn)的,首先對key值進行哈细一幔化得到一個整數(shù)曾沈,然后對整數(shù)進行計算,得到一個數(shù)組中的下標(biāo)鸥昏,然后進行存取塞俱,解決地址沖突常用方法有開放定址法和鏈表法。runtime源碼的存放weak指針哈希表使用的就是開放定址法吏垮,Java里的HashMap使用的是鏈表法障涯。
40. 排序題:冒泡排序,選擇排序膳汪,插入排序唯蝶,快速排序(二路,三路)能寫出那些遗嗽?<span id="40"></span>
這里簡單的說下幾種快速排序的不同之處粘我,隨機快排,是為了解決在近似有序的情況下痹换,時間復(fù)雜度會退化為O(n2),雙路快排是為了解決快速排序在大量數(shù)據(jù)重復(fù)的情況下征字,時間復(fù)雜度會退化為O(n2),三路快排是在大量數(shù)據(jù)重復(fù)的情況下娇豫,對雙路快排的一種優(yōu)化匙姜。
- 冒泡排序
extension Array where Element : Comparable{
public mutating func bubbleSort() {
let count = self.count
for i in 0..<count {
for j in 0..<(count - 1 - i) {
if self[j] > self[j + 1] {
(self[j], self[j + 1]) = (self[j + 1], self[j])
}
}
}
}
}
- 選擇排序
extension Array where Element : Comparable{
public mutating func selectionSort() {
let count = self.count
for i in 0..<count {
var minIndex = i
for j in (i+1)..<count {
if self[j] < self[minIndex] {
minIndex = j
}
}
(self[i], self[minIndex]) = (self[minIndex], self[i])
}
}
}
- 插入排序
extension Array where Element : Comparable{
public mutating func insertionSort() {
let count = self.count
guard count > 1 else { return }
for i in 1..<count {
var preIndex = i - 1
let currentValue = self[i]
while preIndex >= 0 && currentValue < self[preIndex] {
self[preIndex + 1] = self[preIndex]
preIndex -= 1
}
self[preIndex + 1] = currentValue
}
}
}
- 快速排序
extension Array where Element : Comparable{
public mutating func quickSort() {
func quickSort(left:Int, right:Int) {
guard left < right else { return }
var i = left + 1,j = left
let key = self[left]
while i <= right {
if self[i] < key {
j += 1
(self[i], self[j]) = (self[j], self[i])
}
i += 1
}
(self[left], self[j]) = (self[j], self[left])
quickSort(left: j + 1, right: right)
quickSort(left: left, right: j - 1)
}
quickSort(left: 0, right: self.count - 1)
}
}
- 隨機快排
extension Array where Element : Comparable{
public mutating func quickSort1() {
func quickSort(left:Int, right:Int) {
guard left < right else { return }
let randomIndex = Int.random(in: left...right)
(self[left], self[randomIndex]) = (self[randomIndex], self[left])
var i = left + 1,j = left
let key = self[left]
while i <= right {
if self[i] < key {
j += 1
(self[i], self[j]) = (self[j], self[i])
}
i += 1
}
(self[left], self[j]) = (self[j], self[left])
quickSort(left: j + 1, right: right)
quickSort(left: left, right: j - 1)
}
quickSort(left: 0, right: self.count - 1)
}
}
- 雙路快排
extension Array where Element : Comparable{
public mutating func quickSort2() {
func quickSort(left:Int, right:Int) {
guard left < right else { return }
let randomIndex = Int.random(in: left...right)
(self[left], self[randomIndex]) = (self[randomIndex], self[left])
var l = left + 1, r = right
let key = self[left]
while true {
while l <= r && self[l] < key {
l += 1
}
while l < r && key < self[r]{
r -= 1
}
if l > r { break }
(self[l], self[r]) = (self[r], self[l])
l += 1
r -= 1
}
(self[r], self[left]) = (self[left], self[r])
quickSort(left: r + 1, right: right)
quickSort(left: left, right: r - 1)
}
quickSort(left: 0, right: self.count - 1)
}
}
- 三路快排
// 三路快排
extension Array where Element : Comparable{
public mutating func quickSort3() {
func quickSort(left:Int, right:Int) {
guard left < right else { return }
let randomIndex = Int.random(in: left...right)
(self[left], self[randomIndex]) = (self[randomIndex], self[left])
var lt = left, gt = right
var i = left + 1
let key = self[left]
while i <= gt {
if self[i] == key {
i += 1
}else if self[i] < key{
(self[i], self[lt + 1]) = (self[lt + 1], self[i])
lt += 1
i += 1
}else {
(self[i], self[gt]) = (self[gt], self[i])
gt -= 1
}
}
(self[left], self[lt]) = (self[lt], self[left])
quickSort(left: gt + 1, right: right)
quickSort(left: left, right: lt - 1)
}
quickSort(left: 0, right: self.count - 1)
}
}
41. 鏈表題:如何檢測鏈表中是否有環(huán)?如何刪除鏈表中等于某個值的所有節(jié)點冯痢?<span id="41"></span>
- 如何檢測鏈表中是否有環(huán)氮昧?
public class ListNode {
public var val: Int
public var next: ListNode?
public init(_ val: Int) {
self.val = val
self.next = nil
}
}
extension ListNode {
var hasCycle: Bool {
var slow:ListNode? = self
var fast = self.next
while fast != nil {
if slow! === fast! {
return true
}
slow = slow?.next
fast = fast?.next?.next
}
return false
}
}
- 如何刪除鏈表中等于某個值的所有節(jié)點?
func remove(with value:Int, from listNode:ListNode?) -> ListNode? {
let tmpNode = ListNode(0)
tmpNode.next = listNode
var currentNode = tmpNode.next
var persiousNode:ListNode? = tmpNode
while currentNode != nil {
if let nodeValue = currentNode?.val, nodeValue == value {
persiousNode?.next = currentNode?.next
}else {
persiousNode = currentNode
}
currentNode = currentNode?.next
}
return tmpNode.next
}
42. 數(shù)組題:如何在有序數(shù)組中找出和等于給定值的兩個元素浦楣?如何合并兩個有序的數(shù)組之后保持有序袖肥?<span id="42"></span>
- 如何在有序數(shù)組中找出和等于給定值的兩個元素?LeetCode第167題
func twoSum(_ numbers: [Int], _ target: Int) -> [Int] {
var i = 0, j = numbers.count - 1
while i < j {
let sum = numbers[i] + numbers[j]
if sum == target {
return [i + 1, j + 1]
}else if sum > target {
j -= 1
}else {
i += 1
}
}
return []
}
- 如何合并兩個有序的數(shù)組之后保持有椒振?LeetCode第88題
func merge(_ nums1: inout [Int], _ m: Int, _ nums2: [Int], _ n: Int) {
for i in stride(from: n + m - 1, to: n - 1, by: -1) {
nums1[i] = nums1[i - n]
}
var i = 0, j = 0
while i < m && j < n {
if nums1[n + i] > nums2[j] {
nums1[i + j] = nums2[j]
j += 1
}else {
nums1[i + j] = nums1[n + i]
i += 1
}
}
while i < m {
nums1[i + j] = nums1[n + i]
i += 1
}
while j < n {
nums1[i + j] = nums2[j]
j += 1
}
}
43. 二叉樹題:如何反轉(zhuǎn)二叉樹昭伸?如何驗證兩個二叉樹是完全相等的梧乘?<span id="43"></span>
- 如何翻轉(zhuǎn)二叉樹澎迎?LeetCode第226題
func invertTree(_ root: TreeNode?) -> TreeNode? {
guard let root = root else { return nil }
(root.left, root.right) = (root.right, root.left)
invertTree(root.left)
invertTree(root.right)
return root
}
- 如何驗證兩個二叉樹是完全相等的庐杨?
func isSameTree(_ p: TreeNode?, _ q: TreeNode?) -> Bool {
guard let pNode = p ,let qNode = q else { return q == nil && p == nil }
return pNode.val == qNode.val && isSameTree(pNode.left, qNode.left) && isSameTree(pNode.right, qNode.right)
}
參考
iOS 模塊詳解—「Runloop 面試、工作」看我就 ?? 了 _.