์ง๋ ๊ธ์์๋ Objective-C์์ KVC/KVO๊ฐ ์ด๋ป๊ฒ ์ฐ์๋์ง ์์๋ณด์๋ค. ์ด๋ฒ์๋ Swift๋ค.
Key Value Coding
์ฌ๊ธฐ๊น์ง๋ Objective C์์ ์ฌ์ฉํ๋ ๊ฒ๋ค์ด์๋ค. ์ด์ ๋ ์ค์ ๋ง์ด ์ฌ์ฉํ๋ Swift์์ ์ด๋ค์ง ๋ณด์. ์ฌ์ค ์์ฃผ ๋ง์ฃผํ ์ผ์ ์๋ค. ์๋ํ๋ฉด..
struct CreditCard {
var address: String
}
struct Person {
var creditCard: CreditCard
}
let creditCard = CreditCard(address: "์ฉ์ธ์")
let wansik = Person(creditCard: creditCard)
let wansikAddress = wansik.creditCard.address
ํน์ ์ฃผ์๊ฐ ํ์ํ๋ค๋ฉด, ์ง์ ์ ์ผ๋ก ์ ๊ทผํด์ ๊ฐ์ ์ฝ์ด์ค๋ฉด ๋๊ธฐ ๋๋ฌธ์ด๋ค. ๊ทธ๋ฌ๋ฉด ์ด๋ป๊ฒ Swift์์ ์ฌ์ฉํ ์ ์์๊น?
let wansikAddress = wansik[keyPath: \.address.town]
wansik[keyPath: \.address.town] = "์์์"
KeyPath๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ ธ์ฌ ์ ์๋ค! ๊ทธ๋ฆฌ๊ณ ๊ฐ์ ์ ๊ทผํด์ ์์ ๋ ๊ฐ๋ฅํ๋ค. Objective C์์๋ setValue:
๋ก ๊ฐ๋ฅํ์๋๋ฐ, ์ด์ ๋ ์ง์ ๋ฃ๋ ๊ฒ๋ ๊ฐ๋ฅํ๋ค.
๊ทธ๋ฐ๋ฐ Objective C์์ ์ฌ์ฉํ๋ KeyPath์ ๋ญ๊ฐ ๋ชจ์์ด ๋ค๋ฅด๋ค. ๊ฑฐ๊ธฐ์๋ ์ ๋ง path์ ์๋ฏธ๋ฅผ ๊ฐ์ง key์ ์ญํ (๋ฌธ์์ด๊ณผ .์ ์ฃผํฉ)์ ํ๋ ๋๋์ด ๊ฐํ๋ค๋ฉด, ์ฌ๊ธฐ์๋ ๋ญ๊ฐ ๊ท์น์ด ์์ด๋ณด์ธ๋ค. ๋ค์ ํฌ์คํ ์ผ๋ก ๋๊ธฐ์.
Key Value Observing
ํด๋น ๊ฐ๋ ์ Objective C์ ๋์ผํ๋ค. ์ฆ ๊ฐ์ฒด์ Property ๋ณ๊ฒฝ์ฌํญ์ ๋ค๋ฅธ ๊ฐ์ฒด์ ์๋ฆฌ๊ธฐ ์ํ ํจํด์ด๋ค. Model๊ณผ View์ฌ์ด์ ๋ ผ๋ฆฌ์ ์ผ๋ก ๋ถ๋ฆฌ๋ ๋ ์๋ค์ Sync๋ฅผ ๋ง์ถ๊ธฐ ์ํด ์ฌ์ฉํ๋ค. Objective C ๋ฐํ์์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ NSObject๋ฅผ ์์ํ class์์๋ง KVO๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
Observed Object Setup
struct CreditCard {
var address: String
}
struct Person {
var creditCard: CreditCard
}
์, ์๊น๋ณด์๋ ์์ ์ด๋ค. ์์์ด๋ผ๋ ์ฌ๋์ด ์ ๋ถ์๋ผ ๋๋ง์ ๋ค๋
์ ์ฃผ์๋ฅผ ๊ณ์ํด์ ๋ฐ๊พผ๋ค๊ณ ์๊ฐํด๋ณด์.(์ต์ง๋ค) ํ์ฌ ์
์ฅ์์๋ ์ด๋
์์ address๊ฐ ๋ณ๊ฒฝ๋๋ ์์ ์ ์๋ฆผ์ ๋ฐ๊ธธ ์ํ ๊ฒ์ด๋ค. ๊ทธ๋์ ์ ๋
์์ KVO๋ฅผ ์ฌ์ฉํ์ฌ Observingํ ๊ฒ์ด๋ค. ๊ทธ๋ฌ๊ธฐ ์ํด์๋ ํด์ค์ผ ํ๋ ์์
๋ค์ด ์๋ค.
class CreditCard: NSObject {
@objc dynamic var address: String
init(address: String) {
self.address = address
}
}
- class๋ก ๋ณ๊ฒฝ
- NSObject ์์: NSObject ์์ ํด๋์ค์์๋ง KVO ์ฌ์ฉ๊ฐ๋ฅ
@objc
์ถ๊ฐ: Objective runtime ์ฌ์ฉํ ๊ฑฐ์ผ!dynamic
modifier ์ถ๊ฐ: Objective C์ dynamic dispatch๋ฅผ ์ฌ์ฉํ ๊ฑฐ์ผ!
dynamic
์ ๋ํ ๊ฒ์ ์ถํ ํฌ์คํ
์์ ์ฒ๋ฆฌํ๋๋ก ํ์. ์ด๊ฒ๋ ํ ์๊ธฐ๊ฐ ๋ง์ ์ฃผ์ ์ธ ๋ฏํ๋ค.
์ผ๋จ ์ฌ๊ธฐ๊น์ง ํ๋ฉด ์ class์ property๋ฅผ observingํ ์ ์๋ ์ํ๊ฐ ๋์๋ค.
Observer definition
class CreditCard: NSObject {
@objc dynamic var address: String
init(address: String) {
self.address = address
}
}
class ViewController: UIViewController {
private let town = ["์ฉ์ธ์", "์์์", "๊ด์ฃผ์", "์์ธ์", "ํ๋จ์"]
private let creditCard = CreditCard(address: "์ฉ์ธ์")
private var observers: [NSKeyValueObservation] = []
override func viewDidLoad() {
let observer = self.creditCard.observe(\.address, options: [.old, .new]) { (object, change) in
print(object)
print(change.oldValue, change.newValue)
}
self.observers.append(observer)
}
@IBAction func buttonTouched(_ sender: Any) {
self.creditCard.address = "\(self.town[Int.random(in: (0..<5))])์"
}
}
๋ฒํผ์ ๋๋ฅผ ๋๋ง๋ค ๋๋ง๊ฐ๋๋ก ํด๋ณด์. observer ๊ฐ์ฒด๋ ๋ฐ์์, VC๊ฐ deinit๋๋ ์์ ์ ๋ฉ๋ชจ๋ฆฌ์์ ๋ ์๊ฐ๋๋ก ํ์๋ค.
<test.CreditCard: 0x60000101e480>
Optional("์ฉ์ธ์") Optional("๊ด์ฃผ์์")
<test.CreditCard: 0x60000101e480>
Optional("๊ด์ฃผ์์") Optional("์์ธ์์")
<test.CreditCard: 0x60000101e480>
Optional("์์ธ์์") Optional("ํ๋จ์์")
<test.CreditCard: 0x60000101e480>
Optional("ํ๋จ์์") Optional("ํ๋จ์์")
<test.CreditCard: 0x60000101e480>
Optional("ํ๋จ์์") Optional("ํ๋จ์์")
<test.CreditCard: 0x60000101e480>
Optional("ํ๋จ์์") Optional("๊ด์ฃผ์์")
<test.CreditCard: 0x60000101e480>
Optional("๊ด์ฃผ์์") Optional("์์ธ์์")
object๋ ์ค์ ๊ด์ฐฐํ๊ณ ์๋ ๋ ์์ ๋ณด๋ด์ฃผ๊ณ , change์์๋ ์ด์ ๊ฐ, ์ ๊ฐ์ ๋ณด๋ด์ฃผ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
Option
์์ ์์ ์์ ๊ด์ฐฐํ๋ ๋์์ keyPath๋ก ์ค ๊ฒ์ธ์ option์ด๋ผ๋ ๊ฐ์ ์ฃผ์์๋ค. ์ด๊ฒ ๋ฌด์์ธ์ง ์์๋ณด์. NSKeyValueObservingOptions
์ธ ๊ตฌ์กฐ์ฒด๋ก ์ ์๋์ด ์๋ค. .old
, .new
๋ ๋ณด์์ผ๋ ๋์ด๊ฐ๊ณ , .initial
, .prior
๋ง ์์๋ณด์.
.initial
combine์ ์ฐ๋ค๋ณด๋ฉด, ์ด๊ธฐ ์ฐ๊ฒฐ๋ ์๊ธฐ์ ๊ฐ์ ๋ฐ์๋ณด๊ณ ์ถ์ ๊ฒฝ์ฐ๊ฐ ์๋ค. ์ด ๊ฒฝ์ฐ๊ฐ ๋ฑ ๊ทธ๊ฑฐ๋ค. ์ฒ์์ ๋ณ๊ฒฝํ์ง ์์ ์์ ์๋ handler๊ฐ ๋ถ๋ฆฌ๊ฒ ํ๊ณ ์ถ๋ค๋ฉด ํด๋น ์ต์ ์ ์ผ๋ฉด ๋๋ค.
override func viewDidLoad() {
let observer = self.creditCard.observe(\.address, options: [.old, .new, .initial]) { (object, change) in
print(change.oldValue, change.newValue)
}
self.observers.append(observer)
}
nil Optional("์ฉ์ธ์")
Optional("์ฉ์ธ์") Optional("์์์")
Optional("์์์") Optional("ํ๋จ์")
์ด๋ฐ์์ผ๋ก newValue์ ๊ฐ์ด ๋ด๊ฒจ์ ์ฒ์์ ์ค๊ฒ ๋๋ค.
.prior
๋ฐ๋ก ์ด์ ์ ์ํ๋ฅผ ๊ฐ์ด ์ค๋ค! ์ด๊ฒ ๋ฌด์จ ๋ง์ด๋๋ฉด ๋ง๊ทธ๋๋ก ์ด์ ์ ์ถ๋ ฅ๋ ์น๊ตฌ๊น์ง ๊ฐ์ด ์ค์ ๋งํ๋ค.
override func viewDidLoad() {
let observer = self.creditCard.observe(\.address, options: [.old, .new, .prior]) { (object, change) in
if change.isPrior {
print("์ด์ ์ํ์ ๊ฐ์ด์์!", change.oldValue, change.newValue)
} else {
print("์ด๋ฒ์ ๋ณ๊ฒฝ๋ ์ํ๊ฐ์ด์์!", change.oldValue, change.newValue)
}
}
self.observers.append(observer)
}
์ด์ ์ํ์ ๊ฐ์ด์์! Optional("์ฉ์ธ์") nil
์ด๋ฒ์ ๋ณ๊ฒฝ๋ ์ํ๊ฐ์ด์์! Optional("์ฉ์ธ์") Optional("ํ๋จ์")
์ด์ ์ํ์ ๊ฐ์ด์์! Optional("ํ๋จ์") nil
์ด๋ฒ์ ๋ณ๊ฒฝ๋ ์ํ๊ฐ์ด์์! Optional("ํ๋จ์") Optional("์ฉ์ธ์")
์ด์ ์ํ์ ๊ฐ์ด์์! Optional("์ฉ์ธ์") nil
์ด๋ฒ์ ๋ณ๊ฒฝ๋ ์ํ๊ฐ์ด์์! Optional("์ฉ์ธ์") Optional("ํ๋จ์")
์ด์ ์ํ์ ๊ฐ์ด์์! Optional("ํ๋จ์") nil
์ด๋ฒ์ ๋ณ๊ฒฝ๋ ์ํ๊ฐ์ด์์! Optional("ํ๋จ์") Optional("์์์")
์ถ๋ ฅ์ ๋ณด๋ฉด ๊ธ๋ฐฉ ์ดํดํ ์ ์๋ค.
์ฅ๋จ์
์ผ๋จ ์ด ๊ธ์ ์ฝ๋ค๋ณด๋ฉด, ๋ง์ด๋ค ์ฌ์ฉํ๋ willSet
, didSet
๊ณผ ๊ฐ์ propert observer์ ๋น์ทํ๋ค๋ ๊ฒ์ ๋์น์ฑ ์ ์๋ค. ํ์ง๋ง ๋ค๋ฅธ ์ ์ด ์กด์ฌํ๋๋ฐ, KVO๊ฐ์ ๊ฒฝ์ฐ Object ์ธ๋ถ์์ observer๋ฅผ ๊ฑธ ์ ์๋ค๋ ์ ์ด๋ค.
์ฅ์
๋จผ์ , Model, View์ ๊ฐ์ด ๋ ๊ฐ์ฒด ์ด์์ ๋๊ธฐํ๋ฅผ ๋ฌ์ฑํ ์ ์๋ค. ์ด๊ฑด ์์์๋ ์ด์ผ๊ธฐ ํ ๋ด์ฉ์ด๋ค.
๊ฐ์ฒด์ ๊ตฌํ์ ๋ณ๊ฒฝํ์ง ์๊ณ , ์ํ ๋ณํ์ ๋์์ด ๊ฐ๋ฅํ๋ค. ์ฆ, ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์๋๋ฐ, ์ฌ๊ธฐ์ ๋ด๋ถ์ ์ผ๋ก Property observer๋ฅผ ๋ฌ ์ ์๋ ์ํฉ์์๋ ์ธ๋ถ์์ KVO๋ฅผ ์ฌ์ฉํ๋ฉด ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํ๋ค. ํ์ง๋ง ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์๋ property๊ฐ Objective runtime ์ ๊ณต(@objc
, dynamic
)ํ์ง ์์ผ๋ฉด ์๋๋ ๊ฒ์ด ์๋์ง..
Observed Property์ old value, new value๋ฅผ ์ป์ ์ ์๋ค.
KeyPath๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ nested Property ๊ด์ฐฐ์ด ๊ฐ๋ฅํ๋ค.
class CreditCard: NSObject {
class Address: NSObject {
@objc dynamic var location: String
init(location: String) {
self.location = location
}
}
@objc dynamic var address: Address
init(address: Address) {
self.address = address
}
}
class ViewController: UIViewController {
private let town = ["์ฉ์ธ์", "์์์", "๊ด์ฃผ์", "์์ธ์", "ํ๋จ์"]
private let creditCard = CreditCard(address: CreditCard.Address(location: "์ฉ์ธ์"))
private var observers: [NSKeyValueObservation] = []
override func viewDidLoad() {
// Note!
let observer = self.creditCard.observe(\.address.location, options: [.old, .new]) { (object, change) in
print(change.oldValue, change.newValue)
}
self.observers.append(observer)
}
@IBAction func buttonTouched(_ sender: Any) {
self.creditCard.address.location = "\(self.town[Int.random(in: (0..<5))])"
}
}
Optional("์ฉ์ธ์") Optional("์ฉ์ธ์")
Optional("์ฉ์ธ์") Optional("์์ธ์")
Optional("์์ธ์") Optional("๊ด์ฃผ์")
์ด์ ๊ฐ์ด keyPath๋ก ์ ๊ทผํ์ฌ ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค๋ ๋ป์ด๋ค.
๋ง์ง๋ง์ผ๋ก ์ถ๊ฐ์ ์ผ๋ก Observer๋ฅผ ํด์ ํ์ง ์์๋ ๋๋ค๋ ์ ์ด๋ค. ์ด๊ฑด ๋ชฐ๋๋ค. ์๋ฃ๋ฅผ ์ฐพ์๋ณด๋, Foundation Release Notes for macOS 10.13 and iOS 11์ โRelaxed Key-Value Observing Unregistration Requirementsโ์ ๋ช ์๋์ด ์๋ ๊ฒ์ ํ์ธํ๋ค. ์ข ๋ ๊ตฌ์ฒด์ ์ผ๋ก ์ดํดํ๊ณ ์ถ๋ค๋ฉด When is KVO unregistration automatic?๊ธ์ ์ฐธ๊ณ ํ์.
๋จ์
์ผ๋จ Objective C ๋ฐํ์์ ์์กดํ๊ฒ ๋๋ค. NSObject
๋ ์์ํด์ผ ํ๊ณ @objc
, dynamic
modifier๋ ๋ฌ์์ฃผ์ด์ผ ํ๋ค.
์ถ๊ฐ์ ์ผ๋ก ์๊ฒ ๋๋ ๊ฒ์ด ์๋ค๋ฉด ์ฌ๊ธฐ์ ์ ์ด๋๋๋ก ํ๊ฒ ๋ค.
๋ง๋ฌด๋ฆฌ
์ด๋ ๊ฒ Swift์์ KVC, KVO๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ๊น์ง ์ตํ๋ณด์๋ค. ๋ค์์๋ ์ด์ ์ ๋ค๋ฃจ์ง ๋ชปํ ๊ฒ๋ค์ ๋ํด ์ ์ด๋ณด๊ฒ ๋ค. ๋!
Reference
- About Key-Value Coding
- Key-value coding
- https://jcsoohwancho.github.io/2019-11-28-Key-Value-Coding(KVC)/
- Understanding KVC and KVO in Objective-C
- whatโs the difference between single key and keypath?
- Whatโs New in Foundation
- NSKeyValueObservingOptions
- Delegate, NOTIFICATION,KVO pros and cons?
- Foundation Release Notes for macOS 10.13 and iOS 11
- When is KVO unregistration automatic?