Swift 语言特性:请解释一下 struct
和 class
的主要区别。
特性 | struct (值类型) | class (引用类型) |
---|---|---|
类型本质 | 值类型 (复制时创建独立副本) | 引用类型 (复制时共享同一实例) |
内存分配 | 通常在栈上 (更快速) | 在堆上 (需要ARC管理) |
可变性 | 默认不可变 (需 | 默认可变 |
继承 | 不支持继承 | 支持单继承 |
析构器 | 无 | 有 |
线程安全 | 天然线程安全 (独立副本) | 需手动同步 (多线程访问同一实例可能冲突) |
引用计数 | 无 | 依赖ARC管理内存 |
内存管理:谈谈你对 ARC(自动引用计数)的理解。什么是强引用循环?如何避免?
Swift 在编译时自动插入 retain
(增加引用计数)和 release
(减少引用计数)指令,通过跟踪对象的引用关系自动管理内存生命周期。
解释 weak
和 unowned
的底层区别:
weak
通过额外侧表(Side Table) 实现自动置 nil
,unowned
直接访问原始指针。.
生命周期:描述一下 UIViewController
的视图生命周期(例如 viewDidLoad
, viewWillAppear
, viewDidLayoutSubviews
等)。在哪个方法里更新视图的布局或帧是安全的?
loadView() 创建视图层次的根视图
viewDidLoad()视图首次加载到内存
viewWillAppear(_:)视图即将出现
viewDidAppear(_:)视图完成显示
viewWillDisappear(_:)视图即将离开屏幕
viewDidDisappear(_:)视图已经离开屏幕
viewWillLayoutSubviews()
视图即将计算子视图布局
viewDidLayoutSubviews()
视图已完成子视图布局计算(frame
已确定)
我们在viewDidLayoutSubviews()中更新视图布局或者帧是最安全的
多线程:DispatchQueue.main.async
和 DispatchQueue.global().async
分别有什么作用?为什么更新UI必须在主队列进行?
DispatchQueue.main.async:从非主线程切换到主线程,确保UI操作在主线程执行
DispatchQueue.global().async
的作用:全局队列,是一个并发队列
UIKit 和 CoreAnimation 的底层代码未加锁(锁会降低性能),同一时间只允许主线程修改 UI 元素。
Objective-C 中Category
和Extension
的区别是什么?Category 能否添加成员变量?如果不能,如何间接实现 “添加属性” 的效果?(考察 runtime 关联对象的理解)
category分类
用于给已经存在的类添加新方法,不能添加新的成员变量,可以有单独的文件,经常用于讲将一个庞大的类拆分为多个模块
类扩展
本质上是类的匿名分类,只能定义在类的实现文件中,可以添加私有成员变量,方法和属性
Category不能添加成员变量,因为oc的类在编译的时候就会确定内存布局,故无法添加
可以使用runtime关联对象实现,原理是将一个对象和另一个对象关联起来,可以达到添加属性的效果
关联对象本质上是通过 Runtime 函数,将一个对象(值)与另一个对象(宿主)建立关联关系,并通过唯一的 key 来标识这种关联。这种关联关系会被系统管理,当宿主对象被销毁时,关联对象也可以被自动清理。
Swift 的Optional
(可选类型)是如何解决空指针问题的?请解释?
、!
、guard let
、if let
的用法差异,以及nil coalescing
(??
)运算符的底层逻辑。
其本质上是一个枚举类型
enum Optional<T> {case none // 表示空值(nil)case some(T) // 表示有值,关联值为具体数据
}
使用?声明一个可选类型,表示该变量肯为nil
!强制解包,将可选类型强制转换为非可选类型
if let 安全解包方式,判断可选值是否为nil,若有值,将值绑定到新变量并执行代码块,若为nil则跳过代码块
guard let 可选绑定-提前退出
??用于为可选值提供默认值
Objective-C 中strong
、weak
、assign
、copy
四种属性修饰符的区别是什么?
修饰符 | 内存管理策略 | 适用场景 | 核心特点 |
---|---|---|---|
strong | 强引用,会增加对象的引用计数(retain ),持有对象直至自身被释放。 | 大多数对象类型(如自定义类实例) | 确保对象不会被意外释放,是默认的对象修饰符。 |
weak | 弱引用,不增加引用计数,当对象被释放后,指针会自动置为 nil (避免野指针)。 | 避免循环引用的场景(如代理 delegate ) | 不持有对象,对象销毁后自动清空指针,防止访问已释放内存导致崩溃。 |
assign | 直接赋值,不涉及引用计数,适用于基本数据类型。 | 非对象类型(如 int 、float 、CGFloat 等) | 用于基本数据类型,若用于对象类型,对象释放后指针不会清空,可能导致野指针。 |
copy | 创建对象的副本并持有副本(copy 操作),不关心原对象的生命周期。 | 不可变对象(如 NSString 、NSArray 等) | 确保属性持有独立的副本,避免受原对象修改的影响。 |
Swift 中的inout
参数和 Objective-C 的指针参数有什么异同
两者的核心目的一致:允许函数修改外部传入的变量,打破了函数参数默认的 "值传递" 限制。
Objective-C 指针参数:通过传递变量的内存地址(指针),函数可直接修改指针指向的内存数据。
Swift inout
参数:通过特殊的传递机制,使函数能够修改外部变量的值。基于 "复制 - 修改 - 写回"(Copy-In-Copy-Out)机制,并非直接操作内存地址
Objective-C 的@dynamic
和 Swift 的dynamic
关键字作用有何不同?(考察对动态特性的理解)
oc中的@dynamic与synthesize均用于处理属性的存取方法,但是行为完全相反
synthesize为默认行为,告诉编译器自动生成属性的存取方法,如果未显示声明,那么编译器会默认为属性生成synthesize property = _property
@dynamic告诉编译器不要自动生成存取方法和实例变量,由开发者来提供
在swift中
KVO(键值观察):Swift 的属性默认不支持 KVO,标记 dynamic
后才能被观察
隐含 @objc
的效果(自动暴露给 Runtime),且强制开启动态分发,确保成员能被 Runtime 动态修改(如方法交换)。
Swift 的Extension
能否添加存储属性?如果不能,如何实现类似 “添加存储属性” 的效果?**(考察对 Swift 类型系统的理解)
扩展是不改变原有类型结构的前提下为其添加功能
扩展的核心作用是 “扩展功能” 而非 “修改结构
通过 Objective-C Runtime 的关联对象机制,为类(Class)添加 “伪存储属性”
Objective-C 中NSObject
的isKindOfClass:
和isMemberOfClass:
有什么区别?在 Swift 中如何实现类似判断?(考察对类继承与元类的理解)
在 Objective-C 中,一切对象(包括类本身)都是 NSObject
的实例,存在两条关键的继承链
实例对象的继承链:实例对象 → 类对象(如 [Person class]
)→ 父类对象(如 [NSObject class]
)→ nil
。
类对象继承链:类对象 → 元类对象(Meta Class,类的 “类”)→ 父元类对象 → 根元类(Root Meta Class)→ 根类(NSObject
)→ nil
。
调用 [obj isKindOfClass:cls]
时,会检查 obj
的整个继承链(包括自身所属类及所有父类)。
调用 [obj isMemberOfClass:cls]
时,仅检查 obj
直接所属的类(不包含父类)。
Swift 中protocol
的mutating
关键字有什么作用?
mutating
关键字用于修饰协议中的方法,表明该方法 允许修改遵循协议的结构体(struct
)或枚举(enum
)的实例本身。