iOS開(kāi)發(fā)-來(lái)自我的好友大廠面試題

這份面試題來(lái)自朋友的大廠的Interview以及我個(gè)人的答題

  1. 請(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)化的方案。

  1. 如果把上面的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為止。

  2. 如果下面這種情況請(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è)線程。

  1. 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ì)齊
  1. 編寫(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í)間變量安全操作。

  2. 為什么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)存泄露。

  3. 請(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修飾。

  4. 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í)即可。

  5. 編寫(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
    }
  1. 寫(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
            }
        }
    }
  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)題、緩存策略要更加人性化、智能化。
  1. 存在下面三個(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。

  1. 你為代碼改進(jìn)做了哪些工作
    答: 主要從性能架構(gòu)方面入手,畢竟移動(dòng)設(shè)備內(nèi)存才是重點(diǎn),加載時(shí)間、啟動(dòng)時(shí)間的優(yōu)化;模塊解藕、類(lèi)的設(shè)計(jì)是否合理等;屬性的訪問(wèn)方式是否合理等!一切工作就是為了APP低消耗、高性能。

以上答案純屬我個(gè)人理解,如果有不當(dāng)之處歡迎指正&交流。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問(wèn)題, 分享了一些自己做題目的經(jīng)驗(yàn)。 張土汪:刷leetcod...
    土汪閱讀 12,890評(píng)論 0 33
  • SwiftDay011.MySwiftimport UIKitprintln("Hello Swift!")var...
    smile麗語(yǔ)閱讀 4,078評(píng)論 0 6
  • 2014年的蘋(píng)果全球開(kāi)發(fā)者大會(huì)(WWDC),當(dāng)Craig Federighi向全世界宣布“We have new ...
    yeshenlong520閱讀 2,394評(píng)論 0 9
  • 第一章: JS簡(jiǎn)介 從當(dāng)初簡(jiǎn)單的語(yǔ)言,變成了現(xiàn)在能夠處理復(fù)雜計(jì)算和交互,擁有閉包、匿名函數(shù), 甚至元編程等...
    LaBaby_閱讀 1,751評(píng)論 0 6
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類(lèi)相關(guān)的語(yǔ)法,內(nèi)部類(lèi)的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 34,623評(píng)論 18 399

友情鏈接更多精彩內(nèi)容