這份面試題來(lái)自朋友的大廠的Interview以及我個(gè)人的答題
- 請(qǐng)寫(xiě)出下面代碼執(zhí)行順序以及每次執(zhí)行前等待了多長(zhǎng)時(shí)間?并解釋下原因?
DispatchQueue.main.async {
DispatchQueue.main.async {
sleep(2)
print("1"+"\(Thread.current)")
}
print("2" + "\(Thread.current)")
DispatchQueue.main.async {
print("3" + "\(Thread.current)")
}
}
sleep(1)
答:等待1秒輸出2,等待兩秒輸出1,再輸出3,main是一個(gè)串行隊(duì)列,每次按順序執(zhí)行,由于mian 異步的原因所以不會(huì)阻塞線程。
但是輸出2和輸出1、3是在兩個(gè)不同loop周期完成的。
哈哈嵌套async的方式可以很好的把任務(wù)分散到多個(gè)周期執(zhí)行,是一種優(yōu)化的方案。
如果把上面的DispatchQueue.main.async都改成DispatchQueue.global().async是怎么輸出呢?并解釋下原因?
答:在當(dāng)今計(jì)算機(jī)多核情況下,DispatchQueue.global().async都是異步并行隊(duì)列輸出2 再下個(gè)loop周期 3 睡眠1秒輸出1.
注意:如果都沒(méi)啟動(dòng)runloop的話,是不會(huì)執(zhí)行的,直到啟動(dòng)loop為止。如果下面這種情況請(qǐng)輸出print輸出順序?并解釋原因,如果maxConcurrentOperationCount = 1結(jié)果會(huì)是什么樣子?
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2
queue.addOperation {
queue.addOperation {
sleep(2)
print("1"+"\(Thread.current)")
}
print("2"+"\(Thread.current)")
queue.addOperation {
print("3"+"\(Thread.current)")
}
}
sleep(2)
答: addOperation一旦添加到隊(duì)列中,任務(wù)就會(huì)被自動(dòng)異步執(zhí)行,所以當(dāng)maxConcurrentOperationCount = 2時(shí)輸出順序?yàn)?、3睡眠2秒后輸出1。
當(dāng)maxConcurrentOperationCount = 1時(shí)輸出順序?yàn)?、睡眠2秒輸出1、再輸出3,因?yàn)閙axConcurrentOperationCount = 1,1和3用的都是同一個(gè)線程。
- CGPoint在內(nèi)存中的分配是如何的?
CGPoint在OC中是一個(gè)結(jié)構(gòu)體,結(jié)構(gòu)體一般采用內(nèi)存對(duì)齊的方式分配,比如:結(jié)構(gòu)體內(nèi)有char、float、int、long、double幾種數(shù)據(jù)類(lèi)型:
char1個(gè)字節(jié)、float2個(gè)字節(jié)、int4個(gè)字節(jié)、long4個(gè)字節(jié)、double8個(gè)字節(jié)。
在分配內(nèi)存的時(shí)候按照變量順序,變量存放的起始地址相對(duì)于結(jié)構(gòu)體的起始地址的偏移量必須為該變量的類(lèi)型所占用的字節(jié)數(shù)的倍數(shù),不夠時(shí)填充。
即結(jié)構(gòu)體的size必然是最大變量類(lèi)型字節(jié)數(shù)倍數(shù)。
方式有兩種:
- 自然對(duì)界:默認(rèn)的對(duì)齊方式,按結(jié)構(gòu)體的成員中size最大的成員對(duì)齊。
- 指定對(duì)界:使使用偽指令
#pragma pack (n)指定如果結(jié)構(gòu)體內(nèi)的成員size不足對(duì)齊按照n的長(zhǎng)度對(duì)齊,但是注意如果定義的n大于結(jié)構(gòu)體成員最大size時(shí)則不起作用,結(jié)構(gòu)體還是按照size最大成員對(duì)齊。
結(jié)構(gòu)體成員對(duì)齊
編寫(xiě)一個(gè)函數(shù),不管調(diào)用多少次只執(zhí)行一次?再寫(xiě)一個(gè)函數(shù),在time時(shí)間內(nèi)不論調(diào)用多少次,它只執(zhí)行最后一次函數(shù)(debounce)?
答:我快速想到的是定義一個(gè)static var flag條件判斷來(lái)選擇執(zhí)行函數(shù)。類(lèi)似在一個(gè)時(shí)間內(nèi)控制Button被惡意點(diǎn)擊發(fā)生BUG控制思路,一個(gè)判斷條件bool中間變量和一個(gè)時(shí)間變量,當(dāng)時(shí)間為>0時(shí)把bool變量設(shè)置為NO,不調(diào)用方法,直到時(shí)間為<=0時(shí)才調(diào)用,可以通過(guò)信號(hào)量保證時(shí)間變量安全操作。為什么xib連接的property要用weak?用strong會(huì)有什么問(wèn)題?
答:因?yàn)閤ib創(chuàng)建的ViewController或者View,xib是強(qiáng)制持有的,xib連接的屬性用weak修飾的話是為了防止相互持有導(dǎo)致誰(shuí)都釋放不了發(fā)生內(nèi)存泄露(定義的屬性ViewController或者View weak持有xib對(duì)象再則保證了xib生命周期和ViewController和View一樣改用strong對(duì)象就變?yōu)閺?qiáng)引用,誰(shuí)都不能釋放內(nèi)存泄露。請(qǐng)寫(xiě)出一段導(dǎo)致內(nèi)存泄露的代碼(越多越好)
答:上面這題修飾詞使用不當(dāng)也會(huì)發(fā)生,blok里面持有self,self持有block、delegate用strong修飾、timer強(qiáng)制持有target,如果timer到點(diǎn)后不調(diào)用invalidate的話也會(huì)發(fā)生、兩個(gè)對(duì)象相互引用用strong修飾。A、B兩個(gè)label用autolayout橫向布局,如何讓文字過(guò)長(zhǎng)時(shí)擠壓A而不擠壓B?
答:設(shè)置A、B視圖相對(duì)于父視圖縱向居中,A距左為10px,B距父視圖右邊10px,A、B相距10px。由于label、imageView、UIButton遵循intrinsicContentSize在不設(shè)置大小的情況,指定了位置約束不會(huì)出錯(cuò),現(xiàn)在A、B設(shè)置了間距但是由于文字過(guò)長(zhǎng)的時(shí)候誰(shuí)擠壓誰(shuí)這個(gè)是個(gè)問(wèn)題,可以通過(guò)設(shè)置Content Hugging Priority 和 Content Compression Resistance Priority的優(yōu)先級(jí)來(lái)使誰(shuí)變大誰(shuí)縮小。
Content Hugging Priority: 該優(yōu)先級(jí)表示一個(gè)控件抗被拉伸的優(yōu)先級(jí)。優(yōu)先級(jí)越高,越不容易被拉伸,默認(rèn)是250。
Content Compression Resistance Priority: 該優(yōu)先級(jí)和上面那個(gè)優(yōu)先級(jí)相對(duì)應(yīng),表示一個(gè)控件抗壓縮的優(yōu)先級(jí)。優(yōu)先級(jí)越高,越不容易被壓縮,默認(rèn)是750。
所以要實(shí)現(xiàn)擠壓A不擠壓B,就讓B的抗壓縮優(yōu)先級(jí)大于A的抗壓縮優(yōu)先級(jí)即可。編寫(xiě)一個(gè)函數(shù),接受一個(gè)數(shù)組array作為參數(shù),array中包含N個(gè)長(zhǎng)度不等的升序數(shù)組,請(qǐng)將這N個(gè)數(shù)組合并,并保證合并后的數(shù)組也是升序。
答:歸并排序。
//MARK: 歸并排序
/**
8 4 5 3 1 2
8 4 5 3 1 2
8 4 5 3 1 2
4 5 1 2 ---> [1 2]-->[3] [1 2]
第一步:遞歸將一個(gè)大數(shù)組切割成N個(gè)數(shù)組,直到N個(gè)元素為1,典型分治思想。
第二步:再將N個(gè)數(shù)組遞歸合并一個(gè)大序列,合并過(guò)程中做好排序。
缺點(diǎn):額外空間和N成正比。
*/
func mergeSort(_ list: Array<Int>) -> [Int]
{
if list.count == 1 { return list}
let middleIndex = list.count / 2
let leftArray: Array<Int> = self.mergeSort(Array(list[0..<middleIndex]))
let rightArray: Array<Int> = self.mergeSort(Array(list[middleIndex..<list.count]))
return merge(leftArray: leftArray, rightArray: rightArray)
}
func merge(leftArray: Array<Int>, rightArray: Array<Int>) -> [Int]
{
var leftIndex = 0
var rightIndex = 0
var orderArray: Array<Int> = Array()
while leftIndex < leftArray.count && rightIndex < rightArray.count {
if leftArray[leftIndex] > rightArray[rightIndex] {
orderArray.append(leftArray[leftIndex])
leftIndex = leftIndex + 1
}else if leftArray[leftIndex] < rightArray[rightIndex] {
orderArray.append(rightArray[rightIndex])
rightIndex = rightIndex + 1
}else {
orderArray.append(leftArray[leftIndex])
leftIndex = leftIndex + 1
orderArray.append(rightArray[rightIndex])
rightIndex = rightIndex + 1
}
}
while leftIndex < leftArray.count {
orderArray.append(leftArray[leftIndex])
leftIndex = leftIndex + 1
}
while rightIndex < rightArray.count {
orderArray.append(rightArray[rightIndex])
rightIndex = rightIndex + 1
}
return orderArray
}
- 寫(xiě)出快速排序、冒泡排序、選擇排序、插入排序。
// MARK: 快速排序
/**
值類(lèi)型
傳遞的是參數(shù)的一個(gè)副本,這樣在調(diào)用參數(shù)的過(guò)程中不會(huì)影響原始數(shù)據(jù)。
引用類(lèi)型
把參數(shù)本身引用(內(nèi)存地址)傳遞過(guò)去,在調(diào)用的過(guò)程會(huì)影響原始數(shù)據(jù)。
在Swift眾多數(shù)據(jù)類(lèi)型中,只有class是引用類(lèi)型,
其余的如Int,Float,Bool,Character,Array,Set,enum,struct全都是值類(lèi)型.
inout:關(guān)鍵字修飾可以將一個(gè)值類(lèi)型參數(shù)以引用方式傳遞。
數(shù)內(nèi)部實(shí)現(xiàn)改變外部參數(shù)
傳入?yún)?shù)時(shí)(調(diào)用函數(shù)時(shí)),在變量名字前面用&符號(hào)修飾表示。表明這個(gè)變量在參數(shù)內(nèi)部是可以被改變的(可將改變傳遞到原始數(shù)據(jù))
注意:
inout修飾的參數(shù)是不能有默認(rèn)值的(比如list = [1, 2, 3]被賦予默認(rèn)值),有范圍的參數(shù)集合也不能被修飾;
一個(gè)參數(shù)一旦被inout修飾,就不能再被var和let修飾了。
*/
func quickSort(list: inout Array<Int>)
{
quickRecursive(list: &list, leftIndex: 0, rightIndex: list.count - 1)
}
func quickRecursive(list: inout Array<Int>, leftIndex: Int, rightIndex: Int)
{
if leftIndex >= rightIndex {
return
}
var i = leftIndex + 1
var j = rightIndex
let pivot = list[leftIndex]
while i < j {
while i < list.count && list[i] > pivot {
i = i + 1
}
while j >= 0 && list[j] < pivot {
j = j - 1
}
if i < j {
swap(object1: &list[i], object2: &list[j])
}
}
swap(object1: &list[j], object2: &list[leftIndex])
quickRecursive(list: &list, leftIndex: leftIndex, rightIndex: j - 1)
quickRecursive(list: &list, leftIndex: j + 1, rightIndex: rightIndex)
}
func swap(object1: inout Int, object2: inout Int)
{
let temp = object1
object1 = object2
object2 = temp
}
// MARK: 冒泡排序
func bubbleSort(list: inout Array<Int>) {
if list.count == 1 {
return
}
//左歸排序
for i in 0..<list.count - 1 {
for j in 0..<list.count - i - 1 {
if list[j] > list[j + 1] && j < list.count {
swap(object1: &list[j], object2: &list[j+1])
}
}
}
//右歸排序
for i in 0..<list.count - 1
{
for j in i..<list.count
{
if list[i] < list[j] {
swap(object1: &list[i], object2: &list[j])
}
}
}
}
//MARK: 選擇排序
func selectionSort(list: inout Array<Int>) {
if list.count == 1 {
return
}
for i in 0..<list.count {
var minItem = list[i]
var index = i
for j in (i + 1)..<list.count {
if list[j] < minItem {
minItem = list[j]
index = j
}
}
if index != i {
swap(object1: &list[index], object2: &list[i])
}
}
}
//MARK: 插入排序[8 4 3 5 2 1]
func insertionSort(list: inout Array<Int>)
{
if list.count == 1 { return }
for i in 1..<list.count {
var j = i
while j > 0 && list[j - 1] > list[j] {
swap(object1: &list[j - 1], object2: &list[j])
j = j - 1
}
}
}
- 設(shè)計(jì)一個(gè)可擴(kuò)展性較強(qiáng)的的緩存池,可以通過(guò)傳入不同算法實(shí)現(xiàn)FIFO、LRU等調(diào)度方式。請(qǐng)描述思路并附上偽代碼。
答:擴(kuò)展性強(qiáng)可采用protocol,數(shù)據(jù)實(shí)現(xiàn)了這個(gè)協(xié)議即可。
protocol Cacheable {
var priority: Int {get}
var node: Any {get}
var key: String {get}
}
class LinkedMapNode: Cacheable {
var prev: LinkedMapNode?
var next: LinkedMapNode?
var cost: Int?
var time: TimeInterval?
}
a. 定義一組枚舉FIFO、LRU、或者M(jìn)ix,采用雙向鏈表的方式根據(jù)不同的算法,操作緩存池里面的數(shù)據(jù)。
b. 比如LRU(使用最少移出),每次新數(shù)據(jù)直接插入到鏈表頭部,每次數(shù)據(jù)被使用時(shí)移動(dòng)到鏈表頭部,每次緩存滿了或者內(nèi)存不足時(shí)移除鏈表尾部節(jié)點(diǎn)。
c. FIFO(先進(jìn)先出)原則,每次把數(shù)據(jù)插入到鏈表頭部,每次移除尾部節(jié)點(diǎn)。
d. 當(dāng)然實(shí)際中還需要考慮更多更詳細(xì)的細(xì)節(jié),比如數(shù)據(jù)量過(guò)大緩存污染的問(wèn)題、緩存策略要更加人性化、智能化。
- 存在下面三個(gè)接口:
fetchStory(StoryId) -> List<ChapterId>
fetchChapter(ChapterId) -> Chapter
output(Chapter)
設(shè)置一套文章請(qǐng)求框架,畫(huà)圖加偽代碼
答:a. output應(yīng)該根據(jù)章節(jié)緩存順序執(zhí)行。
b. try-catch之后應(yīng)該取消后續(xù)操作。
c. 請(qǐng)求并發(fā)最多為2。
- 你為代碼改進(jìn)做了哪些工作
答: 主要從性能架構(gòu)方面入手,畢竟移動(dòng)設(shè)備內(nèi)存才是重點(diǎn),加載時(shí)間、啟動(dòng)時(shí)間的優(yōu)化;模塊解藕、類(lèi)的設(shè)計(jì)是否合理等;屬性的訪問(wèn)方式是否合理等!一切工作就是為了APP低消耗、高性能。