develop
KVC KVO (Key-Value Coding, Key-Value Observing) 본문
KVC는 Key-Value Coding의 약자이다.
Swift3에서는 #keyPath()로 키패스를 가져왔지만 Swift4에서는 \만 적어주면 키패스를 가져올수 있게 되었다.
class Example: NSObject {
var value: String = ""
}
print(\Example.value)
Swift.ReferenceWritableKeyPath<__lldb_expr_1.Example, Swift.String>
수정도 간단하다.
class Example: NSObject {
var value: String = "HI"
}
let example = Example()
print(example[keyPath: \Example.value])
example[keyPath: \Example.value] = "HELLO"
print(example[keyPath: \Example.value])
HI
HELLO
구조체에서도 가능하다.
struct Example {
var value: String = "HI"
}
var example = Example()
print(example[keyPath: \Example.value])
example[keyPath: \Example.value] = "HELLO"
print(example[keyPath: \Example.value])
HI
HELLO
KVO는 Key-Value Observing의 약자이다.
값 변화를 인식한다.
class Example: NSObject {
@objc dynamic var value: String = ""
}
let example = Example()
let keyValueObservation = example.observe(\.value) { (object, change) in
print("Changed Value: \(object.value)")
}
example.value = "Hello"
Changed Value: Hello
NSObject를 상속받은 클래스만 사용가능하다.
키패스를 알고싶은 변수에 dynamic 키워드를 붙여줘야 한다.
dynamic를 붙여주면 dynamic dispatch(동적 디스패치)가 활성화 된다.
dynamic dispatch는 Objective-C를 동적으로 만드는 기능중의 하나다.
Objective-C 런타임이 호출해야하는 특정 메서드나 함수의 구현을 런타임에 결정한다.
반대되는 개념이 static dispatch(정적 디스패치) 이다.
런타임이 아닌 컴파일타임에 메서드나 함수의 구현을 결정하고 dynamic dispatch보다 속도가 더 빠르다.
dynamic키워드를 사용하면 @objc를 붙여줘야 한다. 붙이지 않으면 런타임 에러가 난다.
Fatal error: Could not extract a String from KeyPath Swift.ReferenceWritableKeyPath<Example.Example, Swift.String>: file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang_overlay_Foundation_Sim/swiftlang-1100.2.259.70/swift/stdlib/public/Darwin/Foundation/NSObject.swift, line 155
2019-11-07 21:08:46.247883+0900 Example[24991:86133] Fatal error: Could not extract a String from KeyPath Swift.ReferenceWritableKeyPath<Example.Example, Swift.String>: file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang_overlay_Foundation_Sim/swiftlang-1100.2.259.70/swift/stdlib/public/Darwin/Foundation/NSObject.swift, line 155
@objc 는 Objective-C 호환성을 만들어준다.
변화를 추적할 프로퍼티가 많은데 다 @objc를 붙여줄 필요 없이 @objcmembers라는 키워드를 붙여줄 수도 있다.
@objcMembers
class Example: NSObject {
dynamic var value: String = ""
dynamic var value2: String = ""
}
let example = Example()
let keyValueObservation = example.observe(\.value) { (object, change) in
print("Changed Value: \(object.value)")
}
let keyValueObservation2 = example.observe(\.value2) { (object, change) in
print("Changed Value2: \(object.value2)")
}
example.value = "Hello"
example.value2 = "Hello2
Changed Value: Hello
Changed Value2: Hello2
options을 줄수 있다.
initial, old, new, initial 총 4가지 타입이 구조체 2021/01/15 - [iOS] - OptionSet 으로 되어 있다.
initial는 초기값을 얻을 수 있다.
@objcMembers
class Example: NSObject {
dynamic var value: String = "Initial"
}
let example = Example()
let keyValueObservation = example.observe(\.value, options: [.initial]) { (object, change) in
print("value: \(object.value)")
}
value: Initial
let example = Example()
let keyValueObservation = example.observe(\.value, options: [.initial]) { (object, change) in
print("value: \(object.value)")
}
example.value = "Hello"
value: Initial
value: Hello
Old와 New는 변하기전 값과 변한 후의 값을 보여준다.
let example = Example()
let keyValueObservation = example.observe(\.value, options: [.old, .new]) { (object, change) in
print("value: \(object.value), oldValue: \(String(describing: change.oldValue)), newValue: \(String(describing: change.newValue))")
}
example.value = "Hello"
example.value = "Hello2"
value: Hello, oldValue: Optional("Initial"), newValue: Optional("Hello")
value: Hello2, oldValue: Optional("Hello"), newValue: Optional("Hello2")
prior는 new와 old처럼 단일로 호출되는게 아닌 이전, 이후 따로 따로 호출된다.
isPrior라는 Bool 값이 있는데 변경 전에 호출될때는 true를 반환하고 변경후에는 false를 호출한다.
let example = Example()
let keyValueObservation = example.observe(\.value, options: [.prior]) { (object, change) in
print("value: \(object.value), isPrior: \(change.isPrior)")
}
example.value = "Hello"
example.value = "Hello2"
value: Initial, isPrior: true
value: Hello, isPrior: false
value: Hello, isPrior: true
value: Hello2, isPrior: false
함수 안에서 self를 사용하게 되면 [weak self] 캡쳐리스트를 사용해 줘야 한다.
deinit시에 observe를 remove해줘야 한다.
NSKeyValueObservation 배열을 사용하여 NSKeyValueObservation를 관리하게 되면 deinit시에 배열에서 제거할수 있어서 관리하기에 편하다.
컨트롤러에서 WKWebView를 이용해서 observe를 사용해보겠다.
class ViewController: UIViewController {
deinit {
if let keyPath = (\WKWebView.isLoading).toString {
self.webView.removeObserver(self, forKeyPath: keyPath)
}
if let keyPath = (\WKWebView.title).toString {
self.webView.removeObserver(self, forKeyPath: keyPath)
}
if let keyPath = (\WKWebView.url).toString {
self.webView.removeObserver(self, forKeyPath: keyPath)
}
if let keyPath = (\WKWebView.estimatedProgress).toString {
self.webView.removeObserver(self, forKeyPath: keyPath)
}
}
private let webView: WKWebView = {
let webView = WKWebView()
return webView
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if let keyPath = (\WKWebView.isLoading).toString {
self.webView.addObserver(self, forKeyPath: keyPath, options: .new, context: nil)
}
if let keyPath = (\WKWebView.title).toString {
self.webView.addObserver(self, forKeyPath: keyPath, options: .new, context: nil)
}
if let keyPath = (\WKWebView.url).toString {
self.webView.addObserver(self, forKeyPath: keyPath, options: .new, context: nil)
}
if let keyPath = (\WKWebView.estimatedProgress).toString {
self.webView.addObserver(self, forKeyPath: keyPath, options: .new, context: nil)
}
self.view.addSubview(self.webView)
self.webView.frame = self.view.frame
if let url = URL(string: "https://www.google.com") {
self.webView.load(URLRequest(url: url))
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let webView = object as? WKWebView {
if keyPath == (\WKWebView.isLoading).toString {
print("isLoading: \(webView.isLoading)")
return
}
if keyPath == (\WKWebView.title).toString {
print("title: \(webView.title ?? "")")
return
}
if keyPath == (\WKWebView.url).toString {
print("url: \(webView.url ?? URL(fileURLWithPath: ""))")
return
}
if keyPath == (\WKWebView.estimatedProgress).toString {
print("estimatedProgress: \(webView.estimatedProgress)")
return
}
}
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
extension AnyKeyPath {
var toString: String? {
return _kvcKeyPathString?.description
}
}
estimatedProgress: 0.1
url: https://www.google.com/
isLoading: true
estimatedProgress: 0.1736083984375
title: Google
estimatedProgress: 0.8722347484328532
estimatedProgress: 1.0
isLoading: false
AnyKeyPath을 확장해서 에서 KeyPath String값을 추출한다.
'iOS' 카테고리의 다른 글
LazySequence (0) | 2021.02.02 |
---|---|
Property(Stored Property, Lazy Property, Computed Property, Property Observers, Type Property) (0) | 2021.02.01 |
Reference Equal 참조 비교하기 (0) | 2021.01.31 |
Struct Mutating (0) | 2021.01.30 |
Require (0) | 2021.01.29 |