2024 iOS面试知识汇总

说在前面:
许久没更新,最近整理就文件的时候,突然翻到两年前换工作时整理的思维导图,包含了原理八股文,网络,算法,以及架构,重构解决方案等,基本上面试必问的一些项目以及原理都包含在内了。当然了这里整理的很多内容都只是帮助当时的我回忆知识脉络,并没有深入说明,还需要读者自己去查阅资料深入理解。希望能也帮助到最近准备换工作的小伙伴梳理汇总知识点。思维导图高清原图下载地址
欢迎关注一下我的 Github: https://github.com/CYXiang
后续会更新一些AI、Flutter等相关文章。

思维导图梳理

iOS原生相关

Objective-C 底层原理

  • OC对象

    • NSObject本质

      • NSObject底层是结构体,有一个 Class isa 指针
      • 创建一个NSObject对象系统分配16字节(至少16),只使用了8字节(用于存放isa),objc源码
    • 复杂对象本质

      • 包含父类成员结构+子类成员属性

        • 内存对齐,结构体的大小必须使最大成员变量的倍数
    • 属性和方法

      • 实例对象存放成员变量,不存放方法(存一份在类对象就够了)
    • 内存分配注意点

      • 内存分配方式内存对齐、桶、堆内存、16的倍数。目的是内存优化(gnu 内存对齐)<sizeof(a) 是个运算符,编译就确定了,不是函数>
    • 对象分类三类

      • instance 实例对象

        • isa指针、成员变量值
      • class 类对象

        • isa指针、superclass指针、类的属性信息(@property)、类的对象方法信息(instance method)、类的协议信息(protocol)、类的成员变量信息(ivar)
      • meta-class 元类对象

        • object_getclass([NSObject class]);
        • 与class类对象结构体一样
        • isa指针、superclass指针、类的类方法信息(class method)
    • isa指向哪里?

      • instance的isa指向class、class的isa指向meta-class、meta-class的isa指向基类的meta-class
      • 调用对象方法轨迹:isa找到class,方法不存在,就通过superclass找父类的方法,再不存在就找基类..
    • superclass指向哪里?

      • class的superclass指向父类的class(如果没有父类,superclass指针为null)、meta-class的superclass指向父类的meta-class(基类的meta-class的superclass指向基类的class)
      • 调用类方法轨迹:isa找meta-class,方法不存在,就通过superclass找父类
    • OC的类信息存放在哪里?

      • 对象方法、属性、成员变量、协议信息,存放在class对象中
      • 类方法,存放在meta-class对象中
      • 成员变量的具体值存放在instance对象中
  • KVO

    • 本质是什么?

      • 利用Runtime动态生成一个子类,让instance对象的isa指向这个子类NSKVONotifying_XXX
      • 当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
      • _NSSetXXXValueAndNotify函数内部触发监听器的监听方法
    • 如何手动触发KVO?

      • 手动调用willChangeValueForKey: 和 didChangeValueForKey:
  • KVC

    • 1、KVC赋值会触发KVO吗?

      • 会!
    • 赋值原理

      • 赋值顺序:①-(void)setKey; ②-(void)_setKey; 查看accessInstanceVariablesDirectly> ③_key; ④_isKey; ⑤key; ⑥isKey
    • 取值原理

      • 取值顺序:① getKey、key、 isKey、_key方法 ②查看accessInstanceVariablesDirectly ③按照
        _key、_isKey、key、isKey顺序查找成员变量
  • Cateogry

    • 通过runtime动态将分类合并到类/元类对象中

    • 实现原理

      • 1、通过Runtime加载某个类所有的Category数据
      • 2、把所有Category的方法、属性、协议数据,合并到一个大数组中(后面参与编译的Category数据,会在数组的前面)
      • 3、将合并后的分类数据(方法、属性、协议),插入到类原来的数据前面
    • Category与Class Extension的区别?

      • Class Extension在编译时数据就包含在类信息中
      • Category是在运行时才会将数据合并到类信息中
    • +load方法

      • 在runtime加载类、分类时调用,在程序运行过程中只调用一次

      • 调用顺序

        • 1、先调用类的+load。(按照编译先后顺序调用,先编译先调用。调用子类的+load之前会先调用父类的+load)
        • 2、在调用分类的+load。(按照编译先后顺序调用,先编译先调用)
    • +initialize方法

      • +initialize方法会在类第一次接收到消息时调用

      • 调用顺序

        • 先调用父类的+initialize,再调用子类的+initialize(先初始化父类,再初始化子类,每个类只会初始化一次)
  • 关联对象

    • 原理

      • 1、关联对象并不是存储在关联对象本身内存中
      • 2、关联对象存储在全局统一的一个AssociationManage中
      • 3、设置关联对象为null,就是移除关联对象
  • block

    • 原理/本质

      • 本质是OC对象,内部也有个isa指针
      • 封装了函数调用以及函数调用环境的OC对象
    • block的类型(三种,内存存储区域不同)

      • NSGlobalBlock

        • 数据区域Data区(与全局变量一起)
      • NSStackBlock

        • 栈(需要手动销毁)
      • NSMallocBlock

        • 堆(自动销毁)
    • block的变量捕获(capture)

      • 为了保证block内部能够正常访问外部变量,有个变量捕获机制

        • 局部变量(捕获!)

          • auto类型(自动变量,离开作用域就销毁):值传递
          • static类型(还能访问内存):指针传递
        • 全局变量(不捕获!)

          • 直接访问
    • __block修饰符

      • 用于解决修改block内部无法修改auto变量值的问题(不能修饰全局变量、静态变量 static)

        • 编译器会把__block变量包装成一个对象
    • __block内存管理

      • 1、当block在栈上时,并不会对__block变量产生强引用

      • 2、当block被copy到堆时

        • 会调用block内部的copy函数
        • copy函数内部会调用_Block_object_assign函数
        • _Block_object_assign函数会对__block变量形成强引用(retain)
    • block循环引用问题?

  • Runtime

    • objc_msgSend执行流程三大阶段

      • 1、消息发送

        • 先找自身缓存,自身缓存找不到找父类方法缓存找,找不到在父类方法列表查找,以此类推,找到就缓存到自身缓存中。找不到进入阶段2 ↓
      • 2、动态方法解析

        • ①调用+resolvenInstanceMethod: 或者+resolvenClassMethod: 方法来动态解析 ,进行动态添加方法
        • ②标记为已经动态解析 YES
        • ③回到阶段1、消息发送,因为已经动态加了方法且已标记为YES(如果没有添加,进入阶段3)
      • 3、消息转发

        • ①调用(id)forwordingTargetForSelector:(SEL)aSelector 返回转发对象,(返回nil就走下一步)

        • ②-返回方法签名(返回nil就报错,不为空就→)

          • 不为空调用 -(void)forwardInvocation:(NSInvocation *)anInvocation
  • RunLoop

    • 三种模式

      • NSDefalultRunLoopMode

      • UITrackingRunLoopMode

      • NSRunLoopCommonMode

        • 并不是一个真的模式
    • 运行逻辑

      • 1、通知Observers:进入Loop;通知Observers:即将处理Timers;通知Observers: 即将处理Sources
      • 2、处理Block;处理Source0(可能再次处理Block)
      • 3、如果存在Source1,跳转到5
      • 4、通知Observers,开始休眠(等待消息唤醒)
      • 5、通知Observers,结束休眠(被某个消息唤醒)①处理Timer ②处理GCD ③处理Source1
      • 6、根据前面的执行结果决定如何操作
    • NSTimer失效问题

      • NSTimer在默认模式下,切换到NSRunLoopCommonMode
    • 线程保活

      • 1、添加Source; addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode]
      • 2、加while(weakSelf&&!weakSelf.isStopped) 循环, 执行currentRunLoop runMode:beforeDate:
    • 利用RunLoop监控卡顿

      • 怎么才算卡顿?

        • 1、进入睡眠前方法执行时间过长而导致无法进入睡眠
        • 2、线程唤醒后接收消息时间过长尔无法进入下一步
      • 如何监控卡顿?

        • 关注两个阶段(进入睡眠之前和唤醒之后的两个loop状态定义的值)

          • 1、kCFRunLoopBeforeSources

            • 触发Source0回调
          • 2、kCFRunLoopAfterWaiting

            • 接收mach_port消息
      • 如何监听

        • 1、创建一个CFRunLoopObserverContext观察者
        • 2、将观察者添加到主线程RunLoop的Common模式下观察。
        • 3、再创建一个持续的子线程专门用来监控主线程的RunLoop状态
        • 4、一旦发现进入睡眠前的kCFRunLoopBeforeSource状态或唤醒后的kCFRunLoopAfterWaiting,在设置的时间阈值内一直没有变化,即可判断为卡顿,再dump出堆栈信息
  • 多线程

    • pthread

    • NSThread

    • GCD

      • 同步 sync

        • 没有开启线程,串行执行任务
      • 异步 async

        • 并且是并发队列,才会开启新线程并发执行任务
      • Semaphore信号量

        • 控制最大并发数量,也可用来线程同步(把信号量设 1 )
    • 死锁

      • ①使用sync函数
      • ②往当前串行队列中添加任务
    • 队列组

    • NSOperation

      • 封装GCD
    • iOS线程同步方案(加锁、GCD串行队列)

      • OSSpinLock 自旋锁

        • (不安全了不建议使用,优先反转问题)
      • os_unfair_lock

        • OSSpinLock的替代方案
      • pthread_mutex

        • pthread_mutex_signal 激活一个等待该条件的线程
      • dispatch_semphore

        • 信号量
      • dispatch_queue(DISPATCH_QUEUE_SERIAL)

        • GCD串行队列
      • pthread_mutex 的OC封装

        • NSLock

        • NSRecursiveLock

          • 递归锁,保证能够递归调用
        • NSCondition

        • NSConditionLock

          • 带条件的lock(生产者消费者模式)
      • @synchronized

        • 性能最差
    • 读写安全方案

      • 1、多读单写(异步读,同步写),用于文件数据读写操作

        • pthread_rwlock 读写锁

          • 互斥锁,等待锁的过程会进入休眠
        • dispatch_barrier_async 异步栅栏调用

          • 传入的并发队列必须是dispatch_queue_create创建的(不是就没效果)
          • 如果传入的是一个串行或全局并发队列,那这个函数等同于dispatch_async效果
  • 内存管理

    • 定时器

      • GCD是最准确的,与RunLoop无关
    • 内存布局

      • 保留内存

      • 代码段(__TEXT)

        • 编译后的代码
      • 数据段(__DATA)

        • 字符串常量
        • 已初始化数据
        • 未初始化数据
      • 堆(heap)

        • 通过alloc、malloc、calloc等动态分配的空间
      • 栈(stack)

        • 函数调用开销,局部变量的开销
      • 内核区

    • Tagged Pointer

    • MRC

    • copy

    • weak 原理

      • 将弱引用存入到哈希表内,当对象销毁时就从表中取出弱引用并清除 (运行时操作)
    • ARC 原理

      • 利用LLVM+Runtime,LLVM自动生成插入retain,release代码
    • autoRelease

UI层基本原理

  • 事件传递机制

    • 当一个事件发生后,事件会从父控件传给子控件,也就是说由UIApplication -> UIWindow -> UIView -> initial view,以上就是事件的传递,也就是寻找最合适的view的过程
    • 事件的传递是从上到下(父控件到子控件),事件的响应是从下到上(顺着响应者链条向上传递:子控件到父控件
    • 拦截事件 hitTest:withEvent:
  • UI绘制原理

    • 在layer内部会创建一个backing store,我们可以理解为CGContextRef上下文。

    • 判断layer是否有delegate:

      • 如果有delegate,则会执行[layer.delegate drawLayer:inContext](这个方法的执行是在系统内部执行的),然后在这个方法中会调用view的drawRect:方法,也就是我们重写view的drawRect:方法才会被调用到。
      • 如果没有delegate,会调用layer的drawInContext方法,也就是我们可以重写的layer的该方法,此刻会被调用到。
    • 最后都由CALayer把绘制完的backing store(可以理解为位图)提交给GPU。

  • 异步绘制原理

    • [UIView setNeedsDisplay]方法的时候,不会立马发送对应视图的绘制工作

      • 调用[UIView setNeedsDisplay]后
      • 然后会调用系统的同名方法[view.layer setNeedsDisplay]方法并在当前view上面打上一个脏标记
      • 当前Runloop将要结束的时候才会调用[CALyer display]方法,然后进入到视图真正的绘制工作当中
    • 是否知道异步绘制?如何进行异步绘制?

      • 基于系统开的口子[layer.delegate dispayLayer:]方法
      • 并且实现/遵从了dispayLayer这个方法,我们就可以进行异步绘制
  • 离屏渲染

    • 触发离屏渲染的场景

      • 采用了光栅化的 layer (layer.shouldRasterize)
      • 使用了 mask 的 layer (layer.mask)
      • 需要进行裁剪的 layer
      • 设置了组透明度为 YES,并且透明度不为 1 的layer
      • 高斯模糊
      • 添加了投影的 layer (layer.shadow*)
      • 绘制了文字的 layer (UILabel, CATextLayer, Core Text 等
    • 使用Instruments的不同工具来测试性能

LLDB

  • 动态调试

    • 子主题 1

Clang + LLVM

  • Clang编译步骤

    • 1、预处理

      • 头文件替换,宏替换,预编译指令替换
    • 2、词法分析

      • 输出token流
    • 3、语法分析

      • 生成AST抽象语法树
    • 4、CodeGen

      • CodeGen 负责将语法树丛顶至下遍历,翻译成LLVM IR

      • 生成中间代码IR,与RunTime桥接

        • ARC:分析对象引用关系,将objc_storeStrong/objc_storeWeak等ARC代码插入
        • 根据修饰符strong/weak/copy/atomic合成@property 自动实现的 setter/getter
  • LLVM后端

    • 优化 IR
    • LLVM BitCode字节码
  • 如何使用Clang做静态分析

    • OCLint

      • 基本覆盖了具有通用性的规则,主要包括语法上的基础规则、Cocoa 库相关规则、一些约定俗成的规则、各种空语句检查、是否按新语法改写的检查、命名上长变量名短变量名检查、无用的语句变量和参数的检查
    • Clang 静态分析器

      • scan-build 是用来运行分析器的命令行工具
    • Infer(Facebook 开源的、使用 OCaml 语言编写)

      • 空指针访问
      • 资源泄露
      • 内存泄露

iOS签名机制

  • 保证安装到用户手机上的APP都是经过Apple官方允许的
  • 生成CertificateSigningRequest.certSigningRequest文件

iOS安全

  • 代码混淆

    • 源码的混淆

      • 类名
      • 方法名
      • 协议名
  • 字符串加密

越狱相关

  • 越狱检测

Swift

性能优化

CPU与GPU

  • CPU

    • 对象的创建销毁、对象属性的调整、布局计算、文本计算和排版、图片的格式转换和解码、图像的绘制(Core Graphics)
  • GPU

    • 纹理的渲染、

启动优化

  • 启动速度监控

    • 1、定时抓取主线程上方法调用堆栈,计算一段时间里各个方法的耗时(Xcode 工具套件里自带的 Time Profiler ,采用的就是这种方式)

    • 2、对objc_msgSend方法进行hook

      • 原理解释
      • 如何使用
  • 方案与实践

    • iOS冷启动阶段思路

      • System Interface

        加载主二进制、启动dyld、加载动态库以及libSystem初始化

        • 避免链接不使用的框架
        • 减少动态库的加载
        • 减少OC类,分类等
      • runtime Init

        执行 +load和staic initializer初始化函数等

        • 避免在+load里操作
        • 延迟加载+load
        • 减少staic initializer(C++ 静态全局变量)
      • UIKit init

      • Application init

        实例化UI Application 和 UI Application Delegate、处理生命周期回调、首帧渲染直到首页渲染完成

        • 减少或延迟各种SDK的初始化
      • initial Frame Render

        • 减少视图层级和视图数量
        • 懒加载View
        • 变AutoLayout为手动frame布局等
      • Extended

        • 去掉viewDidLoad和viewWillAppear中不必要的逻辑,少做事不做事
    • 方案

      • 低成本高收益方案

        • 生命周期延迟
        • +load治理
        • 动态库下线
        • 二进制重排
        • 首页预加载
      • 深入优化方案

        • 动态库懒加载
        • staic initlializer治理
        • 编译期写入I/O
        • 任务编排
  • 流程规范与监控

    • 规范

      • 1、新增或修改任务要有足够的理由,必须经过严格的code review
      • 2、首页渲染完成前不允许监听生命周期
      • 3、不允许新增+load耗时方法
      • 4、不允许新增C++ initialize
      • 5、新增动态库必须经过评估
      • 6、任务项相对上个版本有5ms以上的增长时,必须进行修改
    • 监控

卡顿排查与解决

  • UI界面卡顿优化(滑动掉帧等)

    • 尽量减少CPU GPU资源消耗
  • 崩溃类型的卡顿排查(线程卡顿)

耗电排查与解决

  • 1、如何获取电量

    • (1)引入 IOPowerSources.h、IOPSKeys.h 和 IOKit
    • (2)把 batteryMonitoringEnabled 置为 true
  • 2、如何诊断电量问题

    • (1)通过 task_threads 函数,获取所有的线程信息数组 threads以及线程总数 threadCount
    • (2)thread_basic_info 里有一个记录 CPU 使用百分比的字段 cpu_usage
    • (3)遍历所有线程,去查看是哪个线程的 CPU 使用百分比过高。(某个线程的 CPU 使用率长时间都比较高,可能有问题)
  • 3、如何优化电量

    • (1)避免让 CPU 做多余的事情。对于大量数据的复杂计算,应该把数据传到服务器去处理

      必须要在 App 内处理复杂数据计算,可以通过 GCD 的 dispatch_block_create_with_qos_class 方法指定队列的 Qos 为 QOS_CLASS_UTILITY,将计算工作放到这个队列的 block 里。在 QOS_CLASS_UTILITY 这种 Qos 模式下,系统针对大量数据的计算,以及复杂数据处理专门做了电量优化。

    • (2)I/O 操作也是耗电大户,优化I/O操作

      业内的普遍做法是,将碎片化的数据磁盘存储操作延后,先在内存中聚合,然后再进行磁盘存储。碎片化的数据进行聚合,在内存中进行存储的机制,可以使用系统自带的 NSCache 来完成。

      NSCache 是线程安全的,NSCache 会在到达预设缓存空间值时清理缓存,这时会触发 cache:willEvictObject: 方法的回调,在这个回调里就可以对数据进行 I/O 操作,达到将聚合的数据 I/O 延后的目的。I/O 操作的次数减少了,对电量的消耗也就减少了

    • (3)苹果维护了一个电量优化指南“Energy Efficiency Guide for iOS Apps”

包瘦身

  • 官方 App Thinning

  • 图片资源优化

    • 无用图片移除,图片压缩

      • LSUnusedResources
    • TinyPng或者ImageOptim、转webp

  • 代码瘦身

    • 删除无用功能代码(A/B测试结果删除)

    • 源代码瘦身

      • LinkMap 结合 Mach-O 找无用代码

        • AppCode(人工二次确认)
      • 运行时检查类是否真正被使用过

        • 通过isInitialized,判断一个类是否初始化过
    • 重复代码删除(Clang插件)

  • 可执行文件瘦身

    • 编译器优化

架构设计

组件化

  • 协议式

    • 协议式架构设计主要采用的是协议式编程的思路

      • 在编译层面使用协议定义规范,实现可在不同地方,从而达到分布管理和维护组件的目的
    • 缺陷

      • 协议式编程缺少统一调度层,导致难于集中管理
      • 协议式编程接口定义模式过于规范,从而使得架构的灵活性不够高。当需要引入一个新的设计模式来开发时,我们就会发现很难融入到当前架构中,缺乏架构的统一性。
  • 中间者

    • 优势

      • 拆分的组件都会依赖于中间者,但是组间之间就不存在相互依赖的关系
      • 其他组件都会依赖于这个中间者,相互间的通信都会通过中间者统一调度
      • 在中间者上也能够轻松添加新的设计模式,从而使得架构更容易扩展
      • 中间者架构的易管控带来的架构更稳固,易扩展带来的灵活性
    • 实现方案

      • CTMediator

        • CTMediator 本质就是一个方法,用来接收 target、action、params,对于调用者来说十分不友好

          • 通过响应者给 CTMediator 做的 category 或者 extension 发起调用
          • category 或 extension 以函数声明的方式,解决了参数的问题
          • 不会直接依赖 CTMediator 去发起调用,而是直接依赖 category Pod 去发起调用
        • 解耦的精髓在于业务逻辑能够独立出来,并不是形式上的解除编译上的耦合(编译上解除耦合只能算是解耦的一种手段而已)。更多的还是需要在功能逻辑和组件划分上做到同层级解耦,上下层依赖清晰

      • URLRoutor

        • 缺陷

          • 本地间调用无法传递非常规参数,复杂参数的传递方式非常丑陋
          • 必须要在app启动时注册URL响应者
          • 新增组件化的调用路径时,蘑菇街的操作相对复杂

MVC

MVVM

  • 双向绑定(RAC,RSSwift)

设计模式

系统化思维

五大设计原则

  • 单一功能原则:对象功能要单一,不要在一个对象里添加很多功能
  • 开闭原则:扩展是开放的,修改是封闭的
  • 里氏替换原则:子类对象是可以替代基类对象的
  • 接口隔离原则:接口的用途要单一,不要在一个接口上根据不同入参实现多个功能
  • 依赖反转原则:方法应该依赖抽象,不要依赖实例。iOS 开发就是高层业务方法依赖于协议

23种设计模式实现原理

  • 子主题 1

网络协议相关

IP层

TCP/UDP

  • TCP

    • TCP是一个传输层协议,提供端到端(Host-To-Host) 数据的可靠传输
    • 支持全双工,是一个连接导向的协议(面向连接的)
  • UDP

    • 目标是在传输层提供直接发送报文(Datagram)的能力

HTTP/HTTPS

  • 为什么可以相信一个 HTTPS 网站?

    • 当用户用浏览器打开一个 HTTPS 网站时,会到目标网站下载目标网站的证书
    • 浏览器会去验证证书上的签名,一直验证到根证书,如果根证书被预装,那么就会信任这个网站

DNS

Socket

  • Socket 是一种编程的模型

    • 客户端将数据发送给在客户端侧的Socket 对象,然后客户端侧的 Socket 对象将数据发送给服务端侧的 Socket 对象
    • Socket 对象负责提供通信能力,并处理底层的 TCP 连接/UDP 连接
    • 对服务端而言,每一个客户端接入,就会形成一个和客户端对应的 Socket 对象,如果服务器要读取客户端发送的信息,或者向客户端发送信息,就需要通过这个客户端 Socket 对象
  • Socket 还是一种双向管道文件

    • 操作系统将客户端传来的数据写入这个管道,也将线程写入管道的数据发送到客户端

算法

数组&链表

堆栈&队列

  • 面试题:【判断字符串括号是否合法】

  • 单调栈

    • 递增栈

      • 小数消除大数
    • 递减栈

      • 大数消除小数
  • 优先队列

    • 正常进,安装优先级出
    • 实现机制:1、Heap(堆)(Binary、Binomial、Fiboncci)

哈希表

  • Map/Set

    • 【有效的字母异位词】
    • 【两数之和】
    • 【三数之和】

  • 二叉树

    • 反转二叉树

      • 遍历二叉树
  • 二叉搜索树

  • 字典树

递归&分治

动态规划

  • 贪心算法
  • 买卖股票
  • 背包问题

LRU Cache

Bloom Filter(布隆过滤器)

斐波那契数列

Flutter

底层基本实现

Bloc与响应式

容器化&配置化

组件化

性能优化与实践

安全与密码学

单向散列函数(哈希函数)

  • SHA-1、MD5(已不安全)
  • SHA-256、SHA-384、SHA-512(目前流行)

加密算法

  • 对称加密

    • 序列算法(优先使用)

      • ChaCha20、AES-256、AES-128
    • 分组算法

  • 非对称加密

亮点与疑难解决

动态化

容器化

配置化

持续集成

fastlane

RunTime无埋点方案

产品主要想知道:页面进入次数、页面停留时间、点击事件的埋点(用来计算曝光率、转化率)

运行时方法替换方式进行埋点(AOP)

  • 写一个运行时方法替换的类 SMHook
  • 利用运行时接口将方法的实现进行了交换,原方法调用时就会被 hook 住,从而去执行指定的方法
  • 每个 UIViewController 生命周期到了 ViewWillAppear 时都会去执行 insertToViewWillAppear 方法

事件唯一标识区分不同埋点

  • NSStringFromClass([self class]) 方法来取类名,区别不同的 UIViewController
  • action 选择器名 NSStringFromSelector(action)” +“视图类名 NSStringFromClass([target class])”组合成一个唯一的标识
  • 通过视图的 superview 和 subviews 的属性,我们就能够还原出每个页面的视图树
  • 复用机制 UITableViewCell 用 indexPath

RunLoop运行步骤

1、通知 observers:RunLoop 要开始进入 loop 了。紧接着就进入 loop

2、开启一个 do while 来保活线程。通知 Observers:RunLoop 会触发 Timer 回调、Source0 回调,接着执行加入的 block

通知 Observers:RunLoop 的线程将进入休眠(sleep)状态

4、进入休眠后,会等待 mach_port 的消息,以再次唤醒

5、唤醒时通知 Observer:RunLoop 的线程刚刚被唤醒了

6、RunLoop 被唤醒后就要开始处理消息了

启动优化方案

main() 函数执行前

  • 加载可执行文件(App 的.o 文件的集合)
  • 加载动态链接库,进行 rebase 指针调整和 bind 符号绑定
  • Objc 运行时的初始处理,包括 Objc 相关类的注册、category 注册、selector 唯一性检查等
  • 初始化,包括了执行 +load() 方法、attribute((constructor)) 修饰的函数的调用、创建 C++ 静态全局变量

main() 函数执行后(appDelegate 的 didFinishLaunchingWithOptions 方法里首屏渲染相关方法执行完成)

  • 首屏初始化所需配置文件的读写操作
  • 首屏列表大数据的读取
  • 首屏渲染的大量计算等

首屏渲染完成后(加载时长)

  • 减少视图层级和视图数量
  • 懒加载View
  • 变AutoLayout为手动frame布局等
  • 预加载(缓存首页,骨架屏等)
  • 去掉viewDidLoad和viewWillAppear中不必要的逻辑,少做事不做事

APM系统

启动优化、卡顿监听、崩溃监听、性能监控

GNUStep 源码

LLD链接器

链接器最主要的作用,就是将符号绑定到地址上

使用 dyld 加载动态库

调用 +load 方法是通过 runtime 库处理的

Injection for Xcode 动态调试

  • 1、Injection 会监听源代码文件的变化
  • 2、如果文件被改动了,Injection Server 就会执行 rebuildClass 重新进行编译、打包成动态库,也就是 .dylib 文件
  • 3、编译、打包成动态库后使用 writeSting 方法通过 Socket 通知运行的 App

更安全的方法交换库 Aspects

通过 Runtime 消息转发机制来实现方法交换的库

它将所有的方法调用都指到 _objc_msgForward 函数调用上

按照自己的方式实现了消息转发,自己处理参数列表,处理返回值

最后通过 NSInvocation 调用来实现方法交换

事件总线技术 Promise

PromiseKit

通过简单、清晰、规范的 Promise 接口将异步的数据获取、业务逻辑、界面串起来,对于日后的维护或重构都会容易很多

Source0与Source1

Source0

  • 不能主动触发事件
  • 使用时,你需要先调用CFRunLoopSourceSignal,将这个Source标记为待处理,然后手动调用CFRunLoopWakeUp来唤醒RunLoop,让其处理这个事件

Source1

  • 主动触发事件。其中它有一个mach_port_t

TCP 最核心的价值是提供了可靠性,而 UDP 最核心的价值是灵活

HTTP 协议 1.1 和 2.0 都基于 TCP,而到了 HTTP 3.0 就开始用 UDP

TCP与UDP区别

目的差异

  • TCP 协议的核心目标是提供可靠的网络传输
  • UDP 的目标是在提供报文交换能力基础上尽可能地简化协议轻装上阵

可靠性差异

  • TCP 核心是要在保证可靠性提供更好的服务。TCP 会有握手的过程,需要建立连接
  • UDP 并不具备以上这些特性,它只管发送数据封包,而且 UDP 不需要 ACK

连接 vs 无连接

  • TCP 是一个面向连接的协议,传输数据必须先建立连接
  • UDP 是一个无连接协议,数据随时都可以发送,只提供发送封包(Datagram)的能力

传输速度

  • UDP 协议简化,封包小,没有连接、可靠性检查等,因此单纯从传输速度上讲,UDP 更快

场景差异

  • TCP 场景

    • 远程控制(SSH)

File Transfer Protocol(FTP)

邮件(SMTP、IMAP)等

点对点文件传出(微信等)

  • UDP 场景

    • 网络游戏

音视频传输

DNS

Ping

直播

APP如何加载?

iOS系统架构

  • 用户体验层,主要是提供用户界面。这一层包含了 SpringBoard、Spotlight、Accessibility
  • 第二层是应用框架层,是开发者会用到的。这一层包含了开发框架 Cocoa Touch
  • 第三层是核心框架层,是系统核心功能的框架层。这一层包含了各种图形和媒体核心框架、Metal 等
  • 第四层是 Darwin 层,是操作系统的核心,属于操作系统的内核态。这一层包含了系统内核 XNU、驱动等

XNU 怎么加载 App?

  • iOS 的可执行文件和动态库都是 Mach-O 格式,所以加载 APP 实际上就是加载 Mach-O 文件
  • 加载 Mach-O 文件,内核会 fork 进程,并对进程进行一些基本设置,比如为进程分配虚拟内存、为进程创建主线程、代码签名等。用户态 dyld 会对 Mach-O 文件做库加载和符号解析

好架构定义

高可用

高性能

易扩展

在功能逻辑和组件划分上做到同层级解耦,上下层依赖清晰,这样的结构才能够使得上层组件易插拔,下层组件更稳固

组件化架构

1、业务完全解耦,通用功能下沉

组件

2、每个业务都是一个独立的 Git 仓库,每个业务都能够生成一个 Pod 库,最后再集成到一起

组件分层

  • 底层可以是与业务无关的基础组件,比如网络和存储等
  • 中间层一般是通用的业务组件,比如账号、埋点、支付、购物车等
  • 最上层是迭代业务组件,更新频率最高

多团队之间如何分工?

  • 基建团队,负责业务无关的基础功能组件和业务相关通用业务组件的开发
  • 每个业务都由一个专门的团队来负责开发
  • 基建团队人员应该是流动的,从业务团队里来,再回到业务团队中去

监控崩溃与采集

崩溃类型

  • 信号可捕获到

    • KVO、数组越界、返回类型不匹配NULL

    • 多线程问题

      • 在子线程中进行 UI 更新可能会发生崩溃。多个线程进行数据的读取操作,因为处理时机不一致,比如有一个线程在置空数据的同时另一个线程在读取这个数据,可能会出现崩溃情况
    • 野指针

      • 指针指向一个已删除的对象访问内存区域时,会出现野指针崩溃
  • 信号不可捕获

    • 后台任务超时

      • iOS 后台保活

        • Background Task (3 分钟)

          • 系统提供了 beginBackgroundTaskWithExpirationHandler 方法来延长后台执行时间,可以解决你退后台后还需要一些时间去处理一些任务的诉求
    • 主线程卡顿超阈值

      • 主线程无响应

        • 如果主线程超过系统规定的时间无响应,就会被 Watchdog 杀掉
    • 内存打爆

      • JetSam 机制

        • 操作系统为了控制内存资源过度使用而采用的一种资源管控机制
      • 通过内存警告获取内存限制值

        • didReceiveMemoryWarning

          • 强杀掉 App 之前还有 6 秒钟的时间
      • 定位内存问题信息收集

        • 谁分配的内存?定位到函数

          • 用 fishhook 去 Hook “malloc_logger”函数,分析统计

监控方案

  • 第三方的

    • Fabric或Bugly
  • 第三方开源自建服务器

    • PLCrashReporter

      • 第三方开源库捕获崩溃日志,然后上传到自己服务器上进行整体监控的

A/B测试方案(SkyLab)

三个部分

  • 策略服务,为策略制定者提供策略

    • 决策流程、策略维度
  • A/B 测试 SDK,集成在客户端内

    • 客户端SDK:SkyLab

      • 使用的是 MMKV 保存策略

      • SkyLab 对外的调用接口使用的是 Block ,来接收版本 A 和 B 的区别处理。

      • 如何做人群测试桶划分

        • 随机分配方式,将分配结果通过 MMKV 进行持续化存储,确保测试桶的一致性
  • 日志系统,负责反馈策略结果供分析人员分析不同策略执行的结果

服务端返回A/B实验

性能监控

线下性能

  • Energy Log 就是用来监控耗电量的
  • Leaks 就是专门用来监控内存泄露问题的
  • Network 就是用来专门检查网络情况
  • Time Profiler 就是通过时间采样来分析页面卡顿问题

线上监控(不要侵入到业务代码、采用性能消耗最小的监控方案)

  • CPU 使用率的线上监控(App 作为进程运行起来后会有多个线程,每个线程对 CPU 的使用率不同。各个线程对 CPU 使用率的总和,就是当前 App 对 CPU 的使用率)

    • thread_info.h 根据当前 task 获取所有线程
    • 遍历所有线程来获取单个线程的基本信息
    • thread_basic_info 结构体获取CPU 使用率的字段:cpu_usage
    • 累加这个字段就能够获取到当前的整体 CPU 使用率
  • FPS 线上监控

    • 通过注册 CADisplayLink 得到屏幕的同步刷新率
    • 记录每次刷新时间,然后就可以得到 FPS
  • 内存使用量的线上监控

    • 内存信息存在 task_vm_info
    • 类似于对 CPU 使用率的监控,我们只要从这个结构体里取出 phys_footprint 字段

启动优化监控方案

定时抓取主线程上的方法调用堆栈,计算一段时间里各个方法的耗时(Xcode 工具套件里自带的 Time Profiler ,采用的就是这种方式)

对 objc_msgSend 方法进行 hook 来掌握所有方法的执行耗时

使用RunLoop监控卡顿

原因

  • 复杂 UI 、图文混排的绘制量过大
  • 在主线程上做网络同步请求
  • 在主线程做大量的 IO 操作
  • 运算量过大,CPU 持续高占用
  • 死锁和主子线程抢锁

RunLoop基本原理

  • 用来监听输入源,进行调度处理的。这里的输入源可以是输入设备、网络、周期性或者延迟时间、异步回调
  • RunLoop 会接收两种类型的输入源:一种是来自另一个线程或者来自不同应用的异步消息;另一种是来自预订时间或者重复间隔的同步事件
  • 当有事件要去处理时保持线程忙,当没有事件要处理时让线程进入休眠
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容