# iOS | Swift/--- 공식 문서

[Article] Choosing Between Structures and Classes

jiniz.ll 2021. 12. 27. 21:46

— Deside how to store data and model behavior

 

 

mutating 을 공부하면서 든 의문점 중 하나는 만약 mutating 을 써야할 필요가 있는 경우에 굳이 뭔가 데이터를 변경하기 위해 mutating 키워드를 사용해야 한다면 클래스를 사용하는게 낫지 않을까?

 

왜 swift는 mutating 이란 기능을 만든걸까? 한 구조체 내에서 mutating 을 너무 자주 사용한다면 성능적으로 더 안 좋은 점이 있는건가? 데이터가 메소드내에서 자주 변경된다면 클래스를 사용하는게 더 나은걸까? 뭐 이런 생각이 들면서 그렇다면 구조체와 클래스를 언제 써야 좋은것일지 판단하기가 어려워졌다

 

전에 부캠을 할 때도 이런 얘기가 나왔다. 애플은 구조체를 내부적으로 많이 사용한다고 하는데 (확실하지 않음..ㅋㅋ) 그렇다면 되도록이면 구조체를 쓰는게 좋은건가? 왜 애플은 구조체를 쓰는걸까? (사실 내가 프로젝트를 하게되면 정말 간단한 데이터 모델이 아니라면 주로 클래스를 사용하게 되는데..) 언제 어떤 것을 사용하는게 좋은걸까

 

뭔가 해결 답안이 내려지지 않았기 때문에 여전히 잘 모르겠고.. 마침 mutating 에 대한 검색을 하다 애플의 관련 article 이 있는 것을 발견했다..! 이게 내 궁금증을 해소해줄지는 모르겠지만 일단 읽어보기~

 

(정확한 의도를 파악하기가 너무 어렵다…ㅜ)

 

Overview

  • 구조체와 클래스는 데이터를 저장하고 앱 내에서의 동작을 모델링하는데 사용됨
  • 하지만 이 둘의 유사성 때문에 어떤 것을 사용해야할지 선택하는데 어려움이 있음

앱에 새로운 데이터 타입을 추가할 때 어떤 것을 선택하는 것이 좋을 지 다음 사항을 참고하기

  • Use structures by default — 기본적으로 구조체 사용하세요
  • Use classes when you need Objective-C interoperability — Objective-C 와 상호 운용이 필요하다면 클래스 사용하세요
  • Use classes when you need to control the identity of the data you’re modeling — 모델링한 데이터의 동일성을 통제할 필요가 있다면 클래스 사용하세요
  • Use structures along with protocols to adopt behavior by sharing implementations — 구현을 공유함으로써 행동을 채택하기 위해 프로토콜과 함께 구조체를 사용하세요

 

Choose Structures by Default

— 기본적으로 구조체 사용하기

  • 일반적으로 데이터를 표현할 때 구조체 사용하기
  • 스위트의 구조체는 다른 언어들이 클래스에만 제한하는 많은 기능들을 포함함
  • 저장 프로퍼티(Stored Property), 연산 프로퍼티(Computed Property), 메소드를 포함하며
  • 프로토콜을 채택할 수 있음
  • 스위프트의 표준 라이브러리 및 Foundation 에 포함되어 있는 number, string, array, dictionary 와 같은 타입들은 구조체를 사용함
  •  
  • 구조체를 사용하면 앱의 전체 상태를 고려하지 않고 코드의 일부를 추론하기 쉬워짐
  • 왜냐하면 클래스와 달리 구조체는 값 타입이기 때문에 구조체의 값이 변경되었을 때 의도적으로 앱의 흐름에 일부(part)로 변경 사항을 전달하지 않는다면 앱의 다른 부분에서 알 수 없음
  • 결과적으로 코드의 일부만 확인하면서 보다 확신을 갖고 (구조체) 인스턴스를 변경할 수 있음

 

Use Classes When You Need Objective-C Interoperability

— Objective-C 와 상호 운용이 필요하다면 클래스 사용하기

  • 데이터를 처리하는데 Objective-C API 를 사용하거나 데이터 모델을 Objective-C 프레임워크에 선언된 클래스 계층에 적용해야 한다면 클래스나 클래스 상속을 사용해서 데이터를 모델링 해야함
  • 실제로, 많은 Objective-C 프레임워크는 개발자가 서브클래스화 하려는 클래스를 제공함 (개발자가 상속받아 만들고자 하는 서브클래스의 상위 클래스를 제공함) — [원문. For example, many Objective-C frameworks expose classes that you are expected to subclass. 인데 정확히 의미하는 바를 모르겠다…]
  •  
  • 구조체에 상속받으려고 하면 에러 발생함!

 

Use Classes When You Need to Control Identity

— 동일성을 다뤄야 할 필요가 있을 때는 클래스 사용하기

  • Swift 의 클래스는 참조 유형이기 때문에 identity(동일성/식별자(?)) 에 대한 개념이 존재함 (말 그대로 클래스 사이에서 구분할 수 있는 id 의 개념인듯)
  • 두 개의 다른 클래스 인스턴스가 stored property에 동일한 값을 갖고 있다하더라도 동일 여부를 판단하는 연산자(===)에 의해 다른 클래스로 여겨짐
  • 이것은 클래스 인스턴스를 앱 내에서 공유했을 때, 해당 인스턴스를 변화시킨다면 그 인스턴스의 주소를 갖고 있는 모든 부분에서 그 변화를 확인할 수 있다는 것을 의미함
  • 사용하는 인스턴스가 이런 동일성의 특징이 필요하다면 클래스를 사용할 것
  • 일반적인 사용 예는 file handles, network connections, CBCentralManager와 같은 shared hardware intermediaries 등이 있음
  •  
  • 예를 들어, 로컬 데이터베이스를 연결하는 타입이 있다면, 그 데이터베이스에 접근하는 것을 관리하는 코드는 데이터베이스의 상태를 앱에서 완전히 제어할 수 있어야 함
  • 이런 상황에서는 클래스를 사용하는 것이 적절함
  • 하지만 공유 데이터베이스 객체가 앱 내에서 접근할 수 있는 부분을 제한해야 함

중요.
클래스(identity)는 신중하게 사용해야 합니다. 클래스 인스턴스를 앱 전체에 걸쳐 많은 부분에서 사용하게(sharing) 되면 로직 에러가 더 쉽게 발생합니다. 공유가 많이 된 인스턴스를 변경하면 어떤 결과가 발생할지 알 수 없기 때문에 이러한 코드를 작성할 때는 더욱 정확히 사용해야 합니다.

 

Use Structures When You Don’t Control Identity

— 동일성을 다룰 필요가 없다면 구조체 사용하기

  • 제어하지 않는 identity 가 있는 entity 에 대한 정보를 포함하는 데이터를 모델링할 때는 구조체를 사용하기
  • 예를 들어 원격 데이터베이스를 참조하는 앱에서 인스턴스의 identity 는 외부 엔티티가 소유하고 있고 식별자(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 와 같은 모델 타입으로 바꿀 때 유용함
  • 예를 들어, 앱이 사용자 피드백에 대한 응답으로 여러 펜팔을 추천해줌
  • 이 때, PenPalRecord 구조체는 실제 데이터베이스 데이터(record)의 identity 를 변경(control)하지 않기 때문에 로컬에 있는 PenPalRecord 인스턴스를 변경한다고 해서 데이터베이스의 값이 실수로 변경될 위험이 없음
  •  
  • 만약 앱의 다른 부분에서 myNickname 을 변경하고 서버로 변경 요청을 보내더라도 가장 최근에 거절된 펜팔 추천은 실수로라도 변경 사항에 의해 선택되지 않을 것임(??)
  • 왜냐하면 myID 프로퍼티는 상수로 선언되어 있어서 로컬에서는 변경될 수 없음
  • 따라서 데이터베이스로 보낸 요청은 잘못된 레코드를 변경하지 않을 것임

 

(PenPalRecord 에서 myID 는 상수기 때문에 변경이 불가하고 나머지 프로퍼티는 데이터 변경이 가능함. 이렇게 로컬에서 변경이 가능한 부분을 제한하면 데이터베이스 자체의 레코드 값을 변경하는 것이 아니므로 로컬에서 값이 변경되더라도 데이터베이스 자체에는 문제가 생기지 않음. 서버로부터 이 구조체 인스턴스를 여러 곳에서 받아 사용할 때, 그 중 하나의 부분에서 myNickname 값을 변경하여 서버로 결과를 보내면 변경이 되었더라도 myID 값이 변경된 것은 아니기 때문에 잘못된 결과를 받아오지 않음)

 

Use Structures and Protocols to Model Inheritance and Share Behavior

— 상속 관계를 모델링하고 행동을 공유하기 위해 구조체와 프로토콜을 사용하기
(구조체와 프로토콜을 사용해서 상속 계층 구조를 모델링하면 클래스, 구조체 열거형 모두 허용하기 때문에 여러 Behavior 를 공유할 수 있다는 의미인거 같기도…)

  • 구조체와 클래스는 모두 상속의 형태를 지원함
  • 근데 구조체와 프로토콜은 오직 프로토콜만 채택할 수 있고 클래스를 상속받을 수 없음
  • 하지만 클래스 상속을 사용해서 만들 수 있는 상속 계층 구조는 프로토콜 상속과 구조체를 사용하는 것으로도 구조화 할 수 있음
  •  
  • 만약 처음부터 상속 관계를 설계 한다면 프로토콜 상속을 하는것을 추천함
  • 클래스 상속은 오직 다른 클래스로만 가능한데, 프로토콜은 클래스, 구조체, 열거형 모두 허용하기 때문임
  • 데이터를 어떻게 모델링할지 고민한다면 먼저 프로토콜 상속을 사용해서 데이터 타입의 계층 구조를 설계하기
  • 그런 다음 구조체에 해당 프로토콜을 채택하기

참고.
Choosing Between Structures and Classes
클래스와 구조체 (Choosing Between Structures and Classes)
Swift Choosing Between Structures and Classes