개인적으로 나는 28도 이상이면 덥고 16도 이하면 춥기 때문에 온도 설정을 그에 맞게 지정해주었다. willSet
을 이용하여 온도가 변할 예정임을 알려주는 멘트와 변경 된 후 didSet
을 이용해 변경이 되었음을 안내해주는 걸 설정했고, 아래로 이어서 코드를 작성해갔다.
var isHeatingOn: Bool = false
와 var isCoolingOn: Bool = false
는 난방과 냉방 시스템이 켜져 있는지 여부를 나타내기 위한 Bool
을 선언한 것이다.
Bool 타입
단순히 참(true)
또는 거짓(false)
을 나타내는 자료형인데, 난방 시스템이 켜졌는지(isHeatingOn)
, 냉방 시스템이 켜졌는지(isCoolingOn)
여부를 확인하는 데 논리형 값
이 적합하기 때문에 사용했고, true
일 때는 시스템이 켜져있음 과 false
일 때는 꺼져 있음을 나타내준다.
객체가 처음 생성될 때는 일반적으로 난방이나 냉방 시스템이 꺼져 있는 상태로 시작하는 것이 자연스럽기 때문에 초기값을 false
로 설정한다. 예를 들어, TemperatureController
클래스가 처음 생성될 때는 아무것도 켜져 있지 않으므로 난방과 냉방 시스템의 상태를 false
로 설정하는 것이다.
난방을 켜고 싶을 때는 isHeatingOn = true
로 설정하고, 냉방을 켤 때는 isCoolingOn = true
로 설정하여 해당 시스템이 켜졌음을 표시한다. 코드에서 trueOnHeating
메서드와 trueOnCooling
메서드가 이를 담당하고 있다.
즉, false
로 시작하는 것은 시스템이 꺼져 있는 상태를 나타내며, 후에 조건에 따라 true
로 변경되어 해당 시스템이 작동 중이라는 것을 나타내는 것이다.
아래 코드는 내가 원래 작성했던 코드이다.
처음에 작성한 코드로도 물론 값이 잘 나오지만, 기왕 해 보는거 swift가 추천해준 toggle
을 이용해서도 기능을 만들어보고 싶었다.
toggle()
은 Bool
값의 true
를 false
로, false
를 true
로 자동으로 바꿔주는 기능을 한다.
예를 들어, isHeatingOn
이나 isCoolingOn
의 값을 직접 true
로 설정하는 대신 toggle()
을 사용해 값을 전환할 수 있는 것이다.
왼쪽이 전, 오른쪽이 후 이다.
처음에는 toggle
을 쓰는 것이 더 간결하다고 생각했던 이유가 그저 스위치를 껏다가 켜는 것처럼 코드를 실행할 때마다 toggle()
이 한 번의 메서드 호출로 true
에서 false
, 또는 false
에서 true
로 상태를 자동으로 반전시키기 때문이었다.
따로 isHeatingOn = true
나 isHeatingOn = false
같은 할당을 할 필요가 없어서 상태를 전환할 때 편리하다는 의미에서 그렇게 생각 했던 듯 하다.
하지만 실제 코드의 길이를 보면 toggle()
을 사용할 때 if
문을 사용해 켜거나 끄는 메시지를 출력하는 부분이 추가되어 더 길어졌으니 코드 길이 부분에서는 간결하진 않았다.
"간결하다"
는 표현은 코드를 작성하는 관점에서 상태 전환이 단순해진다는 의미라는데, 나는 그저 코드 길이가 짧아지면 간결하다라고 생각했다. toggle
의 간결함은 '상태 전환 편리함'의 간결함이었다.
따라서 단순히 켜기만 하는 것이 아니라 온도 변화에 따라 시스템을 켜고 끄는 기능을 구현해야 한다면 toggle()
이 더 나은 선택인 건 맞다.
이렇게 변경 전에 어떤 행동을 준비할 수 있게 도와주는 것이 willSet
의 역할이었고, 현재 온도와 새로 설정될 온도를 출력하는 메시지를 통해, 사용자는 어떤 변화가 있을지를 미리 알 수 있었다.
그리고 newValue
는 willSet
내에서 새로 설정될 값에 접근할 수 있는 특별한 변수였던 것도 알게 되었는데, 이걸 통해 현재의 temperature
와 비교하여 조건문을 사용할 수 있었고, 간단하게 새 값을 참조하는 방법을 배울 수 있었다.
추가로 toggle()
은 간결함과 편리함을 제공하는 것이고, 직접적으로 true
또는 false
를 설정하는 것은 명확성과 의도를 중시하는 상황에서 유리하다는 걸 알게 됐다. 상황에 맞게 선택하여 사용하는 것이 중요할 듯 하다.
결론적으로 willSet
은 상태 변화에 대한 피드백을 제공과 코드를 더 읽기 쉽게 만들어주고, 시스템의 작동 방식에 대한 이해를 돕는 중요한 기능이라는 것을 알게 된 공부였다.
온도를 1초마다 내리게 하여 적정온도가 되었을 때 자동으로 냉방을 종료하는 시스템을 만들어보았다.
만약 온도에 따라 냉방 또는 난방을 각각 켜고 끄는 복잡한 조건을 추가하고 싶다면 단순히 toggle()
을 사용하는 것보다는 상황에 맞게 isHeatingOn = true
처럼 상태를 명확히 제어하는 것이 더 나은 경우인 듯 하여 그렇게 했다.
상태 전환이 단순한 상황에서는 toggle()
이 유용하지만, 제어 코드가 복잡해질 때는 명시적인 상태 설정이 더 안전할 수 있다고 하니 toggle()
을 빼버리기로 결정.
우선 맨 처음 작성한 코드들과 다른 점은 첫 줄 부터 다르다.
Import Foundation
이 추가 되었는데, swift에서는 기본적으로 Int
, Double
, String
, Array
, Dictionary
등의 타입을 지원하기 때문에 전에 코드를 작성했던 것처럼 import Foundation
이 없어도 실행이 되고 사용할 수 있었다.
하지만 코드 내에 날짜
, 시간
, 파일 처리
, 네트워킹
등 고급 기능을 사용할 때는 반드시 import Foundation
이 필요하다는 것을 알게 되었고 바로 적용한 것이다.
Import Foundation
가 없으면 Timer
에 오류가 나는 것을 확인 할 수 있다.
그리고 그 뒤로 class
에서 willSet
의 내용이었던
willSet {
print("\(roomName)의 온도가 곧 \(temperature)도 에서 \(newValue)도 변경될 예정입니다.")
}
해당 코드를 뺐다.
이유는 해동 코드를 작성하게 되면 타이머가 작동할 때마다 알림이 뜨게 되니 실행할 때 지저분해 보일뿐더러, 굳이 1초마다 내려가는 온도를 미리 예고할 필요는 없기 때문이었다.
그리고 var properTemperature: Int = 24
라고 작성하여 적정온도를 설정해주는 변수 선언을 해주었다.
추가로 아래 var timer: Timer?
을 추가 해주었는데, 옵셔널이 붙은 이유는 timer
가 초기화되지 않았거나 나중에 중지된 후 nil
이 될 수 있기 때문이다. Timer
는 처음에 설정되지 않을 수 있다. 그래서 타이머가 언제 시작될지 모르기 때문에 처음에는 nil
상태로 두고 필요할 때 타이머를 생성 할 수 있도록 해주는 것.
그리고 타이머가 필요 없어지면 invalidate()
(*무효화 라는 뜻) 를 호출해서 타이머를 중지시킬 수 있다. 이때 더 이상 타이머가 유효하지 않으므로 nil
로 설정하여 타이머가 존재하지 않음을 확인할 수 있도록 한다. 옵셔널이 아니면 중지된 타이머에도 값이 남아있어 혼란을 줄 수 있으니 말이다.
그리고 바로 초기화를 진행하여 TemperatureController
클래스를 만들 때 방의 이름과 초기 온도를 설정하는 역할을 하게 해준다.
setTargetTemperature
의 목적은, 적정 온도를 설정하는 역할을 해줌으로서 (*newTargetTemperature는 사용자가 설정하고자 하는 새로운 적정 온도를 의미한다) 새로 설정된 적정 온도를 출력해주고 확인할 수 있도록 도와줄 수 있게 했다.
그리고 나서 startCoolingOrHeating()
를 호출하여 현재 온도에 따라 에어컨이나 난방을 작동시키도록 설정해주었다.
그 뒤로 온도 조절 시작 메서드인 startCoolingOrHeating
에는 현재 온도에 따라 에어컨이나 난방을 켜고 조절해주는 역할을 한다. 만약 현재온도가 적정온도보다 높을 경우 에어컨을 켜고 온도를 낮추겠다고 출력하고, 그 반대일 경우 난방을 켜고 온동을 높이겠다고 출력할 것이다.
작동 방법은 단순하게 에어컨이 켜질 경우 isCoolingOn
을 true
로, 난방이 켜질 경우 isHeatingOn
을 true
로 설정하고, 반대는 false
로 설정했다.
그리고 나서 1초마다 실행되는 타이머를 설정해주었는데 여기서 좀 복잡했다. 이 기능을 넣으려고 Import Foundation
도 맨 위에 넣어주었으니 기왕이면 잘 사용해보자!
해당 코드는 1초마다 실행되는 타이머인데, 반복해서 호출될 때마다 온도를 증가시키거나 감소시키는 기능을 해주는 것이다.
Timer.scheduledTimer
는 일정한 시간 간격으로 반복적이게 실행되는 타이머를 설정해준다. 이어서 나오는 withTimeInerval: 1
은 1초마다 실행된다는 것을 의미해주고, repeats: true
는 타이머가 반복해서 실행되도록 설정해주는 것인데 만약 false
로 설정했다면 한번만 실행되고 끝날 것이다
repeats - 반복, scheduledTimer - 스케줄 타이머
{ [weak self] _ in ... }
이건 처음 써봤다.
타이머가 호출될 때마다 실행되는 것인데, self
를 약하게 참조(weak) 하도록 설정했다는 것이란다.
알아보니 weak self
를 사용하면, 타이머가 실행되더라도 self
의 강한 참조를 유지하지 않기 때문에, 메모리 누수를 방지할 수 있다. 만약 self
가 해제되면 타이머 클로저 내에서 self
를 사용하지 않도록 한다. 때문에 안전하게 guard let self = self else { return }
를 사용하여 self
가 nil
인지 확인을 해줄 수 있다고 한다.
자기 참조를 약하게 한다는 것은 메모리 관리와 관련된 개념으로 순환 참조(circular reference)
를 방지하기 위해 객체를 강하게 참조하지 않도록 설정하는 것을 의미한다다고 한다.
만약 A라는 사람이 B라는 친구를 "강하게" 소유하고 있다고 한다고 했을 때, B는 A의 소유물이라고 하자.
B가 A에게 의존하는 한 B는 항상 존재할 것이다.
하지만 A가 B를 "약하게" 소유한다면, B는 A와 독립적인 존재가 될 수 있다는 것이다. 그 말은, B가 필요 없게 되면 A는 그냥 자유롭게 사라질 수 있다는 것.
A는 B를 계속 필요로 하지 않을 수도 있으므로, B는 독립적으로도 존재할 수 있는 것이다.
참조 종류들을 간단하게 적어봤다.
- 강한 참조 (Strong Reference)
- 기본적으로 객체를 생성하면 그 객체는 강한 참조를 가진다. 이는 해당 객체가 메모리에 유지되도록 보장하는 것이다. 예를 들어, 객체 A가 객체 B를 강하게 참조하면, 객체 B는 객체 A가 존재하는 한 메모리에 계속 남아있게 된다.
- 약한 참조 (Weak Reference)
- 약한 참조는 객체가 메모리에 존재하는 것을 보장하지 않는다. 즉, 약한 참조를 가진 객체가 메모리에서 해제될 수 있다. 객체 A가 객체 B를 약하게 참조하면, 객체 A가 메모리에서 해제될 때 객체 B의 메모리는 영향을 받지 않는다. weak 키워드를 사용하여 선언하며, 이는 ARC(Automatic Reference Counting)에서 순환 참조를 방지하는 데 도움이 된다.
- 순환 참조 (Circular Reference)
- 두 개 이상의 객체가 서로를 강하게 참조하는 경우, 그 객체들은 서로 해제되지 않고 계속 메모리에 남아있게 된다. 이를 순환 참조라고 한다. 예를 들어, 객체 A가 객체 B를 강하게 참조하고, 동시에 객체 B가 객체 A를 강하게 참조한다면, 둘 다 메모리에서 해제될 수 없다.
채찍피티 감사합니다
다시 돌아오자면,
if self.isCoolingOn { ... } else if self.isHeatingOn { ... }
이 부분은 현재 냉방 또는 난방 시스템이 켜져 있는지 확인하고, 각 상태에 따라 온도를 변경하는 코드다.
냉방이 켜져 있으면 (self.isCoolingOn == true) -> self.temperature -= 1
로 온도가 1도씩 내려갈 것이고, 난방이 켜져 있으면 (self.isHeatingOn == true) -> self.temperature += 1
로 온도가 1도씩 올라갈 것이다.
아 그리고 사진에서 오류 난 것처럼 피를 흘리는 이유는 이 오류는 비동기 작업에서 self
와 같은 참조 타입이 스레드 안전하지 않기 때문에 발생하는 오류라고 하는데.. 내용이 어려웠다.
실행은 잘 돼서 크게 신경쓰진 않았다.
그리고 이어서 아래에 현재 온도를 체크하는 코드를 작성했는데, checkTemperature()
로 클래스 내에서 호출되어 현재 온도를 체크하는 역할을 해주고 if temperature == properTemperature
로 현재 온도
가 설정된 적정 온도
와 같으면 조건이 참이 되어 print 코드가 실행되고,
isCoolingOn
과 isHeatingOn
변수를 false
로 설정해 놓았으니 에어컨과 난방 시스템이 꺼지도록 해준다.
timer?.invalidate()
는 timer
가 nil
이 아닐 경우 타이머를 정지시켜주는데, invalidate()
는 타이머의 동작을 중지시키고 더 이상 호출되지 않도록 해준다.
TemperatureController
클래스의 새로운 인스턴스를 생성하고,livingRoom
이라는 이름으로 저장한다.
roomName:
"거실"이라는 문자가 전달되어 방의 이름을 설정.
temperature:
15라는 수가 전달되어 초기 온도 설정.
생성자가 호출되면 해당 방의 이름과 온도가 설정되고 초기 온도에 대한 메시지가 출력된다.
setTargetTemperature(24)
메서드를 호출하여 적정 온도를 24로 설정한다라는 것.
properTemperature
가 24
로 설정되면서 "적정 온도를 24도로 설정했습니다."
라는 메시지가 출력된다.
그 후, startCoolingOrHeating()
이 호출되어 현재 온도와 적정 온도를 비교하고 이때 현재 온도가 15로 적정 온도인 24보다 낮기 때문에 난방 시스템이 켜지고 온도를 높이는 동작이 수행된다.
냉난방 켜는 함수도 print 부분을 didSet으로 변수에 넣고 함수 넣는 자리에 함수 호출 없이 변수 변경만 해줘도 되겟네요!