美文网首页
四道iOS面试题-解决面试官的底层难题

四道iOS面试题-解决面试官的底层难题

作者: iOS丶lant | 来源:发表于2021-09-28 16:01 被阅读0次

日常扯淡

第一次面试大公司: 饿了么, 收到大公司的召唤非常的兴奋, 觉得自己翻身的机会终于要来了, 兴冲冲的跑去面试, 以为会和一般初级iOS面试的题目相同, 没有做任何的准备, 其实也不知道准备什么, 记得那时候聊的是:

  • UI方面: 如何避免卡顿掉帧, 异步渲染.

  • 性能方面: 性能优化, Vsync, CPU / GPU

  • 网络方面: 如何进行请求缓存策略.

  • 安全方面: lild重签名, Mach-O.

  • 前端方面: 如何避免DOM重绘.

  • 后端方面: 如何进行负载均衡的处理.

第二次面试大公司: 京东, 由于有了上一次的经验, 我变得非常的淡定, 知道自己肯定会被大公司所淘汰, 和优等专业生有着不可逾越的天堑, 比较吃惊的是, 进入京东的大楼需要用身份证换取临时门禁... 那时候的面试题就比饿了么的柔和的多了, 虽然当时还是回答不出.

  • Runtime: isa, 消息转发, 弱引用表.

  • Runloop: mode, timer.

  • Block: __block, __forwording.

  • Property: assign, weak, copy.

  • Category: assoc, load

现在想想, 这TM才是面试iOS啊, 只可惜, 那时候并不具备这些知识, 可惜了了. 之后我就对C++, ASM, Linux, 这三方面进行了学习, 也学习了些MACH-O, 逆向的相关的知识, 以备后面的面试机会.
第三次我准备了所有我能够准备的面试题内容, 底层原理, 逆向安全, 多线程与锁, 内存布局, UI 性能优化等, 果不其然, 足足1个小时的电话面试, 我轻松通过, 问的就是些我准备好的底层知识, 让我觉得机会终于来了, 可是.... 当我现场面试后... 题目全部是上机题...

  • 设计一个网络框架, 如何进行不同数据解析的设计(header, body), 并能够进行自定义, 重连机制如何处理, 状态码错误转发机制的处理, 如何避免回调地狱, 实现Promise的自实现.
  • 根据UIControl实现UIButton....
  • 找到两个排序数组的中位数...
  • pow(double, double)函数的自实现....

果然,网络, UI, 算法... 好吧, 第一次做上机题, 瞬间就蒙了... 然后就是面试官在旁边不停的笑... 不停的笑... 可能是他对我友好的一种方式吧... 最后的面试结论是, 我的知识面还是比较广的, 做过的东西也是挺多的, 但是在知识深度方面还是比较欠缺.

面试题1 自实现pow(double, double)

这道题目上机的时候非常的蒙, 因为幂是double, 完全不知道如何下手, 面试官就降低难度使用整型.

解法1

func _pow_1(_ base: Int, _ exponent: Int) -> Int {

    if exponent < 0 {
        return 0
    }
    if exponent == 0 {
        return 0
    }
    if exponent == 1 {
        return base
    }
    
    var result = base
    for _ in 1..<exponent {
        result *= base
    }
    return result
}

然后, 第一次做算法题的我, 只能想到通过最为粗糙的办法解答, 就是一个循环, 我也知道这不是面试官所期许的答案, 但这有什么办法呢...

解法2

func _pow_2(_ base: Double, _ exponent: Int) -> Double {
    
    if exponent < 0 {
        return 0
    }
    if exponent == 0 {
        return 0
    }
    var ans:Double = 1, last_pow = base, exp = exponent
    
    while exp > 0 {
        if (exp & 1) != 0 {
            ans = ans * last_pow
        }
        exp = exp >> 1
        last_pow = last_pow * last_pow
    }
    return ans
}

这个是我在网上翻阅资料后的另一种看似比较好的解答方式.

解法3

func _pow_3(_ base: Double, _ exponent: Int) -> Double {

    var isNegative = false
    var exp = exponent
    if exp < 0 {
        isNegative = true
        exp = -exp
    }
    let result = _pow_2(base, exp)
    return isNegative ? 1 / result : result
}

这个仅仅是加了一个负值判断.... 但是幂是double的仍然是毫无头绪, 需要请大佬和大神不吝赐教.

面试题2 findMedianSortedArrays

这是一道LeetCode的原题, 但是我至今还没有刷过算法题库... 也是平生第一次面试的时候遇到算法题. 题目的意思是找到两个排序数组的中位数.

解法1
func findMedianSortedArrays_1(_ array1: [Int], _ array2: [Int]) -> Double {

    var array = [Int]()
    array.append(contentsOf: array1)
    array.append(contentsOf: array2)

    quickSort(list: &array)

    let b = array.count % 2
    let c = array.count
    var result = 0.0;
    if  b == 1  {
        result = Double(array[c / 2])
    } else {
        let n1 = array[c / 2 - 1]
        let n2 = array[c / 2]
        result = Double((n1 + n2)) / 2.0
    }

    return result
}

第一次做算法题, 只能无视算法复杂度, 能够完成就算是不错了, 要什么自行车...

解法2
func findMedianSortedArrays_2(_ array1: [Int], _ array2: [Int]) -> Double {

    let c1 = array1.count, c2 = array2.count
    var a1 = array1, a2 = array2
    if c1 <= 0 && c2 <= 0 {
        return 0.0
    }

    func findKth(_ nums1: inout [Int], i: Int, _ nums2: inout [Int], j: Int, k: Int) -> Double {
        if nums1.count - i > nums2.count - j {
            return findKth(&nums2, i: j, &nums1, j: i, k: k)
        }
        if nums1.count == i {
            return Double(nums2[j + k - 1])
        }
        if k == 1 {
            return Double(min(nums1[i], nums2[j]))
        }
        let pa = min(i + k / 2, nums1.count), pb = j + k - pa + i
        if nums1[pa - 1] < nums2[pb - 1] {
            return findKth(&nums1, i: pa, &nums2, j: j, k: k - pa + i)
        } else if nums1[pa - 1] > nums2[pb - 1] {
            return findKth(&nums1, i: i, &nums2, j: pb, k: k - pb + j)
        } else {
            return Double(nums1[pa - 1])
        }
    }

    let total = c1 + c2
    if total % 2 == 1 {
        return findKth(&a1, i: 0, &a2, j: 0, k: total / 2 + 1)
    } else {
        return (findKth(&a1, i: 0, &a2, j: 0, k: total / 2) + findKth(&a1, i: 0, &a2, j: 0, k: total / 2 + 1)) / 2.0
    }
}

这个是我在网上查资料的时候的答案... 还没理清是个什么思路, 反正面试官提示分而治之, 掐头去尾... 也不知道是不是最优算法.

解法3
func findMedianSortedArrays_3(_ array1: [Int], _ array2: [Int]) -> Double {

    let total = array1.count + array2.count
    let index = total / 2
    let count = array1.count < array2.count ? array2.count : array1.count
    var array = [Int]()

    var i = 0, j = 0;
    for _ in 0...count {
        if array.count >= index + 1 {
            break
        }
        if array1[i] < array2[j] {
            array.append(array1[i])
            i += 1
        } else {
            array.append(array2[j])
            j += 1
        }
    }
    return total % 2 == 1 ? Double(array[index]) : Double(array[index] + array[index - 1]) * 0.5
}

这个是请教霜神(@halfrost-一缕殇流化隐半边冰霜)后给的思路, 的确很好实现. 但霜神谦虚的说不是最优解....

奇数测试
var array1 = randomList(1000001)
var array2 = randomList(1000000)
quickSort(list: &array1)
quickSort(list: &array2)
print(findMedianSortedArrays_1(array1, array2))
print(findMedianSortedArrays_2(array1, array2))
print(findMedianSortedArrays_3(array1, array2))
--- scope of: findMedianSortedArrays ---
500045.0
500045.0
500045.0
偶数测试
var array1 = randomList(1000001)
var array2 = randomList(1000000)
quickSort(list: &array1)
quickSort(list: &array2)
print(findMedianSortedArrays_1(array1, array2))
print(findMedianSortedArrays_2(array1, array2))
print(findMedianSortedArrays_3(array1, array2))
--- scope of: findMedianSortedArrays ---
499665.5
499665.5
499665.5
耗时比较
--- scope of: findMedianSortedArrays_1 ---
timing: 2.50845623016357
--- scope of: findMedianSortedArrays_2 ---
timing: 1.28746032714844e-05
--- scope of: findMedianSortedArrays_3 ---
timing: 0.0358490943908691

可以看出网上查资料的答案是三种解法里性能最高的算法, 霜神的思路和网上的答案差了三个数量级, 而我写的差了五个数量级.... 果然我写的果然是最为垃圾的算法....

解法4
@discardableResult func findMedianSortedArrays_4(_ array1: [Int], _ array2: [Int]) -> Double {

    if array1.count == 0 {
        if array2.count % 2 == 1 {
            return Double(array2[array2.count / 2])
        } else {
            return Double(array2[array2.count / 2] + array2[array2.count / 2 - 1]) * 0.5
        }
    } else if array2.count == 0 {
        if array1.count % 2 == 1 {
            return Double(array1[array1.count / 2])
        } else {
            return Double(array1[array1.count / 2] + array1[array1.count / 2 - 1]) * 0.5
        }
    }

    let total = array1.count + array2.count
    let count = array1.count < array2.count ? array1.count : array2.count
    let odd = total % 2 == 1

    var i = 0, j = 0, f = 1, m1 = 0.0, m2 = 0.0, result = 0.0;
    for _ in 0...count {
        if odd { array1[i] < array2[j] ? (i += 1) : (j += 1) }
        if f >= total / 2 {
            if odd {
                result = array1[i] < array2[j] ? Double(array1[i]) : Double(array2[j])
            } else {
                if array1[i] < array2[j] {
                    m1 = Double(array1[i])
                    if (i + 1) < array1.count && array1[i + 1] < array2[j] {
                        m2 = Double(array1[i + 1])
                    } else {
                        m2 = Double(array2[j])
                    }
                } else {
                    m1 = Double(array2[j])
                    if (j + 1) < array2.count && array2[j + 1] < array1[i] {
                        m2 = Double(array2[j + 1])
                    } else {
                        m2 = Double(array1[i])
                    }
                }
                result = (m1 + m2) * 0.5
            }
            break
        }
        if !odd { array1[i] < array2[j] ? (i += 1) : (j += 1) }
        f += 1
    }
    return result
}
--- scope of: findMedianSortedArrays_3 ---
timing: 0.0358932018280029
--- scope of: findMedianSortedArrays_4 ---
timing: 0.0241639614105225

沿着霜神的思路和面试官给的提示, 给出了上面的算法, 但是解法3的数量级是相同的

如果你正在跳槽或者正准备跳槽不妨动动小手,添加一下咱们的交流群1012951431来获取一份详细的大厂面试资料为你的跳槽多添一份保障。

面试题3 UIContorl -> UIButton

protocol ButtonInterface {
    func setTitle(_ title: String);
    func setTitleColor(_ titleColor: UIColor);
    func setTitleEdgeInsets(_ edgeInsets: UIEdgeInsets);
    func setImage(_ image: UIImage);
    func setBackgroundImage(_ image: UIImage);
    func setImageEdgeInsets(_ edgeInsets: UIEdgeInsets);
}

class Button: UIControl, ButtonInterface { 

}

以上就是面试时候的原题, 一开始根本不知道是要让我做些什么, 说是只要让我把上面的方法全部实现就好了, 就像实现一个自己的UIButton... 一开始, 我以为是要我用CALayer来实现, 吓的我瑟瑟发抖... 还好不是... 然后, 我就按照自己平时自定义控件的写法, 写了一个UIImageView, 一个UILabel, 然后布局赋值... 就看到面试官对着我笑着说, 你以为这道题这么简单么... 这么简单么...
然后说, 你知道UIButton setTitle的时候才会创建UILabel, setImage的时候才会创建UIImageView, 你为什么吧frame给写死... 不知道UIViewsizeToFit么, 你怎么不实现sizeThatFits, 你是完全不会用吧... 你知道UIButtonAutoLayout布局的时候只要设置origin坐标, 宽高就可以自适应了, 你自定义的时候怎么不实现呢? setBackgroundImagesetImageEdgeInsets你就不要做了吧, 反正你也不会...
我想说的是,谁没事放着UIButton不用, 用UIContorl这种东西... 就为了一个target-action的设计模式么... 我每次在想思路的时候一直打断我, 可能这是面试官的一种策略吧... 算了不吐槽了, 还是尽力实现吧.

import UIKit

protocol ButtonInterface {
    func setTitle(_ title: String);
    func setTitleColor(_ titleColor: UIColor);
    func setTitleEdgeInsets(_ edgeInsets: UIEdgeInsets);
    func setImage(_ image: UIImage);
    func setBackgroundImage(_ image: UIImage);
    func setImageEdgeInsets(_ edgeInsets: UIEdgeInsets);
}

class Button: UIControl, ButtonInterface {

    lazy var titleLabel: UILabel = UILabel()
    lazy var imageView: UIImageView = UIImageView()
    lazy var backgroundImageView: UIImageView = UIImageView()
    
    var titleLabelIsCreated = false
    var imageViewIsCreated = false
    var backgroundImageViewCreated = false
    
    internal func setTitle(_ text: String) {
        if !titleLabelIsCreated {
            addSubview(titleLabel)
            titleLabelIsCreated = true
        }
        titleLabel.text = text
    }

    internal func setTitleColor(_ textColor: UIColor) {
        if !titleLabelIsCreated {
            return
        }
        titleLabel.textColor = textColor
    }
    
    internal func setTitleEdgeInsets(_ edgeInsets: UIEdgeInsets) {
        if !titleLabelIsCreated {
            return
        }
    }
    
    internal func setImage(_ image: UIImage) {
        if !imageViewIsCreated {
            addSubview(imageView)
            imageViewIsCreated = true
        }
        imageView.image = image
    }
    
    internal func setBackgroundImage(_ image: UIImage) {
        if !backgroundImageViewCreated {
            addSubview(backgroundImageView)
            insertSubview(backgroundImageView, at: 0)
            backgroundImageViewCreated = true
        }
        backgroundImageView.image = image
    }
    
    internal func setImageEdgeInsets(_ edgeInsets: UIEdgeInsets) {
        if !imageViewIsCreated {
            return
        }
    }
    
    override func sizeThatFits(_ size: CGSize) -> CGSize {
        if titleLabelIsCreated && !imageViewIsCreated && !backgroundImageViewCreated {
            let text: NSString? = titleLabel.text as NSString?
            let titleLabelW: CGFloat = text?.boundingRect(with: CGSize(width: CGFloat(MAXFLOAT), height: bounds.height), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font : titleLabel.font], context: nil).size.width ?? 0.0
            let titleLabelH: CGFloat = text?.boundingRect(with: CGSize(width: titleLabelW, height: CGFloat(MAXFLOAT)), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font : titleLabel.font], context: nil).size.height ?? 0.0
            return CGSize(width: titleLabelW, height: titleLabelH + 10)
        } else if !titleLabelIsCreated && imageViewIsCreated {
            return imageView.image?.size ?? CGSize.zero
        } else if titleLabelIsCreated && imageViewIsCreated {
            let text: NSString? = titleLabel.text as NSString?
            let titleLabelW: CGFloat = text?.boundingRect(with: CGSize(width: CGFloat(MAXFLOAT), height: bounds.height), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font : titleLabel.font], context: nil).size.width ?? 0.0
            let titleLabelH: CGFloat = text?.boundingRect(with: CGSize(width: titleLabelW, height: CGFloat(MAXFLOAT)), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font : titleLabel.font], context: nil).size.height ?? 0.0
            let imageViewW: CGFloat = imageView.image?.size.width ?? 0.0
            let imageViewH: CGFloat = imageView.image?.size.height ?? 0.0
            return CGSize(width: titleLabelW + imageViewW, height: imageViewH > titleLabelH ? imageViewH : titleLabelH)
        } else {
            return backgroundImageView.image?.size ?? CGSize.zero
        }
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()

        if titleLabelIsCreated && !imageViewIsCreated {
            titleLabel.frame = bounds
            titleLabel.textAlignment = .center
        } else if !titleLabelIsCreated && imageViewIsCreated {
            let y: CGFloat = 0;
            let width: CGFloat = imageView.image?.size.width ?? 0;
            let x: CGFloat = (bounds.width - width) * 0.5;
            let height: CGFloat = bounds.height;
            imageView.frame = CGRect(x: x, y: y, width: width, height: height)
        } else if titleLabelIsCreated && imageViewIsCreated {
            let imageViewY: CGFloat = 0;
            let imageViewW: CGFloat = imageView.image?.size.width ?? 0;
            let imageViewH: CGFloat = bounds.height;
            let text: NSString? = titleLabel.text as NSString?
            let titleLabelW: CGFloat = text?.boundingRect(with: CGSize(width: CGFloat(MAXFLOAT), height: bounds.height), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font : titleLabel.font], context: nil).size.width ?? 0.0
            let titleLabelH: CGFloat = text?.boundingRect(with: CGSize(width: titleLabelW, height: CGFloat(MAXFLOAT)), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font : titleLabel.font], context: nil).size.height ?? 0.0
            let imageViewX: CGFloat = (bounds.width - imageViewW - titleLabelW) * 0.5;
            let titleLabelX: CGFloat = imageViewX + imageViewW
            let titleLabelY = (bounds.height - titleLabelH) * 0.5
            titleLabel.frame = CGRect(x: titleLabelX, y: titleLabelY, width: titleLabelW, height: titleLabelH)
            imageView.frame = CGRect(x: imageViewX, y: imageViewY, width: imageViewW, height: imageViewH)
        }
        
        if backgroundImageViewCreated {
            backgroundImageView.frame = bounds
        }
    }
}

虽然实现了部分的功能, 但是AutoLayout和EdgeInsets的功能还是没有思路, 还请各位大佬解惑.

面试题4 网络架构实现

这道题真是戳中我的软肋, 网络与多线程是我最为薄弱的地方, 现在对线程的理解应该已经不错了, 但是网络还是有所欠缺的, 所以, 我会在今后学习Linux和精读AFNetWorking&& SDWebImage后, 自己写一个网络架构来进行自我提升.

最后 本文中所有的源码都可以在github上找到:

GitHub Repo:coderZsq.target.swift
Follow: coderZsq · GitHub
Resume: coderzsq.github.io/coderZsq.we…

文末推荐:iOS热门文集

相关文章

网友评论

      本文标题:四道iOS面试题-解决面试官的底层难题

      本文链接:https://www.haomeiwen.com/subject/hlkrnltx.html