Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

쥬스 자판기 [STEP 1] Andy, Effie #71

Merged
merged 16 commits into from
Dec 10, 2023

Conversation

hyeffie
Copy link

@hyeffie hyeffie commented Dec 6, 2023

@ictechgy

안녕하세요, 코든! 쥬스 자판기 프로젝트 진행하게 된 에피, 앤디 @nation81 라고 합니다. 첫 리뷰 잘 부탁드립니다.

이번 STEP에서는 프로젝트 시작 전에 함께 결정한 코드 컨벤션 및 프로젝트 룰을 준수하고, 주어진 요구사항에 충실해 객체들의 역할을 직관적으로 전달할 수 있도록 코드를 작성하는 것을 주안으로 두고 작업했습니다.

저희 팀의 코드 컨벤션이나 프로젝트 룰은 다음에 참조해두겠습니다 😃 wiki - 프로젝트 룰

설계 설명

Fruit

과일의 종류를 나타내는 타입입니다.

FruitStock

과일의 종류별 재고를 나타내는 타입입니다.

  • 전달된 개수만큼 과일 재고를 차감하는 기능을 포함하고 있습니다.

FruitStore

모든 과일의 저장소 타입입니다.

  • 과일별 과일 재고를 가지고 있습니다. 과일 키를 통해 재고에 접근할 수 있습니다.
  • 전달된 과일 종류와 수량만큼의 과일을 재고에서 차감하는 기능을 포함하고 있습니다.

JuiceFlavor

주스의 종류를 나타내는 타입입니다.

  • 주스 종류별로 주스를 만들 때 필요한 과일별 수량을 나타내는 레시피 정보를 가지고 있습니다.

JuiceMaker

과일 저장소에서 과일을 차감해 주스를 만드는 타입입니다.

  • 전달된 주스 종류에 따라 레시피를 참고해 저장소에서 과일 차감하는 주스 만들기 기능을 포함하고 있습니다.

질문

FruitStock 타입 final class / struct 중에 어느 게 어울릴까?

현재 구현에는 FruitStock이 final class로 구현이 되어 있습니다.

기본 템플릿으로 제공되었던 FruitStore가 class로 제공되었고, reference semantics를 사용해야 하는 상황이 있어서 의도적으로 제공되었을 것이라고 생각했습니다.

final class로 구현했던 이유는 그런 가정 하에, 하위 타입이 일관성 있게 구현되면 좋을 것이라고 생각했기 때문인데요.

그런데 굳이 상속하지도 않고, 반드시 참조 타입으로 사용할 이유가 없다면 구조체 타입을 선택해도 문제가 없을 것이라는 생각이 들더라고요.

혹시 코든은 이런 선택에 대해 어떻게 생각하시는지 궁금합니다.

FruitStore의 fruitStocks 프로퍼티의 딕셔너리 구현

FruitStock은 과일 종류와 해당 과일 재고를 표현하는 타입이고, FruitStore가 collection으로 소유하게 됩니다.

처음 FruitStock을 구현할 때는 collection 내에서 과일 종류 별로 중복되는 상황을 타입으로 방지하고자 Hashable 구현해서 FruitStore에서 Set으로 소유하도록 구현했었습니다.

extension FruitStock: Hashable {
    static func == (lhs: FruitStock, rhs: FruitStock) -> Bool {
        lhs.fruitType == rhs.fruitType
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(self.fruitType)
    }
}

class FruitStore {
    private var fruitStocks: Set<FruitStock>
		...
}

그런데 FruitStock의 hash 값을 fruitType만으로 결정하는 것이 어색하고 합리적이지 않다고 느껴졌고, 그래서 key로 Fruit을, value로 FruitStock 인스턴스를 갖는 딕셔너리로 구현 변경했습니다.

이렇게 구현하면 집합 안에서 특정 과일 타입으로 stock을 찾을 필요 없이,딕셔너리에 과일 타입으로 접근할 수 있어서 코드가 조금 더 직관적이어 진다고 생각했습니다.

그런데 현재 구현의 경우 딕셔너리에서 전달된 fruit 타입의 stock이 없다면 어떻게 처리해야 하는지 고민이 되기도 하고(현재는 그냥 리턴하고 있습니다), 유일성을 보장하기에 이전 구현이나, [Fruit: Int] 형태의 구현이 더 나은지 고민이 됩니다.

코든은 이 문제에 대해서 어떻게 생각하지는지 궁금합니다!

andyanappdev and others added 12 commits December 4, 2023 13:08
- EachFruitStore 구조체 구현
- EachFruitStore Hashable 구현
- JuiceMakerError 선언 및 과일 부족 케이스 추가
- FruitStore 구현 추가
- Fruits CaseIterable 구현 추가
- FruitStore 과일 가져오기 기능 추가
- EachFruitStore struct > class 로 변경
- EachFruitStore Equatable 구현 추가
- 주스 만들기 기능 추가
- FruitStore eachFruits 속성 타입 Set<EachFruitStore> > Dictionary<Fruits, EachFruitStore>
- EachFruitStore Hashable 구현 제거
@hyeffie hyeffie changed the title D andy 쥬스 자판기 [STEP 1] 앤디, 에피 Dec 6, 2023
@hyeffie hyeffie changed the title 쥬스 자판기 [STEP 1] 앤디, 에피 쥬스 자판기 [STEP 1] Andy, Effie Dec 6, 2023
Copy link

@ictechgy ictechgy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 @hyeffie , @nation81 ! 두 분의 쥬스메이커 프로젝트를 맡게 된 코든이라고 합니다. 잘부탁드려요~~!! 😆

STEP1 진행하느라 고생하셨습니다! 코드컨벤션 및 프로젝트 룰에 대해 wiki 정리까지 해두시다니 정말 좋네요! 👍👍👍
이렇게 문서화를 해주시니 코드를 더 이해하기 쉬운 것 같아요!

아래에는 질문주신 사항들에 대해 답변 드려볼게요~


1. FruitStock 타입을 final class / struct 중 어떤 것으로 할지

class를 선택하신 것에 대해 나름의 이유가 있으셨군요! final까지 붙여주시다니 좋습니다! 코드에는 정답이 없고 이유가 있다면 다 옳은 코드이겠지만 이 부분에 대해 제 의견을 말씀드려볼게요!

  • 참조 형태로 만들면 '이 친구가 이 곳 저 곳에서 참조되어야 하는 경우가 있는건가?'하고 생각이 들기 때문에 저는 개인적으로 struct를 우선 고려하는 편입니다. (물론 value semantic이 가지는 성능상 이점들도 있겠죠)
  • struct로 만든 뒤에 무언가 참조가 되는 것이 더 나은 상황들이 발생한다면 그 때 class로 변경합니다. (하나의 인스턴스에 대해 call by reference적인 형태가 필요한 경우)
  • 어떤걸 선택해야 할지 조금 더 명확한 기준을 원하신다면 아래 문서/영상을 참고해보시면 좋을 것 같습니다. (지금 전부를 다 보실 필요는 없습니다)

2. FruitStore의 fruitStocks 프로퍼티의 딕셔너리 구현

여러 Collection 타입들을 고려해보셨군요 😊 멋있네요. 👍 Key에 다른 값이 들어가지 않도록 enum을 활용하신점도 좋습니다.

  • 우선 저는 현재 형태도 좋아보입니다. 기존 Set 형태의 경우에는 원하는 과일 및 재고를 찾는 추가 로직이 들어가게 되고 [Fruit: Int] 같은 경우에는 너무 단순화된 형태로 보였거든요. (Int타입명을 typealias 이용하여 대체해볼 수도 있었겠지만요)
  • 재고가 없는 경우에 대해 error throw 처리를 해주신 점도 직관적으로 느껴졌어요. 말 그대로 문제가 되는 상황이니까요.
  • 다만 FruitStock에는 어떤 과일인지에 대해 Fruit 프로퍼티를 가지고 있는데요. 이 값이 실질적으로는 안쓰이고 있어요. 이 부분은 어떻게 해야할지 천천히 고민해보면 좋을 것 같아요~

수정해주시면 좋을거 같은 부분

큰 문제는 없지만, 변수와 함수 네이밍만 조금 더 고민해봐주시면 좋을 것 같아요! 물론 지금도 너무 잘해주셨습니다..!!😊
(특히 타입별로 역할이 잘 나뉘어있어 보기
편했습니다)

궁금한점

  1. 이건 개인적으로 궁금한 내용인데요. 커밋 이력을 보니 에피가 계속 author이고 앤디가 committer이던데 어떤 형식으로 커밋을 진행하신건가요?? 🤔

.gitignore Show resolved Hide resolved
JuiceMaker/JuiceMaker/Model/Fruit.swift Show resolved Hide resolved
// Created by Effie on 12/4/23.
//

final class FruitStock {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 final을 썼을 때의 성능적 이점도 알고 계실까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

상속이 가능한 class의 경우 하위 클래스가 상위 클래스의 구현을 오버라이딩 할 수 있는데요. 상속 관계에 있는 타입들은 상위 클래스 타입으로 업캐스팅 되어 사용될 수 있고, 이 과정에서 구현을 사용하면 인스턴스의 실제 구현을 look up 하는 과정이 필요해요. (dynamic dispatch)

final 키워드를 사용하면 클래스의 상속을 제한하고, 구현의 오버라이딩을 막음으로써 dispatch 비용을 줄일 수 있는 이점이 있다고 알고 있어요.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

일단 상속을 더이상 하지 않는 것이 명확한경우 final로 선언하는 것이 유리하다고 알고 있습니다.

default class의 경우 Table Dispatch로 작동 (Dynamic Dispatch)

  • 함수의 포인터(메모리 주소)를 배열형태 보관후 실행 (메모리 주소를 배열형태로 보관 한다는 의미에 Table Dispatch 라고도 부르기도함)
  • 런타임에 결정
  • 클래스와 프로토콜에서 사용
    • 클래스 테이블이 Virture Table
    • 프로토콜 테이블 Witness Table

fianl class의 경우 Direct Dispatch 작동

  • 코드 자체에 함수의 메모리 주소를 삽입 또는 함수의 명령 코드를 해당 위치에 코드를 심는다 (In -line)
  • 컴파일 시점에 결정
  • Value Type (구조체, 열거형)에 사용
  • Reference Type (class)에 final 키워드를 사용하여 Direct Dispatch로 메서드가 작동하도록 만들수 있다.
  • 장점 : 가장 빠른 속도
  • 단점 : 다형성, 상속의 장점 적용 불가

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

두 분 다 너무나 좋은 답변 주셨네요 👍👍👍😆
혹시 WWDC - Understanding Swift Performance를 두 분 다 보신걸까요,,?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수요일에 학습활동으로 WWDC16 해당 세션의 내용을 가지고 했습니다.
대략적인 내용은 파악하고 있으나 주말에 한글자막을 활용해서 좀더 집중해서 다시 볼 예정입니다.

final class FruitStock {
let fruitType: Fruit

private var count: Int = 10
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

접근 제어자 설정해주신 점 좋습니다. 😊

타입에 대해 추론과 어노테이션을 같이 써주셨는데 의도하신걸까요??

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이전까지는 추론이 가능한 컨텍스트에서는 따로 명시를 안 해줬었는데, 타입을 명시하는 게 컴파일링 속도에 영향을 준다는 얘기를 듣고 쓰려고 하고 있어요!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

크.. 컴파일 속도까지 챙기시다니! 👍

JuiceMaker/JuiceMaker/Model/FruitStock.swift Show resolved Hide resolved
JuiceMaker/JuiceMaker/Model/JuiceMaker.swift Show resolved Hide resolved
JuiceMaker/JuiceMaker/Model/JuiceMaker.swift Show resolved Hide resolved
self.fruitStore = fruitStore
}

func makeJuice(flavor: JuiceFlavor) throws {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make 말고 다른 prefix는 어떨까요?

Swift API Design Guidelines에서 Strive for Fluent Usage 부분 참고 부탁드려요!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요 부분 네이밍 할 때 반환값이 없는 것도 같이 고려해주시면 좋을 것 같아요~

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

공유해주신 문서에 따르면 특정 타입을 리턴하는 팩토리 메서드에 해당되지 않고 있어서 make 말고 다른 표현을 고려하는 게 좋긴 할 것 같더라고요 !

안 그래도 전에 앤디가 이 메소드의 동작을 명확히 하는 표현이 어떠냐고 질문해주셨던 것 같아서, consumeFruitsForJuice 또는 consumeFruitsForMakingJuice 같은 표현을 생각해봤는데 어떻게 생각하시나요?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 둘 다 좋은것 같은데요??
개인적으로는 consumeFruitsForMakingJuice가 더 명확하게 다가오네요!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 좋습니다! 반영해두겠습니다 😃


func makeJuice(flavor: JuiceFlavor) throws {
try flavor.recipe.forEach { (fruit: Fruit, count: Int) in
try fruitStore.getFruits(fruit, count: count)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

만약, 딸기바나나 쥬스를 만드는데 딸기의 재고는 충분하고, 바나나의 재고는 충분치 않다면 어떻게 동작하게 될까요? 🤔 한번 고민해보시면 좋을 거 같아요!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요구사항에 따라 다르겠지만 지금 구현에서는 로직을 에러로 처리하고 있어서 과일의 재고가 없다면 부족한 해당 과일의 정보에 대해서는 호출 지점에서 알지 못할 거예요.

만약 화면에 어떤 과일이 부족한지 등에 대한 로직이 필요하다면 과일 정보를 에러의 연관값으로 함께 던지도록하고, 하위 로직을 호출하는 쪽에서 정보를 사용하는 동시에 처리 로직을 좀 더 바꿀 수도 있을 거예요.

이 부분도 다음 스텝에서 풍부하게 고민해보도록 하겠습니다!

Copy link

@ictechgy ictechgy Dec 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋습니다! ㅎㅎ

다만 제가 여쭤봤던 것은 다음의 상황이 벌어지지 않을까 해서 였습니다.

  1. 딸기의 재고는 충분하나 바나나의 재고는 충분치 않은 상황
  2. 딸기 바나나주스 주문이 들어온다.
  3. 딸기에 대한 consume만 처리되고 바나나에 대한 consume은 에러가 throw된다.
  4. 딸기바나나주스는 만들어지지 못했지만 딸기 재고는 감소하였다.

한번 고민해봐주시면 좋을 것 같아요! 바꾸신 로직은 따로 또 챙겨볼게요~!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 그럴 수 있겠네요...! 그렇다면 오히려 stock 타입을 구조체로 고려해야 하나 생각이 들기도 하네요.... 다음 스텝 진행하면서 이 부분 반영해보겠습니다 ! 감사합니다 :)

@andyanappdev
Copy link

andyanappdev commented Dec 7, 2023

코든 꼼꼼한 리뷰에 대해 다시 한번더 감사합니다.
Review Comment에 대한 간단한 저의 생각 및 답변을 남기도록 하겠습니다.

1. FruitStock 타입을 final class / struct 중 어떤 것으로 할지

  • 우선 Class와 Struct의 공통점과 차이점에 대해 간단히 이야기 하고자 합니다.

    • 공통점

      class & struct의 공통점은 커스텀 모델 타입으로 인스턴스화 할수 있다는 점이 가장 큰것 같습니다.

    • 차이점

      Class

      • Reference Type
      • 인스턴스 데이터는 Heap에 저장, 해당 Heap을 가르키는 변수는 Stack에 저장
      • 메모리 주소값이 Hepa을 가르킴
      • 복사시 저장된 주소를 복사(전달)
      • ARC 시스템을 통해 해제가 관리 되고 있기 때문에 메모리 관리에 주의 필요 (강한참조 → 순환참조로 인하여 해제가 안되는 경우가 발생할수 있기 때문에)

      Struct

      • Value Type
      • 인스턴스 데이터를 모두 Stack에 저장
      • 복사시 복사본을 생성해서 전달 (다른 메모리공간 생성)
      • Stack의 공간에 저장됨에 따라 Stack프레임 종료시, 메모리에서 자동 해제
  • 재가 Custom Data Model Type을 만들때 고려 하는 저만의 기준을 말씀드리겠습니다.

    • 다른 언어에서는 불변 객체를 사용하기 위해 sealed class로 만들어 불변객채로 사용합니다.
    • Swift는 불변의 Custom Data model에 사용할수 있도록 struct를 제공해 주고 있다고 생각합니다.
    • 그래서 저는 무조건 Data Model은 Struct로 만들고 시작 합니다. 아래의 두 조건이 필요할 경우만 Class를 사용을 고려하게 됩니다.
      • Inheritance
      • Works with objective-C

2. FruitStore의 fruitStocks 프로퍼티의 딕셔너리 구현

  • 에피와 PR 전에 논의한 점이 에피의 설계 및 구현에서는 과일과 그 해당 과일수량과 관계가 FruitStore의 딕셔너리로 들어가기 전까지 설정되지 않는다는 부분 이었습니다.

  • 에피의 아이디어와 저는 조금 다른 접근 방식을 채택 했었습니다. (과거)

  • Fruit Data Model

    struct Fruit {
        let type: FruitType
        var quntity: Int
    }
    
    enum FruitType: String {
        case strawberry, banana, pineapple, kiwi, mango
    }
  • FruitStore (초기 대략적인 틀만 잡은 코드)

    class FruitStore: FruitStoreProtocol {
        private var inventory: [FruitType: Int] = [:]
    
        init() {
            for type in FruitType.allCases {
                inventory[type] = 10
            }
        }
    
        func getFruitInventory() -> [Fruit] {
            return inventory.map { Fruit(type: $0.key, quantity: $0.value) }
        }
    
        func updateFruitQuantity(type: FruitType, quantity: Int) throws {
            guard var currentQuantity = inventory[type] else {
                throw FruitStoreError.unknownFruitType
            }
    
            currentQuantity += quantity
            guard currentQuantity >= 0 else {
                throw FruitStoreError.insufficientFruitQuantity
            }
    
            inventory[type] = currentQuantity
        }
    }
  • FruitStock에는 어떤 과일인지에 대해 Fruit 프로퍼티를 가지고 있는 부분을 어떻게 할지 고민해 보겠습니다.

궁금한점

  • 저희 처음 프로젝트를 시작할때 ‘각자 설계에 대해서 고민해보고 (만약 가능하다면 일부 구현까지 해서) 다음날 서로의 설계에 대해서 이야기하고 가장 적합하고 합리적인 한가지를 채택하여 진행하자 ‘ 라고 결정 하였습니다.
  • 서로의 설계에 대해 논의를 통해 에피의 아이디어를 결정하게 되었습니다.
  • 그러다 보니 에피가 구현까지 어느정도 되어 있어서 에피의 커밋을 그대로 사용하게 되었습니다.

@hyeffie
Copy link
Author

hyeffie commented Dec 7, 2023

코든 안녕하세요! 빠르고 꼼꼼한 리뷰 너무 감사합니다 :)

저희가 질문에 대했던 내용 이전에 코든의 질문에 대한 답변을 먼저 드리자면,

우선 이번 스텝 요구사항에 대해서 각자 파악을 하고 나서 이야기를 해보기로 했는데, 요구사항 자체가 간단한 편이고 모델에 관한 구현이라서 저는 구현을 빨리 끝낸 편이었어요. 그런데 이 프로젝트가 화면에 대한 구현을 포함하고 있다보니까, 각자 요구 사항에 대해서 고민하는 범위가 다르더라고요.

요구사항을 파악하고 서로 생각한 설계를 공유하는 과정에서 이번 스텝에 대한 저의 생각과 구현에 공감해주셨고, 이 구현으로 리뷰를 받고 나서 다음 스텝을 고민해보기로 했어요. 그래서 제가 설계의 일환으로 구현했던 코드를 이번 PR에 사용했던 것 같아요.

tasycode의 저장소 > 앤디 저장소 > 에피 저장소 의 순서로 fork하고 제가 제 저장소에서 작업했던 걸 앤디 저장소로 rebase pull 하고, 이 코드들을 최종 검토 후에 PR을 보내서 author와 committer가 분리되어 있는 것 같습니다.

그리고 이하는 저희 질문에 답변해주신 부분에 대한 내용입니다!

1. FruitStock 타입을 final class / struct 중 어떤 것으로 할지

참조 타입과 값 타입 사이에서의 선택 항상 고민되는 부분인데 😂 공유해주신 자료 공부도 해보고 참고해볼게요.

다음 스텝들을 진행하면서 정말 프로젝트 맥락상 참조가 필요한지 계속 고민해보고, 변경이 필요할 때 반영하는 방향으로 진행해보도록 하겠습니다!

2. FruitStore의 fruitStocks 프로퍼티의 딕셔너리 구현

맞아요. 이 속성을 구현하면서 생각했던 부분들을 그대로 말씀해주신 것 같아요! 저도 각 과일의 재고를 타입으로서 관리하고 싶었고, 그러다보니 이 타입을 사용할 속성의 최적의 구현에 대해서도 고민이 됐던 것 같아요.

말씀해주신 것처럼 외부의 이 타입이 value로 사용되는 딕셔너리에서 과일의 종류를 매치한 상태로 생성하고 사용하다 보니까 stock 내부의 fruitType 프로퍼티는 사용되지 않고 있는데요. 일단 private으로 가려보고 다음 요구사항에 따라서 좀 더 고민해보기로 하겠습니다 :)

리뷰 사항들 반영해 본 후에 다시 연락 드리겠습니다 ㅎㅎ

- 외부에서 안 쓰는 속성 접근 제한
- import Foundation 제거
- FruitStore init 구현 내 상수 이름 변경
- FruitStore getFruits 메소드 이름 변경
- 템플릿 주석 제거
- JuiceMakerError.fruitStockNotFound 추가
- 전달받은 과일의 stock이 없는 경우 에러 던지도록 구현 수정
@hyeffie hyeffie requested a review from ictechgy December 7, 2023 04:56
Copy link

@ictechgy ictechgy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코멘트를 봤는데요. 두 분 다 대단하신 것 같아요!
코드 작성 이유도 뚜렷하고 의견전달도 굉장히 명확하셔요 (저보다 나으신 것 같은데요 😄)

아래는 코멘트 남겨주신 내용들에 대해 제 생각을 말씀드려볼게요.


커밋 방식에 대해 답변해주신 내용

아하~ 그렇군요. 이 부분에 대해 저는 조금 다른 생각인데요!

각자 개발하고 고민해보는 시간도 좋지만 중간중간 코드 하나하나마다 의견을 같이 교류해나가는 것도 좋지 않을까 싶어요. (야곰이 페어프로그래밍을 요구하셨던 것은 나름의 이유가 있으셨겠죠?)
앤디의 코드도 충분히 섞어볼만 하다고 생각이 들어서 말씀드려봐요!


FruitStock에 대해.

FruitStock에 대해 저는 사실 커스텀 Collection을 생각해봤습니다! 그런데 너무 오버엔지니어링이지 않을까 했어요. Collection protocol을 채택하여 커스텀 타입을 만드는 것도 같이 생각해보시면 좋을 것 같네요. (나중에요!)


value / reference semantic에 대해 두 분 다 너무 잘 이해하고 계셔서 여쭤봅니다!

class는 힙에 생성된다고 하셨고 struct는 스택에 생성된다고 하셨는데요!

  • class가 가진 struct 프로퍼티는 메모리 중 어디에 생성될까요?

마지막으로

수정해주신 내용 살펴봤습니다! 전체적으로 다 깔끔하네요 ㅎㅎㅎ. makeJuice에 대한 네이밍만 변경해주신다면 문제없이 머지 진행해볼 수 있을 것 같아요!

고생 많으셨어요!

@andyanappdev
Copy link

1. class가 가진 struct 프로퍼티는 메모리 중 어디에 생성될까요?

  • 구글링 전에 머리속으로 그려 보았을때의 답은

    • class가 가지고 있는 프로퍼티 이기때문에 비록 그것이 struct라고 해도 Heap 영역에 할당 될것이라 생각이 되었습니다.
      • String Type도 struct 이지만 가변적인 값을 가지는 경우 Heap 할당된다고 알고 있습니다.
      • 그렇다면 무조건 struct = Stack 영역에 할당 된다는 공식이 아니고 상황적인 예외 존재 한다.
      • 만약 struct이니까 Stack 영역에 할당 된다고 가정했을때 Stack에는 LIFO 방식으로 push 및 pop이 될텐데… 해당 구조체 프로퍼티가 pop 되면? 뭔가 그러면 class 인스턴스에 접근해서 해당프로퍼티를 사용이 불가능해지는거? 뭔가 상황적으로 어색하고 말이 안되는거 같다는 생각이 들었습니다.
  • 구글링을 통한 정보 수집

    Swift 메모리 할당 - 타입별 메모리 할당 위치 분석 심화 (String타입, struct안에 class, class안에 struct)

  • 결론

    • 위의 검색 내용도 100% 신뢰 하지 못해서 솔직히 모르겠습니다. 코든 좀 자세히 알려주시면 감사하겠습니다.

@hyeffie
Copy link
Author

hyeffie commented Dec 8, 2023

안녕하세요 코든 @ictechgy ! 질문과 답변해주신 부분에 대해 다시 답변 드려요!

커밋 방식에 대해 답변해주신 내용

맞아요 저도 다음 단계에서는 앤디한테 많이 배우게 될 것 같고, 프로젝트로서도 많은 걸 같이 하고 싶어요! 다음 PR에서는 나눈 대화들이 코드에도 반영되는 모습 보여드릴 수 있으면 좋겠네요 😊

FruitStock에 대해.

오… 원하는 collection 이 없어서 문제가 그런 타입을 만드는 것도 방법이겠네요. collection protocol을 사용해 볼 생각은 못했는데 좋은 아이디어 감사합니다! 기회가 있다면 꼭 활용해 볼게요 :)

value / reference semantic에 대해 두 분 다 너무 잘 이해하고 계셔서 여쭤봅니다!

추측으로는 class 인스턴스의 일부이니까 heap 영역이 할당될 것 같아요.

stack은 코드 실행 동안 공간이 동적으로 할당되는 영역이라 참조와 무관하게 이곳을 할당하지 않을 것 같고,

또 클래스 인스턴스는 더 이상 참조되지 않으면 할당이 해제되어야 할텐데 메모리의 다른 영역에 별도에 저장된다면 관리가 어려울 것 같다는 생각이 드네요.

라고 쓰려다가 궁금해서 한 번 확인해봤습니다!

https://effie-ios.notion.site/class-struct-f432beeeb5c6445895e87e51f6235878?pvs=4

마지막까지 꼼꼼한 리뷰 감사합니다! 항상 배워갑니다 !!

Copy link

@ictechgy ictechgy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nation81 , @hyeffie
STEP 1 마무리 고생하셨습니다~! 이 PR은 머지하도록 할게요.

제가 드렸던 질문에 대해 두 분 다 나름의 답변을 주셨네요! 최고!! 👍👍👍
우선 답을 말씀드리자면 class가 소유한 struct 프로퍼티는 Heap영역에 존재한다입니다.


정리

다음의 답변은 제 주관적 지식이 섞여있어 틀린 내용이 있을 수 있습니다. 유의하여 읽어주세요.

Swift도 결국에는 C언어를 기반으로 한 언어입니다. 때문에 Class 인스턴스에 대한 관리는 포인터 형태로 다루게 되는데요.
(힙에 존재하는 인스턴스는 스택 인스턴스가 참조하고 있거나 다른 힙 인스턴스가 참조. 아무도 참조를 하고 있지 않으면 참조카운트가 0이 되어 메모리에서 해제)

  1. withUnsafePointer라는 포인터 변수 A를 만든 뒤 주소를 알길 원하는 인스턴스 B를 가리키게 만든다. (B는 포인티라고 부르기도 합니다. Pointer-Pointee)
  2. 포인터 변수 A의 값은 인스턴스 B의 메모리 주소를 가지게 됩니다. (Int타입이 정수 값을 가지듯 포인터 변수는 특정 변수의 주소 값을 가집니다.)
  3. 포인터 변수 A가 가진 값을 출력하면 인스턴스 B가 메모리 상 어디에 있는지 그 주소를 볼 수 있게 됩니다.
  4. (물론 포인터 변수 A 자체의 주소 상 위치도 찍을 수 있습니다. 다른 포인터변수 A'를 만들어서 A를 가리키도록 만들면 됩니다.)

Andy가 올리신 링크와 Effie가 올리신 글 모두 이러한 특성을 이용하여 메모리 주소를 찾아나가고 있습니다.


아래는 Effie가 작성하신 글을 그대로 실행한 뒤 중간에 breakpoint를 건 사진입니다. (원하는 인스턴스들 모두 생성된 뒤)
중단점이 걸렸을 때 좌측 Variables View에서 원하는 인스턴스 우클릭 ➡️ View Memory Of "{인스턴스 명}" 을 클릭하면 해당 인스턴스의 주소 정보와 내부 메모리 값 정보가 16진수로 보이게 됩니다.

image

위의 스크린샷에서도 알 수 있듯 Class 인스턴스 내부의 Struct 프로퍼티는 Heap 영역에 존재하고 있습니다.
(근데 왜 Heap 영역 주소가 Stack보다 높게 잡히는지는.. 뭔가 옛날에 알아봤던 것 같은데 기억이 잘 안나네요..😅 혹시 발견하시면 저도 좀 알려주세요...!🙏)


이런 부분에 대해서는

withUnsafePointer에 대한 문서와 Unmanaged.passUnretained에 대한 문서 그리고 아래 아티클도 한번 봐보시면 좋을 것 같아요!


제 답변이 도움이 좀 되었으면 좋겠네요.
아무쪼록 STEP 2도 파이팅입니다!

@@ -11,7 +11,7 @@ struct JuiceMaker {
self.fruitStore = fruitStore
}

func makeJuice(flavor: JuiceFlavor) throws {
func consumeFruitsForMakingJuice(flavor: JuiceFlavor) throws {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍 !!!

@ictechgy ictechgy merged commit 4ed4e93 into tasty-code:d_Andy Dec 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants