秋招ios面试 -- 真题篇(二)

1. 介绍一下传值通知和推送通知

传值通知:传值是进程内的组件通信,范围小,延迟低,不依赖网络

常见方式

a. 直接回调/闭包

如:A 页面弹出 B 页面,B 做完事把结果回传给 A。

b. 委托/协议(delegate)

如:一个对象(委托方/被委托者,通常是"子组件/下游")将某些"决定/事件处理"交给另一个对象(代理/委托对象,通常是"上层/外部")来完成。

c. 通知中心

如:一对多广播,模块松耦合,不想建立直接引用。

Swift 复制代码
// 发送
NotificationCenter.default.post(name: .loginSuccess, object: nil, userInfo: ["userId": "123"])
// 监听
NotificationCenter.default.addObserver(forName: .loginSuccess, object: nil, queue: .main) { note in
    print(note.userInfo?["userId"] as? String ?? "")
}

d. KVO/Combine/Reactive

如:对某个状态变化感兴趣的多个订阅者。

对某个状态变化感兴趣的多个订阅者。

推送通知:系统级消息,显示在通知中心,通常由服务端发起。

远程推送:服务端通过 Apple Push Notification service 下发到设备。

本地推送:App 在本机排程,定时或基于触发条件发送。

静默推送:不弹 UI,唤醒 App 在后台拉数据(节制使用,受系统调度)。

2. 介绍一下NSCache & NSDcitionary的区别

|---------------------------------|----------------------------------------|
| NSCache | NSDictionary |
| 内存缓存,自动清理 | 通用键值存储 |
| NSCache 自身可变(类似于可变字典) | NSDictionary 不可变 |
| 多数操作线程安全:存在内部锁和队列 | NSDictionary/NSMutableDictionary 不线程安全 |
| 会在内存压力下自动移除对象 | 不会自动回收,除非你手动清理或对象释放 |
| 键是 AnyObject,但会强引用 | NSDictionary 支持任意符合 Hashable 的对象 |
| 可设置总成本、总数量上限;系统可能随时回收 | 没有成本概念; |
| 可通过 `NSCacheDelegate` 监听对象被移除 | 无 |
| 适用于图片缓存,计算结果缓存,富文本布局缓存 | 主要就是使用作为一个字典容器 |

3. 介绍一下UIView 与 CALayer

UIView:负责交互和事件的视图对象,管理层级、布局、触摸、手势、Auto Layout、响应链等。
CALayer:负责"绘制与合成"的渲染对象,管理位图内容、圆角阴影、变换、动画,性能更靠近硬件。

每个UIView内部都存在一个属性:view.layer(该属性的类型为CALayer)

视图树与层树一一对应

你可以在一个UIView上继续添加layer,做到轻量绘制而无序更多UIView

如果我们只做展示无交互的装饰元素(分割线、阴影、渐变、形状),我们会采用CALayer,因为其相较于UIView更加轻量,"交互在 View、渲染交给 Layer"

视图树:视图树就是由视图(View)按照父子关系组成的一棵树形结构,用来组织界面上的所有可见元素以及它们的层级关系。

视图树的作用如下:

  1. 布局与位置计算:父视图决定子视图的坐标系和布局范围,Auto Layout 也是沿着树自顶向下/自底向上参与计算。

  2. 事件分发:触摸、手势等事件沿着视图树进行命中测试(hit-testing)和响应链传递。

  3. 渲染合成:系统以视图树为蓝本,生成对应的 CALayer 树,在 GPU 进行合成与绘制。

  4. 生命周期与管理:添加/移除、隐藏/显示、层级顺序(z-order)管理都依赖树结构。

4. 介绍一下OC中的category & extension

Category:在不修改原始类源码的情况下,给类添加方法

运行时加载(链接期合并,可以覆盖同名方法,按照编译顺序进行覆盖)

不能直接添加实力变量,但是可以添加属性声明,需要你自己实现getter和setter(或者通过关联对象)

逻辑拆分:把庞大类按功能拆成多个分类文件。
Extension:在"类的实现文件(.m)内部"给类补充"私有的"声明(方法、属性、实例变量)。

仅对当前编译单元可见,避免暴露到外部头文件。

可以隐藏内部细节

关于category的底层原理:

编译后,分类会被编译成一个 `category_t` 结构体并放入镜像(Mach‑O)特定段中,供运行时加载。

cpp 复制代码
// 每个分类一个入口
struct category_t {
    const char *name;           // 类名(C 字符串)
    classref_t cls;             // 指向目标类(运行时填充)
    struct method_list_t *instanceMethods; // 实例方法列表
    struct method_list_t *classMethods;    // 类方法列表(作用在元类)
    struct protocol_list_t *protocols;     // 该分类声明遵循的协议
    struct property_list_t *instanceProperties; // 属性列表(仅声明)
    // 还有一些可选字段:classProperties(新 runtime)
};

当 App 启动或动态库加载时,Objective‑C 运行时会执行"镜像扫描与注册":

  1. dyld 加载镜像(Mach‑O),定位 Objective‑C 的各段(__objc_classlist、__objc_catlist 等)。

  2. runtime 遍历 `__objc_catlist`,拿到每个 `category_t`。

  3. 随后对每个分类进行合并:合并规则通常是"把分类的方法列表插到现有方法列表的前面",使其在方法查找顺序中优先生效。

关联对象:关联对象把"某个对象 A 的一个键"关联到"任意值(对象)",由 runtime 管理一张全局哈希表。

5. CFSocket使用步骤

  1. 准备 CFSocketContext,用于在回调中携带 self/对象指针。

  2. 使用 `CFSocketCreate` 指定协议族、套接字类型、协议、感兴趣事件、回调函数和上下文。

常见参数:

协议族:`PF_INET`(IPv4)、`PF_INET6`(IPv6)

类型:`SOCK_STREAM`(TCP)、`SOCK_DGRAM`(UDP)

协议:`IPPROTO_TCP`、`IPPROTO_UDP`

  1. 配置本机地址或远端地址

  2. 绑定/连接

  3. 将 CFSocket 加入 RunLoop

  4. 在回调中处理事件

  5. 读写数据

  6. 关闭与清理

相关推荐
Lee川14 小时前
优雅进化的JavaScript:从ES6+新特性看现代前端开发范式
javascript·面试
Lee川17 小时前
从异步迷雾到优雅流程:JavaScript异步编程与内存管理的现代化之旅
javascript·面试
晴殇i19 小时前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
绝无仅有20 小时前
Redis过期删除与内存淘汰策略详解
后端·面试·架构
绝无仅有20 小时前
Redis大Key问题排查与解决方案全解析
后端·面试·架构
AAA梅狸猫21 小时前
Looper.loop() 循环机制
面试
AAA梅狸猫21 小时前
Handler基本概念
面试
Wect21 小时前
浏览器缓存机制
前端·面试·浏览器
掘金安东尼1 天前
Fun with TypeScript Generics:玩转 TS 泛型
前端·javascript·面试
掘金安东尼1 天前
Next.js 企业级落地
前端·javascript·面试