수행 내용

  • 쥬스메이커 프로젝트 Step 1 리뷰어 피드백 관련 학습 및 코드 리팩터링
  • JSON 디코딩 시 사용자 정의 타입으로 받을 수 있도록 이니셜라이저 작성
  • 타입별 수행 기능 정리
  • Swift 기초 문법 복습

학습 내용

Swift 기초 문법 복습

프로젝트를 성실히 수행하는 것도 중요하지만, 향후에 나의 실력을 지탱해주는 것은 기초라고 생각하고 있다. 그래서 오늘도 문법 복습을 했다.

오늘 공부한 대부분의 내용은 고민한 점 / 문제점과 해결 방법 섹션에 기술한다.

고민한 점 / 문제점

구조체, 클래스, 무엇을 써야 하는가?

구조체와 클래스로 동일한 기능을 구현할 수 있다는 것을 알게된 순간부터 어떤 경우에 무엇을 사용하여야 할지 고민하고 있다. 구조체는 익스텐션과 초기구현된 프로토콜로, 클래스는 여기에 상속을 통한 수직적 기능확장과 기존 기능의 오버라이딩이 가능해서 기능 확장 측면으로 보아도 두 선택지 모두 흠이 없다. 구조체가 값 타입이라고 내부 프로퍼티를 수정할 수 없는 것도 아니다. mutating 키워드를 메서드에 추가하면 저장 프로퍼티의 변경이 가능하기 때문이다. 몇 가지의 차이라고 하면 구조체는 값 타입, 클래스는 참조 타입인 점과 상속과 오버라이딩을 통한 재정의가 가능한지가 있어보이는데.. 과연 상황별로 어떤 것을 채택해야 좋을까? 오늘 값진 참고자료를 받았다.
[링크: 구조체와 클래스 중 선택하기 - Apple Developer]

Source - Apple Developer
위 내용은 링크에 있는 내용을 일부 가져온 것이다. 애플에서는 구조체와 클래스를 선택할 때 아래 네 가지를 고려해보라고 한다.

  • 기본적으로 구조체를 사용할 것
  • Objective-C와의 상호운용성이 필요한 경우 클래스를 사용할 것
  • 모델링하고 있는 데이터의 정체성(identity)을 제어해야 할 경우 클래스를 사용할 것
  • 구현부를 공유할 경우 구조체에 프로토콜을 채택하여 사용할 것

지금 수준에서 이해하기로는 내부 프로퍼티 변경과 같은 작업을 수행하면 클래스로 설계를 하는 것이 맞다고 생각한다. 구조체에서 프로퍼티 변경을 지원하기 위해 일일히 mutating 키워드를 채택한 메서드를 만들었는데, 이건 허용하지 않는 것을 키워드를 통해 강제로 해제하는 느낌이 있었다. 이제부터는 프로퍼티 변경의 여지가 있는 타입은 클래스로 만들어야지.
추가로 mutating 키워드로 프로퍼티를 변경하면 어떤 일이 발생하는지 알아보고 싶다. 클래스에서 단순히 값을 바꾸는 것과 어떤 점이 다를까? 오늘 잠시 공부한 바로는 구조체에서 mutating 키워드를 채택한 메서드를 통해 프로퍼티를 변경하면 프로퍼티 변경을 위해 완전히 다른 인스턴스를 생성한다는 듯 한데, 더 알아봐야겠다. 값 타입과 참조 타입이니 메모리 활용 관점에서도 다른 부분이 있을 것 같은데.. ARC 같이 단어만 들어본 잡지식 같은게 들어있으니 초보인데도 지향점이 자꾸 이리저리 한다. 어디 좋은 자료 없을까..

  • 값을 참조 방식으로 접근한다는게 무슨 뜻일까?

해결 방법

사용자 정의 타입으로 디코딩하기 (feat. JSON)

쥬스메이커 프로젝트 중 JSON을 디코딩하는 과정에서 저장 프로퍼티의 타입을 사용자 정의 타입으로 받으면 돌아갈 필요 없이 코드의 흐름을 논리적이게 구성할 수 있다고 판단했다 (3.11 TIL). 하지만 Decodable 프로토콜을 타입에 적용하여도 파싱이 제대로 되지 않는 문제가 있었는데, 리뷰어인 Lin이 init(from decoder: Decoder)라는 힌트를 주어 장시간의 사투 끝에 작은 결과를 얻을 수 있었다. 디코딩 과정에서 JSON 내 정의된 이름과 받고자 하는 이름이 다를 경우 활용할 수 있는 CodingKey 프로토콜과 init(from decoder: Decoder) 이니셜라이저를 통해 받고자하는 프로퍼티의 타입을 정의해주었더니 해결이 가능했다.

struct Ingredient: Decodable {
  var fruitName: Fruit?
  var quantity: Int?
  
  private enum CodingKeys: String, CodingKey {
    case fruitName
    case quantity
  }
  
  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.fruitName = try? container.decode(Fruit.self, forKey: .fruitName)
    self.quantity = try? container.decode(Int.self, forKey: .quantity)
  }
}

try문의 실패 가능성이 있기에 디코딩된 내용을 받는 프로퍼티를 옵셔널 타입으로 지정해주었다. 디코딩 문제는 해결했지만 다른 문제가 등장했다..!

바로 위와 같이 출력할 때 문구가 이상하게 출력되는 단점이 있다는 것인데, 과연 intValue: nil 구문이 뜻하는 의미는 무엇일까? 분명 fruitName 프로퍼티는 Int값을 가지지 않는 단순한 이름일 뿐인데 Fruit(stringValue: "Pineapple", intValue: nil)과 같이 출력된다. 당장은 CustomStringConvertible 프로토콜을 통해 출력되는 문구를 예쁘게 만들어 두었는데, 이 부분은 추가로 더 고민해봐야겠다.

profile
합리적인 해법 찾기를 좋아합니다.

0개의 댓글