본문은 야곰 아카데미 커리어 스타터 캠프를 통해 학습한 내용을 회고한 글입니다.
프로퍼티 옵저버 Property Observer
프로퍼티 옵저머는 프로퍼티의 값 변화를 관측하고 반응한다. 프로퍼티 옵저버는 프로퍼티의 값이 설정될 때마다 호출이되는데, 프로퍼티의 현재 값과 새 값이 같을 때도 호출이 된다. 다음과 같은 곳에서 프로퍼티 옵저버를 사용할 수 있다:
- 정의한 저장 프로퍼티
- 상속받은 저장 프로퍼티
- 상속 받은 연산 프로퍼티
상속받은 프로퍼티에서는, 서브클래스에서 그 프로퍼티를 오버라이딩함으로써 프로퍼티 옵저버를 더할 수 있다. 그리고 정의한 연산 프로퍼티에서는 옵저버를 만드는 대신에 프로퍼티의 setter를 사용할 수 있다. 다음의 옵저버를 둘 중 하나 혹은 둘 다 구현할 수 있다:
- willSet: 값이 저장되기 바로 전에 호출됨
- didSet: 새로운 값이 저장된 후에 바로 호출됨
willSet 옵저버를 구현하면 새로운 프로퍼티 값을 상수 매개변수로 바꾸어준다. willSet을 구현할 때 이 매개변수의 이름을 정해줄 수 있는데, 이름과 괄호를 사용해주지 않으면 자동으로 newValue라는 이름의 매개변수가 생성된다. 마찬가지로, didSet 옵저버를 구현할 때도, 이전의 프로퍼티 값을 가진 상수 매개변수가 생긴다. 이 매개변수의 이름을 지어줄 수도 있고 oldValue라는 이름을 사용할 수도 있다. didSet 옵저버를 사용해서 프로퍼티에 값을 할당하게 되면 설정했던 값이 새로운 값으로 바뀐다.
슈퍼클래스의 willSet과 didSet 옵저버는 프로퍼티가 서브클래스 이니셜라이저에서 설정될 때 호출된다. willSet과 didSet은 슈퍼클래스 이니셜라이저가 호출되고 클래스가 자신의 프로퍼티들을 설정할 때까지는 호출되지 않는다.
프로퍼티 옵저버 구현 예시 Property Observer implement sample
class Exam {
//score는 저장 프로퍼티
var score: Int = 0 {
//willSet은 값이 저장되기 전에 호출
willSet(newScore) {
print("시험 점수가 \(newScore)점이 되었습니다.")
}
//didSet은 값이 저장된 후에 호출
didSet(oldScore) {
if score > oldScore {
print("\(score - oldScore)점이 올랐습니다.")
} else if score < oldScore {
print("\(oldScore - score)점이 깎였습니다.")
} else {
print("점수에 변동이 없습니다.")
}
}
}
init(score: Int) {
self.score = score
}
func studyHard() {
self.score += 10
}
func doNothing() {
self.score -= 5
}
}
let exam = Exam(score: 0)
exam.score = 50
//시험 점수가 50점이 되었습니다.(값이 저장되기 전에 호출: 50이라는 값이 newScore에 먼저 들어감)
//50점이 올랐습니다.(값이 저장된 후에 호출: 50이라는 값이 score에 들어감. 50-0점이 올랐다고 출력해줌)
exam.studyHard()
//시험 점수가 60점이 되었습니다.(값이 저장되기 전에 호출: 50+10인 60이 newScore에 먼저 들어감)
//10점이 올랐습니다.(값이 저장된 후에 호출: 60이라는 값이 score에 들어감. 60-50점이 올랐다고 출력해줌)
exam.doNothing()
//시험 점수가 55점이 되었습니다.(값이 저장되기 전에 호출: 60-5인 55가 newScore에 먼저 들어감)
//5점이 깎였습니다.(값이 저장된 후에 호출: 55라는 값이 score에 들어감. 60-55점이 깎였다고 출력해줌)
exam.studyHard()
//시험 점수가 65점이 되었습니다.(값이 저장되기 전에 호출: 55+10인 65가 newScore에 먼저 들어감)
//10점이 올랐습니다.(값이 저장된 후에 호출: 65라는 값이 score에 들어감. 65-55점이 올랐다고 출력해줌)
이 경우는 score라는 정의한 저장 프로퍼티에 프로퍼티 옵저버를 구현한 경우라고 할 수 있다. wilSet은 값이 저장이 되기 전에 호출이 되므로 메서드를 호출하든 변수에 직접 값을 입력해주든, 그 새로운 값이 newScore에 먼저 들어간다. 그리고 나서 값이 score에 저장이 되면 didSet이 호출이 된다. 그러면 기존에 있던 값은 oldScore가 되고 score과의 차이를 통해 점수가 올랐는지 깎였는지를 출력해준다.
KVO
KVO에 대한 설명은 내가 써놓은 블로그의 내용을 참조해보자.
https://c-git-erg-sum.tistory.com/32
KVO 구현 예시 KVO implement sample
//Exam이라는 클래스는 NSObject를 상속하고 있어야한다.
class Exam: NSObject {
@objc dynamic var score: Int = 0
}
//Student이라는 클래스는 NSObject를 상속하고 있어야한다.
class Student: NSObject {
@objc var exam: Exam
var observation: NSKeyValueObservation?
init(exam: Exam) {
self.exam = exam
super.init()
observation = observe(\.exam.score, options: [.old, .new], changeHandler: { student, change in
//change.newValue와 change.oldValue는 옵셔널 값이기 때문에 옵셔널 바인딩을 해주었다.
guard let newScore = change.newValue, let oldScore = change.oldValue else {
return
}
print("시험점수가 \(newScore)점이 되었습니다.")
if newScore > oldScore {
print("\(newScore - oldScore)점이 올랐습니다.")
} else if newScore < oldScore {
print("\(oldScore - newScore)점이 깎였습니다.")
} else {
print("점수에 변동이 없습니다.")
}
})
}
}
let codingTest: Exam = Exam()
let rhode: Student = Student(exam: codingTest)
codingTest.score = 50
//시험점수가 50점이 되었습니다.
//50점이 올랐습니다.
codingTest.score = 70
//시험점수가 70점이 되었습니다.
//20점이 올랐습니다.
codingTest.score가 50으로 바뀌게 되면 observation 안의 change.newValue가 50으로 바뀐다. 그리고 그 전의 Exam 클래스에서 지정한 score의 값 0은 oldValue가 된다. 그리고 codingTest.score가 70점으로 바뀌게 되면 observation 안의 change.newValue가 70점으로 바뀐다. 그리고 그 전의 50점은 oldValue가 되는 것이다.
options에는 .old와 .new 외에도 .prior과 .initial이라는 옵션이 있다. prior과 initial은 조만간 공부해서 다시 올리는 것으로..ㅎㅎ
노티피케이션 센터 Notification Center
오브젝트는 노티피케이션을 받기 위해서 addObserver( :selector:name:object:) 혹은 addObserver(forName:object:queue:using:) 메서드를 사용해서 노티피케이션 센터를 등록할 수 있다. 오브젝트가 자기자신을 옵저버로 등록하게 되면, 어떤 노티피케이션을 받아야할지 구체화될 수 있다. 그래서 한 오브젝트는 자기자신을 여러 다른 노티피케이션의 옵저버로 지정하기 위해서 이 메서드를 여러번 호출할 수 있다. 한 노티피케이션 센터는 한 프로그램에서만 노티피케이션을 전달할 수 있기 때문에, 다른 과정에 노티피케이션을 전달코자 하거나 다른 프로세스에서 받아오고자한다면 DistributedNotificationCenter를 사용해야한다.
노티피케이션 센터 구현 예시 Notification Center implement sample
let center: NotificationCenter = NotificationCenter.default
class Subject {
var subjectName: Notification.Name
init(subjectName: Notification.Name) {
self.subjectName = subjectName
}
func startExam() {
center.post(name: subjectName, object: nil)
}
}
class Student {
var name: String
init(name: String) {
self.name = name
}
func takeCourse(_ name: Notification.Name) {
center.addObserver(forName: name, object: nil, queue: .main) { noti in
print("\(self.name)야 \(noti.name.rawValue) 시험 보러 가자!")
}
}
func withdrawCourse(_ name: Notification.Name) {
center.removeObserver(self, name: name, object: nil)
}
}
let yagomNotificationName = Notification.Name("야곰의 목요 활동학습")
let kioNotificationName = Notification.Name("키오의 월요 활동학습")
let yagom: Subject = Subject(subjectName: yagomNotificationName)
let kio: Subject = Subject(subjectName: kioNotificationName)
let rhode: Student = Student(name: "Rhode")
let gundy: Student = Student(name: "Gundy")
let rowan: Student = Student(name: "Rowan")
let jeremy: Student = Student(name: "Jeremy")
rhode.takeCourse(yagomNotificationName)
rhode.takeCourse(kioNotificationName)
gundy.takeCourse(yagomNotificationName)
rowan.takeCourse(kioNotificationName)
jeremy.takeCourse(yagomNotificationName)
jeremy.withdrawCourse(yagomNotificationName)
yagom.startExam()
//Rhode야 야곰의 목요 활동학습 시험 보러 가자!
//Gundy야 야곰의 목요 활동학습 시험 보러 가자!
//Jeremy야 야곰의 목요 활동학습 시험 보러 가자!
kio.startExam()
//Rhode야 키오의 월요 활동학습 시험 보러 가자!
//Rowan야 키오의 월요 활동학습 시험 보러 가자!
토요스터디
실험 3
IBAction을 활용해 Label에 값이 들어가도록 구현했던 것을 notification center를 사용하여 구현해보자!
import UIKit
struct Registrant {
let name: String
let phoneNumber: String
}
class ViewController: UIViewController {
@IBOutlet weak var nameTextField: UITextField!
@IBOutlet weak var phoneNumberTextField: UITextField!
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var phoneNumberLabel: UILabel!
var registrantList: [Registrant] = []
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func hitRegisterButton(_ sender: Any) {
guard let newName = nameTextField.text, let newPhoneNumber = phoneNumberTextField.text else {
return
}
register(newName: newName, newPhoneNumber: newPhoneNumber)
}
@IBAction func hitCheckButton(_ sender: Any) {
displayRegistrant()
}
func register(newName: String, newPhoneNumber: String) {
var NewInformation = Registrant(name: newName, phoneNumber: newPhoneNumber)
registrantList.append(NewInformation)
nameTextField.text = ""
phoneNumberTextField.text = ""
}
func displayRegistrant() {
nameLabel.text = registrantList.last?.name
phoneNumberLabel.text = registrantList.last?.phoneNumber
}
}
IBAction 활용 코드
import UIKit
struct Registrant {
let name: String
let phoneNumber: String
}
class ViewController: UIViewController {
let center:NotificationCenter = NotificationCenter.default
@IBOutlet weak var nameTextField: UITextField!
@IBOutlet weak var phoneNumberTextField: UITextField!
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var phoneNumberLabel: UILabel!
//var registrantList: [Registrant] = []
override func viewDidLoad() {
super.viewDidLoad()
center.addObserver(forName: .init("register"), object: nil, queue: .main) { noti in
//noti.object가 Any?이기 때문에 Registrant의 것들을 사용하기 위해서 다운캐스팅
guard let object = noti.object as? Registrant else {
return
}
//클로저 내부에선 캡쳐가 발생하여 self.붙여줘야함
self.nameLabel.text = object.name
self.phoneNumberLabel.text = object.phoneNumber
}
}
@IBAction func hitRegisterButton(_ sender: Any) {
guard let newName = nameTextField.text, let newPhoneNumber = phoneNumberTextField.text else {
return
}
register(newName: newName, newPhoneNumber: newPhoneNumber)
}
// @IBAction func hitCheckButton(_ sender: Any) {
// displayRegistrant()
// }
func register(newName: String, newPhoneNumber: String) {
let newInformation = Registrant(name: newName, phoneNumber: newPhoneNumber)
// registrantList.append(newInformation)
nameTextField.text = ""
phoneNumberTextField.text = ""
center.post(name: .init("register"), object: newInformation)
}
// func displayRegistrant() {
// nameLabel.text = registrantList.last?.name
// phoneNumberLabel.text = registrantList.last?.phoneNumber
// }
}
notification center 활용 코드
참조
https://zeddios.tistory.com/247
https://velog.io/@wansook0316/KVCKVO-in-Swift
'YAGOM CAREER STARTER' 카테고리의 다른 글
[TIL] 20230110: Protocol1 (0) | 2023.01.11 |
---|---|
[TIL] 20230109: Singleton (0) | 2023.01.10 |
[TIL] 20230106: Modality/Navigation (0) | 2023.01.06 |
[자료구조스터디] 20230104: Linked List (0) | 2023.01.06 |
[TIL] 20230105: Type Casting, Singleton, 반환타입, Nil 반환 (0) | 2023.01.06 |