[Apple] Struct / Class 선택기준

J.Noma·2021년 10월 18일
0

Swift : 중요한 주제

목록 보기
1/5

***Reference

결론

일단 결론은 안전성 측면에서 "가능한 한 struct를 사용하라"입니다.

값타입을 쓰면 다른 code section에서는 인스턴스에 변화를 줄 수 없기에 code의 한 부분에만 집중할 수 있습니다

그럼에도 class를 써야 하는 경우는

  1. Objective-C에 class로 정의된 타입을 써야할 때
  2. "DB 연결"처럼 app 전체 코드에서 상황을 공유해야 할 때

그리고, 상속이 필요한 경우는 protocol+struct 조합으로 구현할 수 있습니다.

protocol은 class와 달리, struct/class/enum 모두와 상속 관계를 지을 수 있어 선호됩니다


Overview

struct와 class 모두 Data를 저장하고 behavior를 설계하는데 좋은 선택들입니다
하지만 그 둘이 유사하기 때문에 가끔 어느 것을 선택할지 어렵습니다

어느 선택이 타당한지 판단하기 위해 아래의 4가지 기준을 제안합니다

  1. 기본적으로 struct를 사용합니다
  2. Objective-C 코드와 연계가 필요하면 class를 사용합니다
  3. Data의 identity를 다뤄야 한다면 class를 사용합니다 (참조방식)
  4. 상속이 필요하면 struct + protocol 조합을 사용합니다

각 기준들의 이유에 대해 알아보겠습니다


1. 기본적으로 struct를 사용합니다

일반적인 종류의 Data를 보여줄 때 struct를 사용하면 됩니다. Swift에서 struct는 다른 언어에서 class에게만 허용하는 여러 feature들을 사용할 수 있습니다.
struct는 stored 프로퍼티, computed 프로퍼티, 메소드를 포함합니다
게다가 Swift의 struct는 protocol을 채택하여 default implementation을 지정할 수도 있습니다.
Swift 표준 라이브러리와 Foundation 프레임워크는 당신이 빈번히 사용할 요소들을 struct로만들어 놓았습니다. numbers, strings, arrays, dictionaries 등

struct를 사용하면, app의 전체 상태를 고려할 필요없이 코드의 일부에 대해 더 쉽게 추론할 수 있습니다.
왜냐하면 struct는 값 타입이기 때문입니다. struct의 지역적인 변화는 의도적으로 알리지 않는 한 app의 다른 부분에서는 보이지 않습니다.(신경쓸 필요가 없죠)
결과적으로, 다른 code section에서는 인스턴스에 변화를 줄 수 없기에 code의 한 부분에만 집중할 수 있습니다.

참조 타입인 class를 사용하는데 만약 시스템이 크고 복잡하다면, 서로를 뒤죽박죽 참조해댈 수도 있고 아무곳에서나 인스턴스를 변경할 수 있기에 코드 전체를 신경써야 합니다

2. Objective-C 코드와 연계가 필요하면 class를 사용합니다

만약 data를 처리하기 위해 Objective-C API를 사용해야 한다거나, Objective-C Framework에 "class로 정의된" 타입으로 data modeling을 해야 한다면, class와 class 상속을 쓰게될 것 입니다.
예로, 많은 Objective-C Framework들이 자신의 subclass(?)들을 노출시켜줍니다

3. Data의 identity를 다뤄야 한다면 class를 사용합니다 (참조방식)

Swift의 class는 참조타입이므로 identity를 자동탑재(?)하여 옵니다.
이는 두 개의 서로 다른 class instance에서 stored 프로퍼티의 값이 같더라도, 여전히 다를 identity operator(===)에 의해 혹시 다른가?하고 의심받습니다

이는 또한, app간 class instance를 공유할 때, 당신이 이 instance를 변경하면 이를 참조하는 코드의 다른 모든 부분에서도 이 변화가 보임을 뜻합니다
instance가 이런 종류의 identity를 가지길 원한다면 class를 사용합니다

예로는 file handles, network connections, shared hardware intermediaries가 있습니다

예로, 만약 당신이 local DB 연결을 나타내는 타입을 가지고 있다면, DB접근을 관리하는 코드는 DB의 상태를 완전히 제어해야 합니다.
이 경우 class를 사용하는게 적절합니다. 하지만, app의 어느 부분에서 shared DB에 접근을 허용할지를 제한해야 합니다.

Important
identity를 다루는 것은 조심해야 합니다. class instance를 app 동네방네 공유하는 것은 logic error를 유발하기 쉽습니다.
이렇게 heavily 공유된 instance는 변경 결과를 예측하지 못할지도 모릅니다. 그러므로 이런 코드는 정확하게 작성해야 합니다

identity를 가지더라도 제어할 필요가 없으면 struct를 사용합니다

modeling 중인 data가 entity에 대한 정보이며, 이 entity에 identity가 들어있지만 control이 필요없다면 struct를 사용합니다
Entity: 저장되고, 관리되어야 하는 데이터의 집합 - Zedd

예로, remote DB를 참조하는 app에서 instance의 identity는 외부 entity에 의해 완전히 소유되거나 identifier에 의해 전달될 수도 있습니다
(identity에 대해 app은 결정권이 없고 외부가 정해주는대로 그저 사용할 뿐이라는 의미입니다)

app의 model들의 일관성이 외부 서버에 저장되어 있다면, 기록들을 identity가 포함된 struct로 modeling할 수 있습니다
아래 예시에서 서버로부터 받은 jsonResponse에는 인코딩된 PenPalRecord가 포함되어 있습니다

struct PenPalRecord {
    let myID: Int
    var myNickname: String
    var recommendedPenPalID: Int
}

var myRecord = try JSONDecoder().decode(PenPalRecord.self, from: jsonResponse)

이렇게 PenPalRecord 같은 타입을 modeling하기 위해 local 변경하는 것은 유용합니다
예로, app은 사용자의 피드백에 따라 multiple different penpals을 추천할지도 모릅니다
왜냐하면, PenPalRecord struct는 원본 DB의 identity를 control하지 않기 때문입니다
local로 선언된 PenPalRecord를 아무리 변경하더라도 DB에 영향을 주지 않습니다

만약 app의 다른 부분에서 myNickname을 변경하고 서버에 submit해버린다면, 아마 nickname이 바뀌었으므로 서버로 reject을 당할 것입니다. 하지만 그렇다하더라도 identity가 잘못 선택된 채로 요청하는 상황은 발생하지 않을 것입니다. 왜냐하면, identity(myID)는 constant로 정의되어 local 변경이 불가하기 때문입니다
결과적으로, 실수로 다른 record를 바꿔버리는 DB 요청은 없을 것입니다

4. 상속이 필요하면 struct + protocol 조합을 사용합니다

struct와 class 모두 상속의 한 형태를 지원합니다 (응?)
struct와 protocol은 protocol 채택 행위만 할 수 있고 class를 상속받을 순 없습니다
(protocol 상속을 protocol이 protocol을 채택한다고 표현한 듯 합니다)
하지만, class가 하는 것처럼 protocol 상속 + struct 조합으로 상속계층을 만들 수 있습니다

만약 당신이 상속 관계를 처음(scratch)부터 만들고 있다면, protocol 상속을 선호하라
protocol은 class, struct, enum 모두가 상속에 참여할 수 있도록 허용합니다
반면, class 상속은 class끼리만 가능합니다

data를 어떻게 modeling할지 고르고 있다면, 우선 protocol 상속을 쓰고 struct가 채택하도록 해보세요

profile
노션으로 이사갑니다 https://tungsten-run-778.notion.site/Study-Archive-98e51c3793684d428070695d5722d1fe

0개의 댓글