구조체와 클래스는 데이터 저장 및 앱에서의 행동 모델링을 위한 좋은 선택이지만, 둘의 유사성 때문에 하나를 선택하는 것이 어려울 수 있습니다.
다음 권장 사항을 고려해 앱에 새로운 데이터 유형을 추가할 때 어떤 옵션이 의미가 있는지 선택하는데 도움을 줍니다.
일반적인 종류의 데이터를 나타내기 위해 구조체를 사용합니다. Swift의 구조체는 다른 언어의 클래스에만 한정된 많은 기능을 포함합니다. 저장 속성, 계산 속성 및 메서드를 포함할 수 있습니다. 게다가, Swift 구조체는 기본 구현을 통해 동작을 얻기 위해 프로토콜을 채택할 수 있습니다. Swift 표준 라이브러리와 Foundation은 숫자, 문자열, 배열 및 딕셔너리와 같이 자주 사용하는 유형에 구조체를 사용합니다.
구조체를 사용하면 애플리케이션의 전체 상태를 고려하지 않고도 코드의 일부에 대해 더 쉽게 추론할 수 있습니다. 구조체는 값 타입이기 때문에 클래스와는 달리 구조체에 대한 지역 변경 사항은 애플리케이션의 나머지 부분에서는 볼 수 없으며, 변경 사항을 애플리케이션의 흐름의 일부로 의도적으로 전달하지 않는 한 보이지 않습니다. 결과적으로 코드의 한 섹션을 보고 그 섹션의 인스턴스에 대한 변경 사항이 간접적으로 관련된 함수 호출로부터 보이지 않게 이루어지는 것이 아니라 명시적으로 이루어질 것이라는 확신을 가질 수 있습니다.
데이터를 처리해야 하는 Objective-C API를 사용하거나 데이터 모델을 Objective-C 프레임워크에서 정의된 기존 클래스 계층에 맞춰야 하는 경우, 데이터를 모델링하기 위해 클래스 및 클래스 상속을 사용해야 할 수 있다. 예를 들어, 많은 Objective-C 프레임워크는 서브클래싱할 것으로 예상되는 클래스를 노출합니다.
Swift 클래스는 참조 타입이기 때문에 내장된 정체성 개념을 가지고 있습니다. 이는 두 개의 서로 다른 클래스 인스턴스가 각 저장 속성에 대해 동일한 값을 가질 때, 여전히 정체성 연산자(===)ㅇ 의해 다르게 간주된다는 것을 의미합니다. 또한 앱 전반에 걸쳐 클래스 인스턴스를 공유할 때, 해당 인스턴스에 대한 변경 사항이 해당 인스턴스에 대한 참조를 가진 코드의 모든 부분에 표시된다는 것을 의미합니다. 인스턴스에 이러한 종류의 정체성을 필요할 때 클래스를 사용합니다. 일반적인 사용 사례로는 file handles, network connections, shared hardware, CBCentralManager와 같은 것이 있습니다.
여러 클론 프로젝트를 진행했고, 바이브 코딩을 진행했을 때 네트워크 작업을 수행하는 NetworkManager 혹은 NetworkService 전부 class로 작성이 되어 있었습니다. URLSession 자체가 class로 구현되어 있기 때문에, class를 사용하는 것에 대한 답변이 될 수 있을 것 같습니다.
예를 들어, 로컬 데이터베이스 연결을 나타내는 유형이 있는 경우, 해당 데이터베이스에 대한 액세스를 관리하는 코드는 앱에서 보는 데이터베이스의 상태에 대한 완전한 제어가 필요합니다. 이 경우 클래스 사용이 적절하지만, 앱의 어떤 부분이 공유 데이터베이스 객체에 접근할 수 있는지를 제한하는 것이 중요합니다.
Important
정체성을 신중하게 다뤄야합니다. 앱 전반에 걸쳐 클래스 인스턴스를 광범위하게 공유하면 논리 오류가 발생할 가능성이 높아집니다. 많이 공유되는 인스턴스를 변경할 때 그 결과를 예상하지 못할 수 있으므로, 그러한 코드를 올바르게 작성하는 데 더 많은 작업이 필요합니다.
제어하지 않는 정체성을 가진 엔티티에 대한 정보를 포함한 데이터를 모델링할 때는 구조체를 사용하세요. 원격 데이터베이스에 상담하는 앱에서는 예를 들어, 인스턴스의 정체성이 외부 엔티티에 의해 완전히 소유되고 식별자로 전달될 수 있습니다. 앱의 모델의 일관성이 서버에 저장된다면, 식별자로 구조체로 레코드를 모델링할 수 있습니다. 아래 예시에는 jsonResponse 서버에서 인코딩된 PenPalRecord 인스턴스를 포함하고 있습니다.
struct PenPalRecord {
let myID: Int
var myNickname: String
var recommendedPenPalID: Int
}
var myRecord = try JSONDecoder().decode(PenPalRecord.self, from: jsonResponse)
모델 유형인 PenPalRecord에 대한 로컬 변경 사항은 유용합니다. 예를 들어, 앱은 사용자 피드백에 따라 여러 다른 펜팔을 추천할 수 있습니다. PenPalRecord 구조는 기본 데이터베이스 레코드의 아이텐티티를 제어하지 않기 때문에, 로컬 PenPalRecord 인스턴스에 대해 수행된 변경 사항이 데이터베이스의 값을 우연히 변경할 위험이 없습니다. 앱의 다른 부분이 myNickname을 변경하고 서버에 변경 요청을 제출하면, 가장 최근에 거부된 PenPal 추천이 변경으로 인해 실수로 선택되지 않을 것입니다. myID 속성이 상수로 선언되었기 때문에 로컬에서 변경될 수 없습니다. 결과적으로 데이터베이스에 대한 요청은 실수로 잘못된 레코드를 변경하지 않을 것입니다.
Model을 정의할 때 구조체를 왜 사용해야 하는지에 대한 답변이 될 수 있을 것 같습니다.
구조체와 클래스는 모두 일종의 상속을 지원합니다. 구조체와 프로토콜은 프로토콜만 채택할 수 있고, 클래스에서는 상속받을 수 없습니다. 그러나 클래스 상속으로 구축할 수 있는 상속 계층의 종류는 프로토콜 상속과 구조체를 사용해 모델링할 수 있습니다. 만약 처음부터 상속 관계를 구축하고 있다면, 프로토콜 상속을 선호하세요. 프로토콜은 클래스와 구조체 및 열거형이 상속에 참여할 수 있도록 허용하는 반면, 클래스 상속은 다른 클래스와만 호환됩니다. 데이터 모델링 방법을 선택할 때는 먼저 프로토콜 상속을 사용해 데이터 유형의 계층을 구축한 다음, 해당 프로토콜을 구조체에 채택해 보세요.
Choosing Between Structures and Classes
읽어주셔서 감사합니다! 오타 및 잘못된 내용은 언제든 댓글 달아주시면 최대한 빠르게 수정하겠습니다!