본 글은 Choosing Between Structures and Classes (애플 공식 문서)를 한국어로 번역하여 옮긴 글입니다.
데이터를 저장하고 동작을 모델링하는 방법을 결정하세요.
구조체와 클래스는 앱에서 데이터를 저장하고 동작을 모델링하는 데 좋은 선택이지만, 이들의 유사성은 선택을 하기 어렵게 만듭니다.
앱에 새로운 데이터 타입을 추가할 때 어떤 옵션이 더 합리적인지 결정하도록 도와주는 아래 권장 사항을 고려해보세요.
기본적으로 구조체를 사용하세요.
Objective-C와 상호 운용이 필요하면 클래스를 사용하세요.
모델링하는 데이터의 참조(identity)를 제어할 필요가 있으면 클래스를 사용하세요.
구현을 공유함으로써 동작을 채택하는 프로토콜과 함께 구조체를 사용하세요.
공통 유형의 데이터를 표시하려면 구조체를 사용하세요. Swift의 구조체는 다른 언어의 클래스에만 제한된 많은 기능을 포함하고 있습니다. 구조체는 저장 프로퍼티(stored property), 계산 프로퍼티(computed property)와 메서드(method)를 포함할 수 있습니다. 더욱이, Swift의 구조체는 프로토콜을 채택하여 기본 구현을 통한 동작을 얻을 수 있습니다. Swift 표준 라이브러리와 파운데이션(foundation) 라이브러리는 숫자, 문자열, 배열과 딕셔너리와 같이 개발자가 자주 사용하는 타입에 구조체를 사용합니다.
구조체를 사용하면 앱의 전체 상태를 고려할 필요없이 코드의 일부분을 이해하는 게 더 쉬워집니다. 클래스와 달리 구조체는 값 타입(value type)이기에, 구조체의 국소적(local) 변경은 의도적으로 해당 변경 사항을 앱의 흐름에 따라 전달하지 않는 한, 앱의 다른 부분에 보이지 않습니다. 결과적으로, 코드의 특정 부분을 살펴볼 때 해당 부분의 인스턴스에 대한 변경이 다른 관련 없는 함수 호출에 의해 보이지 않게 이루어지는 게 아니라, 명시적으로 이루어진다는 점에 더 확신을 가질 수 있게 됩니다.
데이터를 처리하기 위해 Objective-C API를 사용하거나, 데이터 모델을 Objective-C 프레임워크에 정의된 기존 클래스 계층 구조에 집어넣어야 할 필요가 있을 때, 데이터를 모델링하기 위해 클래스 상속을 하거나 클래스를 사용하는 게 필요할 수 있습니다. 예를 들어, 많은 Objective-C 프레임워크는 서브 클래싱(subclass)하기를 원하는 클래스를 가지고 있습니다.
Swift의 클래스는 참조 타입(reference type)이기에 내재된 참조의 개념(notion of identity)를 가지고 있습니다. 이는 서로 다른 두 클래스 인스턴스가 각 저장 프로퍼티에 동일한 값을 가지고 있음에도, 참조 연산자(===)에 의해 여전히 서로 다르다고 여겨진다는 걸 의미합니다. 이는 또한 앱 전역에 클래스 인스턴스를 공유할 때, 인스턴스의 변경은 해당 인스턴스에 대한 참조를 가지는 코드의 모든 부분에서 보여진다는 걸 의미합니다. 인스턴스가 이러한 유형의 참조를 가져야 한다면 클래스를 사용하세요. 일반적인 사용 케이스로 파일 처리, 네크워크 연결과 CBCenteralManager와 같이 공유된 하드웨어 중개자가 있습니다.
🟡 Important
참조를 조심히 다루세요. 앱 전역에 걸친 클래스의 공유는 로직 에러를 발생시킬 가능성을 높입니다. 많이 공유된 인스턴스 변경의 결과는 예측하지 못할 수 있으므로, 해당 코드를 올바르게 작성하는 데 더 많은 노력을 들여야 합니다.
참조를 제어하지 않는 엔터티(entity)에 대한 정보를 포함하는 데이터를 모델링을 할 때 구조체를 사용하세요.
예를 들어, 원격 데이터베이스에 접근하는 앱에서, 인스턴스의 참조는 외부 엔터티에 의해 완전히 소유될 수 있으며 식별자(identifier)에 의해 전달될 수 있습니다. 앱의 모델의 정합성(consistency)이 서버에 저장되면, 식별자와 함께 구조체로 레코드(record)를 모델링할 수 있습니다. 아래 예제에서, jsonResponse는 서버로부터 인코딩된 PenpalRecord 인스턴스를 포함합니다.
struct PenPalRecord {
let myID: Int
var myNickname: String
var recommendedPenPalID: Int
}
var myRecord = try JSONDecoder().decode(PenpalRecord.self, from: jsonResponse)
PenpalRecord와 같은 모델 타입의 국소적 변경은 실용적입니다. 예를 들어, 앱은 사용자에 피드백에 반응하여 서로 다른 여러 펜팔(penpal)을 추천할 수 있습니다. PenpalRecord 구조체는 기본 데이터베이스 레코드의 참조를 제어하지 않기에, 실수로 지역 PenPalRecord 인스턴스를 변경하더라도 데이터베이스의 값이 변경될 위험은 없습니다.
앱의 다른 부분에서 myNickname을 변경하고 다시 서버에 변경 요청을 보내더라도, 가장 최근에 거부된 펜팔 추천이 해당 변경에 의해 실수로 선택되지 않습니다. myID 프로퍼티가 상수로 선언되었기에, 지역적으로 변경하는 건 불가능합니다. 결과적으로, 데이터베이스에 대한 요청은 잘못된 레코드를 실수로 변경하는 일은 발생하지 않습니다.
구조체와 클래스 모두 상속의 형태를 지원합니다. 구조체와 프로토콜은 오직 프로토콜만 채택할 수 있으며, 클래스로부터 상속하는 건 불가능합니다. 그러나, 개발자가 클래스 상속으로 구축할 수 있는 상속 계층 구조의 유형은 프로토콜 상속과 구조체를 사용하여도 모델링할 수 있습니다.
상속 관계를 처음부터 구축하는 경우, 프로토콜 상속을 우선하세요. 클래스 상속은 오직 클래스만 가능한 반면에, 프로토콜은 클래스, 구조체와 열거형이 상속에 참여하도록 허용합니다. 데이터를 어떻게 모델링할 지 선택할 때, 먼저 프로토콜 상속을 사용하여 데이터 타입의 계층 구조 구축을 시도하고, 그런 다음 구조체에 해당 프로토콜을 채택하세요.