iOS 공부하는 감자

Swift) Choosing Between Structures and Classes 본문

Swift

Swift) Choosing Between Structures and Classes

DongTaTo 2023. 1. 30. 23:03
반응형

객체 vs 인스턴스

다른 프로그래밍 언어들은 Swift와 다르게 구조체의 기능이 제한적이다. (메서드를 사용하지 못함) 그래서 클래스의 인스턴스를 많이 사용하고, 클래스의 인스턴스를 전통적으로 객체라고 부른다.

하지만 Swift에서 구조체는 Class와 기능이 흡사하기 때문에 객체보단 Class와 Struct를 모두 표현할 수 있는 인스턴스라는 표현을 사용한다.

Structures and Classes
An instance of a class is traditionally known as an object. However, Swift structures and classes are much closer in functionality than in other languages, and much of this chapter describes functionality that applies to instances of 
either a class or a structure type. Because of this, the more general term 
instance is used.

 

 

Swift에서 구조체와 클래스는 기능적으로 매우 유사하기 때문에 어떤 것을 사용하여 데이터를 저장하고 기능을 정의할지 선택에 어려움을 느낄 수 있다.

그래서 애플은 다음과 같은 권장 사항을 고려하여 선택하도록 가이드라인을 제공하고 있다.

  1. 기본적으로 구조체를 사용한다.
  2. Objective-C와 상호 운용성이 필요한 경우 클래스 사용한다.
  3. 모델링 중인 데이터의 Identity를 제어해야 하는 경우 클래스를 사용한다.
  4. 구조체와 프로토콜을 사용하여 상속 및 공유 동작을 모델링한다.

 

 

1. 기본적으로 구조체를 사용한다.

Swift의 구조체는 다른 언어의 구조체와 다르게 클래스와 기능이 흡사하다.

프로퍼티와 메서드를 사용할 수 있고, Protocol 채택도 가능하며, Swift 표준 라이브러리와 Foundation에서 String, Array 처럼 자주 사용되는 타입들을 구조체로 정의해서 사용하고 있다.

 

구조체가 기본적으로 권장되는 이유는 다음과 같다.

  1. Class에 비해 빠른 속도
  2. 코드의 데이터 변경 사항을 추론하기 쉬움
  3. 다중 스레드 환경에서 안전

위 내용들은 Class와 Struct의 가장 중요한 차이인 참조 타입, 값 타입과 관련이 있다.

 

1-1. Class에 비해 빠른 속도

1. Class는 참조 타입으로 메모리의 Heap 영역에 저장된다. Heap 영역은 LIFO 방식으로 동작하는 Stack과 다르게 동적 메모리 할당이 필요하기 때문에 인스턴스를 생성하고 해제하는 과정이 Stack 영역에 저장되는 구조체보다 느리게 동작한다.

 

2. 멀티 스레드 환경에서 여러 스레드가 동시에 Heap 메모리에 접근이 가능하기 때문에, 내부적으로 동기화 메커니즘을 통해 무결성을 달성해야 한다. (동적 할당을 위한 비용)

 

3. 구조체는 클래스의 인스턴스보다 적은 메모리 공간을 사용한다. (클래스의 인스턴스는 상속을 지원하기 위해 클래스 타입이 저장된 메모리 영역과 RC를 내부적으로 추가 저장한다.)

 

4. 구조체에서는 참조 카운팅 계산이 필요하지 않다.

 

5. 메모리 단편화가 발생할 수 있다. Class의 인스턴스를 생성하면 동적 메모리 할당을 하기 때문에 인스턴스를 생성하고 해제하는 과정에서 메모리가 조각화되어 성능 저하가 발생할 수 있다. 

 

6. 구조체는 인라인으로 저장된다. 즉, 클래스의 경우처럼 데이터가 별도 메모리 공간에 저장되는 것이 아니라 인스턴스의 메모리에 직접 저장되기 때문에, 접근 시간이 단축될 수 있다.

 

7. Class는 상속을 지원하기 위해 메서드가 Dynamic Dispatch로 동작한다. (Method Dispatch)

 

8. 구조체를 사용하면 캐시 적중률이 높아질 수 있다. (이유는 좀 더 찾아봐야 할듯..)

 

1-2. 코드의 데이터 변경 사항을 추론하기 쉬움

값 타입은 기본적으로 다른 변수에 할당하면 값을 복사하여 저장한다. 즉, 새로운 메모리 공간에 복사하여 값을 할당하게 되는데 이를 " copy-on-assignment" 라고 한다.

앱 흐름의 일부로 데이터의 변경 사항을 의도적으로 전달하지 않는 한 값을 복사하여 저장하기 때문에 구조체 인스턴스의 주소를 참조하고 조작하는 외부 객체가 없어서 코드를 작성할 때 구조체가 사용되는 영역에만 집중하면 된다. (외부 요인으로 인해 값이 변경되는 일이 없다.)

따라서 인스턴스에 대한 변경 사항이 해당 영역에서 보이지 않는 함수(메서드)에 의해 변경되는 것이 아닌, 명시적으로 해당 영역의 코드에 의해 변경되기 때문에 데이터의 변경 사항을 추론하기 쉽다.

 

1-3. 다중 스레드 환경에서 안전

구조체는 값 타입으로 메모리의 스택 영역에 저장되고, 멀티 스레드 환경에서 각 스레드는 메모리의 스택 영역을 따로 할당받는다.

데이터가 공유되는 Code, Data, Heap 영역과 다르게 고유한 Stack 영역을 할당받아서 값 타입을 저장하기 때문에 다른 스레드가 값 타입에 직접 접근할 수 없으므로 공유 변수로 인한 문제(경쟁 조건, 교착 상태 등)가 발생하지 않는다.

반면 클래스의 경우, 여러 스레드에서 공유되는 Heap 영역에 인스턴스를 저장하기 때문에 의도적으로 다중 스레드 환경에서 안전하도록 만들지 않는 한 이러한 고유한 안정성을 갖지 않는다.

 

 

2. Objective-C와 상호 운용성이 필요한 경우 클래스 사용한다.

Objective-C에서 사용되는 대부분의 타입은 NSObject의 하위 클래스로 만들어져 있다.

NSString, NSArray 등등 사용되는 모든 타입은 클래스이고, 참조 타입이다.

때문에 1. Objective-C API를 통해 데이터를 처리해야 하거나, 2. Swift에서 생성한 데이터 모델을 Objective-C 프레임워크에 정의된 클래스 계층 구조에 맞춰야 하는 경우 Class를 사용하여 데이터를 모델링해야 할 수 있다.

 

 

 

3. 모델링 중인 데이터의 Identity를 제어해야 하는 경우 클래스를 사용한다.

참조 타입인 Class는 내장된 Identity의 개념이 있다.

클래스의 인스턴스를 다른 곳으로 할당할 때, 값을 복사하여 전달하는 구조체와 다르게 인스턴스의 참조를 전달한다.

여러 곳에서 하나의 인스턴스에 대한 참조를 활용하여 사용해야 하는 경우 Class 사용이 권장된다.

ex) 데이터 처리, 네트워크 연결 등을 처리할 때

 

예를 들어 Local Database에 접근하여 데이터 처리를 하는 타입을 구현할 때, 해당 Database에 대한 접근을 관리하는 타입은 앱에서 볼 때 Database의 상태를 완전히 제어해야 한다.

이 경우 클래스를 사용하는 것이 적절하지만 공유 데이터베이스에 엑세스할 수 있는 앱의 부분을 제한해야 한다.

 

왜냐하면 앱 전체에서 클래스의 인스턴스를 광범위하게 공유하면 논리 오류가 발생할 가능성이 높아지고, 예상하지 못한 데이터의 변경이 발생할 수 있으며, 그 원인을 추적하기 어렵기 때문..

그래서 광범위하게 공유되는 인스턴스를 사용할 때는 주의가 필요하다.

 

Identity를 제어할 필요가 없다면 구조체를 사용한다.

예를 들어 Database에 저장하기 위한 데이터 타입을 정의해야 한다면, 해당 타입들에 대해서 Identity를 제어할 필요가 없다.

Request, Response를 위한 데이터 타입의 정의도 마찬가지.. 단순 데이터 타입을 정의하는 상황에서는 구조체의 사용이 권장된다.

 

 

 

4. 구조체와 프로토콜을 사용하여 상속 및 공유 동작을 모델링한다.

구조체와 클래스는 모두 상속 형식을 지원한다. 구조체와 프로토콜은 다른 프로토콜만 채택할 수 있고, 클래스로부터 상속을 받을 수는 없다.

그러나, 클래스의 상속으로 구축할 수 있는 상속 계층은 프로토콜의 상속과 구조체를 사용하여 모델링할 수도 있다.

 

처음부터 상속 관계를 구축하는 경우 프로토콜의 상속을 사용한 모델링을 선호한다.(권장한다?)

프로토콜을 사용하면 클래스, 구조체, 열거형에서 채택하여 상속 구조를 사용할 수 있지만, 클래스의 상속은 다른 클래스와만 호환되기 때문

따라서 데이터 모델링 방법을 선택할 땐, 먼저 프로토콜 상속을 통해 데이터 타입의 계층 구조를 구축한 후, 프로토콜을 구조체에 채택하여 사용한다.

 

 

 


 

 

 

Struct 사용을 고려할 상황

  • 단순히 데이터를 캡슐화하기 위한 목적으로 사용될 때
  • Identity를 제어할 필요가 없는 경우
  • 상속이 필요하지 않은 경우
  • 멀티 쓰레드 환경에서 공유 변수로 인한 문제가 발생할 가능성이 있는 경우

 

Class 사용을 고려할 상황

  • 상속 구조가 필요한 경우
  • deinit 구문이 필요한 경우
  • 인스턴스의 Identity를 제어할 필요가 있는 경우
  • Objective-C와 함께 사용해야 하는 경우 Class를 사용해야 한다.
  • 인스턴스가 단순히 외부 상태를 나타내기 위한 목적으로 사용되는 경우 (APIManager, NetworkMonitor 처럼 외부 정보를 전달하기 위한 통로 역할을 수행하는 경우)

 

 

 

 

Hola

반응형

'Swift' 카테고리의 다른 글

Operation, OperationQueue (feat. GCD)  (0) 2022.12.28
Swift) Property Wrapper  (0) 2022.07.20
split() vs components()  (0) 2022.06.04
문자열 다루기  (0) 2022.01.31