내일배움캠프 앱개발 iOS 트랙 본캠프 3주차
Today I Learned
Stack Overflow
지정한 스택 메모리 사이즈보다 더 많은 스택 메모리를 사용하게 되어 에러가 발생하는 상황
class Main {
func printMainMenu() {
// 메인 메뉴 출력
print("""
--------------------------------------------------------
[ SHAKESHACK MENU ]
1. Burgers | 앵거스 비프 통살을 다져만든 버거
2. Frozen Custard | 매장에서 신선하게 만드는 아이스크림
3. Drinks | 매장에서 직접 만드는 음료
4. Beer | 뉴욕 브루클린 브루어리에서 양조한 맥주
0. 프로그램 종료
""")
if !isCartEmpty {
print("""
[ ORDER MENU ]
5. Order | 장바구니를 확인 후 주문합니다.
6. Cancel | 진행중인 주문을 취소합니다.
""")
}
print("잔액 : W \(round100(wallet))")
print("번호를 입력하세요 -> ", terminator: "")
}
func kiosk() {
print("""
\"SHAKESHACK BURGER 에 오신걸 환영합니다.\"
아래 메뉴판을 보시고 메뉴를 골라 입력해주세요.
""")
while true {
printMainMenu()
while let choice = readInt() {
switch choice {
case 1:
Burgers().detailMenu()
case 2:
FrozenCustard().detailMenu()
case 3:
Drinks().detailMenu()
case 4:
Beer().detailMenu()
case 0:
print("----- 키오스크를 종료합니다 -----")
exit(0)
default:
ERROR_input()
continue
}
}
}
}
}
func printDetailMenu(options: Product) {
print("""
--------------------------------------------------------
========= [ \(options.name) MENU ] =========
""")
for i in 0 ..< options.productName.count {
print("\(i+1). \(options.productName[i]) | \(options.productPrice[i])w | \(options.productInfo[i])")
}
print("""
0. 뒤로가기
""")
print("번호를 입력하세요 -> ", terminator: "")
}
func detailMenu(options: Product) {
printDetailMenu(options: options)
while let choice = readInt() {
switch choice {
case 1 ... options.productName.count :
let index = choice - 1
let data: CartItem = CartItem(name: options.productName[index], price: options.productPrice[index], info: options.productInfo[index])
Cart().checkInputCart(option: data)
case 0:
Main().kiosk()
default:
ERROR_input()
continue
}
break
}
}
}
Main 클래스에서 값을 입력하면 새로운 Menu클래스 인스턴스를 생성해서 메뉴로 보내준다.
메뉴 클래스에서 뒤로가기를 구현하고 싶어서, Main().kiosk() 형식으로 구현을 했더니 자꾸 xcode가 꺼졌다.
결론부터 말하자면...
"뒤로 가기"를 구현하고 싶은데 Main 클래스 인스턴스를 새로 생성해서 호출하는건.. 완전히 오답인 것 같다.
현재 내 코드는 Menu().detailMenu()를 부르고 뒤로가기를 눌렀을 때, Main 클래스 인스턴스를 새로 생성해서 메소드를 사용한다.
이것은 "뒤로 가기"가 아니다.
스택에 쌓이는 메모리를 디버깅으로 확인할 수 있었다.
1을 입력하자 Main 클래스의 kiosk 가 생성되었다
0을 입력해서 "뒤로 가기를" 시도하자 일단 Menu 클래스 인스턴스가 생성되어 메모리에 들어갔고
그 다음 다시 1을 입력해서 메뉴로 진입하자 새로운 Main 클래스 인스턴스가 생성되었다
쌓인다.. 계속
이렇게 새로 생성한 클래스 인스턴스 만들때마다 해당 객체에 대한 메모리가 할당된다.
인스턴스는 실행될 때마다 스택에 하나하나 과정이 쌓이게 되고 그 메모리들은 전부 독립적으로 동작한다.
1번 main 인스턴스 -> 1번 menu 인스턴스
-> 2번 main 인스턴스 -> 2번 menu 인스턴스
-> 3번 main 인스턴스 -> 3번 menu 인스턴스
-> 4번 main 인스턴스 -> 4번 menu 인스턴스
-> . . .
그래서 의도는 "뒤로 가기"였지만 실제로는 새로운 인스턴스를 만들어서 "앞으로 가기" 였던 것이다.
먼저 만들었던 인스턴스와는 완전히 별개의 객체를 만들어서 새로운 데이터를 넣고 넣고 넣고... 이러고 있었다.
이렇게 계속해서 스택에 메모리가 쌓이다가 결국 스택 크기를 초과하면 스택이 터지고 프로그램과 툴이 종료될 수 있다.
이것을 스택 오버플로우 Stack Overflow 라고 부른다.
질문 ) 클래스 자신을 넘겨줄 수는 없을까?
궁금한 게 생겼다. 새로운 인스턴스 객체를 만드는 게 문제라면, 앞에서 사용한 클래스를 프로퍼티로 담아 보내주면 안되나?
간단히 검색해보니 한 키워드를 발견할 수 있었다.
self
현재 인스턴스를 나타내는 키워드라고 한다.
class Teapot
{
var canTalk:Bool
init(canTalk: Bool) {
self.canTalk = canTalk
}
}
let potts = Teapot(canTalk: true)
Teapot 클래스 안에서 canTalk 이라는 변수를 초기화 하는 코드이다.
이 코드 안에서 self는 Teapot 클래스 그 자체이다. Teapot 클래스 인스턴스를 참조하고 있다.
그렇기 때문에 dot 문법으로 변수를 사용할 수 있는 것이다.
변경한 코드
class Main {
func kiosk() {
print("Welcome!")
while true {
print("Main menu options")
if let input = readLine() {
switch input {
case "1":
let menu = Menu()
/*
모든 인스턴스는 self 라는 암시적 프로퍼티를 가진다.
여기의 self 는 Main을 의미한다
인스턴스 메소드 안에서 현재 인스턴스에 접근할 때 사용한다.
*/
menu.showDetailMenu(mainInstance: self, input: input)
case "0":
print("프로그램을 종료합니다")
exit(0)
default:
print("다시 입력해주세요 Main")
}
}
}
}
}
// class Menu
func showDetailMenu(mainInstance: Main, input: String) {
selectOption = input
printMenu()
while true {
if let input = readLine() {
switch Int(input) {
case 1:
print("")
return
case "0":
print("Main 클래스로 돌아갑니다")
mainInstance.kiosk()
default:
print("다시 입력해주세요 Menu")
continue
}
}
}
Menu().showDetailmenu()의 프로퍼티로 Main을 보내주었다.
Main 클래스 안에서는 매개변수로 self를 담아 Main 인스턴스 객체를 전달했고,
Menu 클래스 안에서는 전달받은 mainInstance를 사용해서 kiosk()를 호출했다.
Main 인스턴스 생성
-> (self 키워드로 Main 인스턴스 전달)
-> Menu 인스턴스 생성
-> "뒤로 가기" 시 self로 전달된 Main 인스턴스를 사용해서 Main으로 돌아감
이렇게 한 사이클로 돌아가게 된다고 생각한다...
Main().kiosk()와 mainInstance.kiosk()의 차이점은 인스턴스 생성 횟수에 있다고 본다.
Main().kiosk()
1. 뒤로가기를 할 때 마다 Main()이 새롭게 생성된다.
2. 새롭게 생성되는 인스턴스가 전부 메모리에 쌓인다.
3. 계속계속 쌓인다...
mainInstance.kiosk()
1. Main 클래스에서 1회 생성된 클래스 인스턴스를 self로 Menu 클래스에 전달한다.
2. Menu 클래스에서는 이미 Main에서 생성된 클래스 인스턴스를 사용하는 것이므로
Main 클래스 인스턴스 생성에 관련된 메모리가 쌓이지 않는다.
Main().kiosk()와 mainInstance.kiosk() 둘 다 스택에 쌓이는 건 맞지만 그 횟수에 유의미한 차이가 있어서
mainInstance.kiosk()를 사용할 때 스택 오버플로우 위험이 더 적어진다.
파악한 대로 썼는데 솔직히 이게 맞는지는 모르겠다.
튜터님께 다시 교차확인하러 가야한다...
'TIL' 카테고리의 다른 글
[TIL] 23.12.20 (0) | 2023.12.20 |
---|---|
[TIL] 23.12.19 (0) | 2023.12.19 |
[TIL] 2023.12.05 | 키오스크(2/n) 데이터 모델링 (0) | 2023.12.05 |
[TIL] 2023.12.04 | 키오스크(1/n) 열거형과 구조체의 사용법 차이, CaseIterable (0) | 2023.12.04 |
[TIL] 2023.12.01 | 계산기(4/4) 클래스 추상화, 의존성주입, 의존성역전원칙 (0) | 2023.12.02 |