1) 프로그래머스 - 치킨 쿠폰
2) 프로그래머스 - 리스트 자르기
3) 프로그래머스 - 배열 조각하기
코드 내 등장하는 여러 게임 메세지들을 하나의 열거형으로 분리하고 있다.
대부분은 단순히 문자열을 원시값으로 정의하면 됐었는데, 힌트를 주는 부분에서 난관에 부딪혔다.
// 힌트에 따른 분기 처리
if hint.strike == 3 {
isCorrect = true
print("🎉 정답입니다!\n")
} else if hint.strike == 0 && hint.ball == 0 {
print("❌ Nothing\n")
} else {
print("🎯 \(hint.strike) 스트라이크 ⚾️ \(hint.ball) 볼 입니다!\n")
}
기존의 힌트는 (strike: Int, ball: Int) 타입의 hint 변수에 접근하여 문자열을 출력하고 있었다.
하지만 게임 메세지는 문자열을 원시값으로 채택한 열거형으로 구현중이었다.
enum SystemMessage: String {
case welcome = """
환영합니다! 원하시는 번호를 입력해주세요. (예: 1)
1. 게임 시작하기 2. 게임 기록 보기 3. 종료하기
"""
case gameStart = "\n❮ 게임을 시작합니다. ❯"
...
}
하지만 이 경우, strike와 ball에 따른 메세지를 출력하지 못한다.
케이스 별로 다른 연관값을 주어 해결하려 했지만, 그 경우 외부에서 열거형 객체를 생성해주어야한다는 단점이 있었다.
enum SystemMessage {
case welcome (String)
case hint (strike: Int, ball: Int)
}
// 위 처럼 정의할 경우 따로 객체를 생성해야함
let message = SystemMessage.welcome("환영합니다!")
이 방법은 번거롭다고 생각했다.
그래서 별도의 Hint 열거형을 생성하여 해결해보려 했다.
enum Hint {
case hint(strike: Int, ball: Int)
func toString() -> String {
switch self {
case .hint(let s, let b):
return "\(s) 스트라이크 \(b) 볼 입니다!\n"
}
}
}
이렇게 하면 가능은 한데...
// 힌트에 따른 분기 처리
if hint.strike == 3 {
isCorrect = true
print("🎉 정답입니다!\n")
} else if hint.strike == 0 && hint.ball == 0 {
print("❌ Nothing\n")
} else {
let m = Hint.hint(strike: hint.strike, ball: hint.ball)
print(m.toSring())
}
이 경우에는 그럼 Hint.hint.toString 내에서 분기 처리해서 케이스별 메세지를 내보내는 게 낫지 않나? 라는 고민이 생겼다.
그런데! 그렇게 하면 '정답을 비교한다'라는 기능을 checkAnswer 함수가 아니라 메세지 표시 열거형에서 하지 않나?라는 생각을...
→ 지금 생각해보니 메세지만 처리하는 거니 괜찮지 않을까 싶다.
그리고 메세지를 생성하기 위한 코드가 한 줄이 더 들어간다는 게... 조금 마음에 걸려 방법이 없을지 튜터님께 여쭤보았다.
그리고 알려주신 세 가지 방법!
SystemMessage 열거형을 확장하여 CustomStringConvertible 프로토콜을 채택하는 방법
enum SystemMessage {
case welcome
case hint(strike: Int, ball: Int)
}
extension SystemMessage: CustomStringConvertible {
var description: String {
switch self {
case .welcome:
return "환영합니다!"
case .hint(let s, let b):
return s == 3 ? "🎉 정답입니다! 🎉"
: s == 0 && b == 0 ? "❌ Nothing\n" :
"🎯 \(s) 스트라이크 ⚾️ \(b) 볼 입니다!\n"
}
→ CustomStringConvertible 프로토콜은 description 변수를 정의하여 케이스 별로 문자열 값을 사용가능하도록 한다.
여기서 정의해둔 문자열 description 값은 String Interpolation으로 열거형을 사용할 때 description에 접근할 필요 없이 자동으로 사용된다고 한다!
print("\(SystemMessage.welcome)") // "환영합니다!" 출력
enum 혹은 struct 타입에 타입 변수와 타입 메서드를 활용하는 방법
enum SystemMessage {
static var welcome = "환영합니다!"
static var hint = ""
static func getHint(for strike: Int, _ ball: Int) {
hint = strike == 3 ? "🎉 정답입니다! 🎉"
: strike == 0 && ball == 0 ? "❌ Nothing\n" :
"🎯 \(strike) 스트라이크 ⚾️ \(ball) 볼 입니다!\n"
}
}
→ 열거형을 선언하고 확장해줄 필요 없이 한방에 선언 가능!
💡 보통 열거형으로 사용하는 이유?
위 예시와 같은 용도로 사용할 경우, 따로systemMessage라는 객체를 만들 필요가 없음. 그러니 인스턴스가 만들어질 수 있는 구조체보다 열거형을 사용!
-> 혹시나 인스턴스가 만들어진다거나 하는 걸 방지하는 목적이 아닐지?
weak를 사용하면 HeapObject 내부에 side...Table이 생겨서 메모리를 더 사용
→ side 무슨 테이블은 64byte, unowned를 처리하는 테이블은 8byte라서 용량에 차이가 있음!
하지만! 둘 중 뭘 써야할지 모르겠다면 weak를 쓸 것!
→ unowned는 참조할 녀석이 없을 때 nil값을 반환해주지 않고 런타임 에러를 일으킴
⇒ 메모리 조금 더 잡아먹는게 앱의 안전성을 낮추는 것보다 낫다!!
XCode는 모든 메모리 누수를 잡아주지는 못함
→ Swift의 ARC는 자동으로 메모리를 관리하는 무언가가 아니라, RC를 증가시키고 감소시키는 함수를 코드별로 넣어주는 것뿐임.
⇒ 그래서 순환 참조 같은 경우는 컴파일러가 알아내기 힘듦
→debugSwift를 통해 메모리 누수가 일어나는 지점을 알아내고, XCode의 Memory Graph Debugger를 통해 원인을 알아내고 수정
(실습 진행)