面试题答案
一键面试Swift键值编码(KVC)底层原理
- 基本概念:KVC是一种通过键来访问对象属性的机制,它提供了一种间接访问对象属性的方法,而不仅仅依赖于直接的属性访问语法。
- 查找路径:
- 当通过KVC访问属性时,对象首先会在自身的属性列表中查找对应的键。如果找到了匹配的属性,就直接返回该属性的值。
- 如果在自身属性列表中未找到,对象会检查是否有
accessInstanceVariablesDirectly
方法返回true
(默认是true
)。若为true
,则会按照_<key>
、_is<Key>
、<key>
、is<Key>
的顺序查找实例变量。 - 如果仍然未找到,对象会调用
valueForUndefinedKey(_:)
方法,默认实现会抛出NSUndefinedKeyException
异常。
- 设值过程:
- 当通过KVC设置属性值时,对象会首先查找
set<Key>:
形式的方法。如果找到,就调用该方法来设置值。 - 如果没有找到
set<Key>:
方法,对象会检查accessInstanceVariablesDirectly
是否为true
。若为true
,则会按照与取值类似的顺序查找实例变量并设置值。 - 如果都未成功,对象会调用
setValue:forUndefinedKey:
方法,默认实现会抛出NSUndefinedKeyException
异常。
- 当通过KVC设置属性值时,对象会首先查找
在复杂自定义对象层级结构中通过路径访问深层嵌套对象属性并修改
- 示例对象层级结构:
假设有如下自定义对象层级结构:
class Address { var street: String? } class Person { var address: Address? } class Company { var ceo: Person? }
- 使用KVC访问并修改深层属性:
let company = Company() let person = Person() let address = Address() address.street = "123 Main St" person.address = address company.ceo = person if let street = company.value(forKeyPath: "ceo.address.street") as? String { print("Original street: \(street)") } company.setValue("456 Elm St", forKeyPath: "ceo.address.street") if let newStreet = company.value(forKeyPath: "ceo.address.street") as? String { print("New street: \(newStreet)") }
异常情况及处理方式
- 键不存在异常:
- 当访问或设置一个不存在的键时,会抛出
NSUndefinedKeyException
异常。 - 处理方式:
- 在访问属性前,可以先使用
responds(to:)
方法检查对象是否响应对应的KVC操作。例如:if company.responds(to: #selector(NSObject.value(forKeyPath:))) { if let street = company.value(forKeyPath: "ceo.address.street") as? String { print("Street: \(street)") } }
- 可以重写
valueForUndefinedKey(_:)
和setValue:forUndefinedKey:
方法来自定义处理逻辑,避免抛出异常。例如:class Company { var ceo: Person? override func valueForUndefinedKey(_ key: String) -> Any? { return nil } override func setValue(_ value: Any?, forUndefinedKey key: String) { // 自定义处理逻辑,比如记录日志等 } }
- 在访问属性前,可以先使用
- 当访问或设置一个不存在的键时,会抛出
- 类型不匹配异常:
- 当设置的值与属性的预期类型不匹配时,会抛出
NSInvalidArgumentException
异常。 - 处理方式:在设置值之前,进行类型检查。例如:
let newStreetValue = "789 Oak St" if let street = company.value(forKeyPath: "ceo.address.street") as? String { if type(of: newStreetValue) == type(of: street) { company.setValue(newStreetValue, forKeyPath: "ceo.address.street") } else { print("Type mismatch. Cannot set value.") } }
- 当设置的值与属性的预期类型不匹配时,会抛出