iOS开发-来自我的好友大厂面试题

这份面试题来自朋友的大厂的Interview以及我个人的答题

  1. 请写出下面代码执行顺序以及每次执行前等待了多长时间?并解释下原因?
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是一个串行队列,每次按顺序执行,由于mian 异步的原因所以不会阻塞线程。
但是输出2和输出1、3是在两个不同loop周期完成的。
哈哈嵌套async的方式可以很好的把任务分散到多个周期执行,是一种优化的方案。

  1. 如果把上面的DispatchQueue.main.async都改成DispatchQueue.global().async是怎么输出呢?并解释下原因?
    答:在当今计算机多核情况下,DispatchQueue.global().async都是异步并行队列输出2 再下个loop周期 3 睡眠1秒输出1.
    注意:如果都没启动runloop的话,是不会执行的,直到启动loop为止。

  2. 如果下面这种情况请输出print输出顺序?并解释原因,如果maxConcurrentOperationCount = 1结果会是什么样子?

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一旦添加到队列中,任务就会被自动异步执行,所以当maxConcurrentOperationCount = 2时输出顺序为2、3睡眠2秒后输出1。
当maxConcurrentOperationCount = 1时输出顺序为2、睡眠2秒输出1、再输出3,因为maxConcurrentOperationCount = 1,1和3用的都是同一个线程。

  1. CGPoint在内存中的分配是如何的?
    CGPoint在OC中是一个结构体,结构体一般采用内存对齐的方式分配,比如:结构体内有char、float、int、long、double几种数据类型:
    char1个字节、float2个字节、int4个字节、long4个字节、double8个字节。
    在分配内存的时候按照变量顺序,变量存放的起始地址相对于结构体的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数,不够时填充。
    即结构体的size必然是最大变量类型字节数倍数。
    方式有两种:
  • 自然对界:默认的对齐方式,按结构体的成员中size最大的成员对齐。
  • 指定对界:使使用伪指令#pragma pack (n)指定如果结构体内的成员size不足对齐按照n的长度对齐,但是注意如果定义的n大于结构体成员最大size时则不起作用,结构体还是按照size最大成员对齐。
    结构体成员对齐
  1. 编写一个函数,不管调用多少次只执行一次?再写一个函数,在time时间内不论调用多少次,它只执行最后一次函数(debounce)?
    答:我快速想到的是定义一个static var flag条件判断来选择执行函数。类似在一个时间内控制Button被恶意点击发生BUG控制思路,一个判断条件bool中间变量和一个时间变量,当时间为>0时把bool变量设置为NO,不调用方法,直到时间为<=0时才调用,可以通过信号量保证时间变量安全操作。

  2. 为什么xib连接的property要用weak?用strong会有什么问题?
    答:因为xib创建的ViewController或者View,xib是强制持有的,xib连接的属性用weak修饰的话是为了防止相互持有导致谁都释放不了发生内存泄露(定义的属性ViewController或者View weak持有xib对象再则保证了xib生命周期和ViewController和View一样改用strong对象就变为强引用,谁都不能释放内存泄露。

  3. 请写出一段导致内存泄露的代码(越多越好)
    答:上面这题修饰词使用不当也会发生,blok里面持有self,self持有block、delegate用strong修饰、timer强制持有target,如果timer到点后不调用invalidate的话也会发生、两个对象相互引用用strong修饰。

  4. A、B两个label用autolayout横向布局,如何让文字过长时挤压A而不挤压B?
    答:设置A、B视图相对于父视图纵向居中,A距左为10px,B距父视图右边10px,A、B相距10px。由于label、imageView、UIButton遵循intrinsicContentSize在不设置大小的情况,指定了位置约束不会出错,现在A、B设置了间距但是由于文字过长的时候谁挤压谁这个是个问题,可以通过设置Content Hugging Priority 和 Content Compression Resistance Priority的优先级来使谁变大谁缩小。
    Content Hugging Priority: 该优先级表示一个控件抗被拉伸的优先级。优先级越高,越不容易被拉伸,默认是250。
    Content Compression Resistance Priority: 该优先级和上面那个优先级相对应,表示一个控件抗压缩的优先级。优先级越高,越不容易被压缩,默认是750。
    所以要实现挤压A不挤压B,就让B的抗压缩优先级大于A的抗压缩优先级即可。

  5. 编写一个函数,接受一个数组array作为参数,array中包含N个长度不等的升序数组,请将这N个数组合并,并保证合并后的数组也是升序。
    答:归并排序。

    //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]
     第一步:递归将一个大数组切割成N个数组,直到N个元素为1,典型分治思想。
     第二步:再将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. 写出快速排序、冒泡排序、选择排序、插入排序。
    // MARK: 快速排序 
    /**
     值类型
     传递的是参数的一个副本,这样在调用参数的过程中不会影响原始数据。
     
     引用类型
     把参数本身引用(内存地址)传递过去,在调用的过程会影响原始数据。
     在Swift众多数据类型中,只有class是引用类型,
     其余的如Int,Float,Bool,Character,Array,Set,enum,struct全都是值类型.
     inout:关键字修饰可以将一个值类型参数以引用方式传递。
     数内部实现改变外部参数
     传入参数时(调用函数时),在变量名字前面用&符号修饰表示。表明这个变量在参数内部是可以被改变的(可将改变传递到原始数据)
     注意:
     inout修饰的参数是不能有默认值的(比如list = [1, 2, 3]被赋予默认值),有范围的参数集合也不能被修饰;
     一个参数一旦被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. 设计一个可扩展性较强的的缓存池,可以通过传入不同算法实现FIFO、LRU等调度方式。请描述思路并附上伪代码。
    答:扩展性强可采用protocol,数据实现了这个协议即可。
    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、或者Mix,采用双向链表的方式根据不同的算法,操作缓存池里面的数据。
b. 比如LRU(使用最少移出),每次新数据直接插入到链表头部,每次数据被使用时移动到链表头部,每次缓存满了或者内存不足时移除链表尾部节点。
c. FIFO(先进先出)原则,每次把数据插入到链表头部,每次移除尾部节点。
d. 当然实际中还需要考虑更多更详细的细节,比如数据量过大缓存污染的问题、缓存策略要更加人性化、智能化。
  1. 存在下面三个接口:
fetchStory(StoryId) -> List<ChapterId>
fetchChapter(ChapterId) -> Chapter
output(Chapter)

设置一套文章请求框架,画图加伪代码
答:a. output应该根据章节缓存顺序执行。
b. try-catch之后应该取消后续操作。
c. 请求并发最多为2。

  1. 你为代码改进做了哪些工作
    答: 主要从性能架构方面入手,毕竟移动设备内存才是重点,加载时间、启动时间的优化;模块解藕、类的设计是否合理等;属性的访问方式是否合理等!一切工作就是为了APP低消耗、高性能。

以上答案纯属我个人理解,如果有不当之处欢迎指正&交流。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容

  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,743评论 0 33
  • SwiftDay011.MySwiftimport UIKitprintln("Hello Swift!")var...
    smile丽语阅读 3,830评论 0 6
  • 2014年的苹果全球开发者大会(WWDC),当Craig Federighi向全世界宣布“We have new ...
    yeshenlong520阅读 2,283评论 0 9
  • 第一章: JS简介 从当初简单的语言,变成了现在能够处理复杂计算和交互,拥有闭包、匿名函数, 甚至元编程等...
    LaBaby_阅读 1,658评论 0 6
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,605评论 18 399