본문 바로가기

Swift/꼬꼬무

Structures and Classes

Structures and Classes

 

데이터를 캡슐화 하는 사용자 정의 타입을 모델.

 

클래스의 인스턴스는 객체(Object)라고 불렀지만, Swift의 구조체와 클래스는 다른 언어보다 훨씬 비슷합니다. 그래서 일반적으로 인스턴스(Instance) 라는 용어로 사용합니다.

 

구조체와 클래스의 비교

스위프트에서 구조체와 클래스는 공통적인 부분이 많습니다.

  • 값을 저장하기 위한 properties정의 (저장 프로퍼티)
  • 기능을 제공하기 위한 method 정의 (메서드)
  • 서브 스크립트 구문을 사용하여 값에 접근을 제공하는 서브 스크립트 정의
  • 초기 상태를 세팅하기 위한 initializers 정의
  • 기본 구현을 넘어 기능적인 확장 (extension)
  • 특정 종류의 표준 기능을 제공하기 위한 프로토콜 준수
For more information, see PropertiesMethodsSubscriptsInitializationExtensions, and Protocols.

 

클래스에만 있는 추가적인 기능

  • 한 클래스가 다른 클래스의 특성을 상속 가능 (단일 상속)
  • 타입 캐스팅을 사용하면 런타임에 클래스 instance 타입을 확인하고 해석이 가능
  • Deinitalizers 이용하여 클래스의 instance 할당한 모든 리소스를 해제할 수 있도록 합니다.
  • 참조 카운팅은 클래스 instance에 대한 하나 이상의 참조를 허용합니다.

 

클래스가 지원하는 추가 기능은 코드의 복잡성이 증가하게 되기 때문에 일반적으로 추론하기 쉬운 구조체를 이용하고 클래스만의 기능의 사용이 필요할때 클래스를 사용하도록 합니다. 실제로 대부분의 사용자 정의 타입이 구조체와 열거형입니다.

클래스가 지원하는 추가 기능은 복잡성이 증가합니다.

 

 
구조체와 클래스의 비교 포스팅은 추후에

 

구조체와 클래스는 정의하는 구문이 유사합니다. 구조체는 struct,  클래스는 class 키워드로 시작하고, 둘다 중괄호 안에 정의됩니다.
struct SomeSttructure {
	// structure definition goes here
}

class SomeClass {
	// class definition goes here
}

 

새로운 구조체나 클래스를 정의할때 표준 Swift 타입의 대소문자와 일치하도록 UpperCamelCase로 지정하도록 하고 (SomeStructure, SomeClass) 내부의 메서드나 프로퍼티는 타입의 이름과 구분하기 위해 lowerCamelCase로 네이밍 합니다.

 

구조체와 클래스 정의의 예

struct Resolution {
	var width = 0
	var height = 0
}

class VideoMode {
	var resolution = Resolution()
	var interlaced = false
	var frameRate = 0.0
	var name: String?
}

위 예제에서 Resolution 이라는 새로운 구조체를 정의하고, 구조체는 width와 height라는 저장 프로퍼티를 가집니다.

저장 프로퍼티는 구조체나 클래스의 일부로 저장되는 상수 또는 변수입니다. 이 두 프로퍼티는 정수 값 0으로 초기값이 설정되어 Int로 타입 유추 됩니다.

VideoMode라는 클래스는 4개의 저장 프로퍼티를 가지고 있습니다.

각 프로퍼티 resolution은 Resolution 구조체 인스턴스로 초기화되고 interlaced는 false, frameRate는 0.0, name은 옵셔널 스트링 값으로 초기화 됩니다.

 

Structure and Class Instances

Resolution 구조체나 VideoMode 클래스는 기본적인 Resolution, VideoMode의 모양만을 설명합니다. 자체적인 해상도나 비디오 모드에 대해 나타내기 위해서 인스턴스 생성이 필요합니다.

 

인스턴스 생성 구문은 구조체와 클래스가 매우 유사합니다.

let someResolution = Resolution()
let someVideoMode = VideoMode()

구조체와 클래스 모두 새로운 인스턴스 생성을 위해 initializer 구문을 사용합니다.

initializer 구문의 간단한 형태는 Resolution() 또는 VideoMode() 처럼 클래스나 구조체 이름 다음에 빈 소괄호를 사용하는 것입니다. 이렇게 하면 모든 프로퍼티가 기본 값으로 초기화된 새로운 클래스나 구조체 인스턴스가 생성됩니다.

 
Class and structure initialization is described in more detail in Initialization.

 

Accessing Properties

dot 연산자를 사용하여 특정 인스턴스의 프로퍼티에 액세스가 가능합니다.

dot 연산자는 인스턴스 이름 뒤에 “.” 구문으로 분리하고 공백없이 프로퍼티 이름을 입력합니다.

Print("The width or someResoultion is \(someResolution.width)")
// Prints "The width or someResolution is 0"

예제에서 someResolution.width 는 someResolution의 width 프로퍼티를 참조하고 기본 값인 0이 반환됩니다.

 

VideoMode의 resolution 프로퍼티에 width 프로퍼티 처럼 서브 프로퍼티들에 drill down 할 수 있습니다.

print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// Prints "The width of someVideo is 0"

 

변수 프로퍼티에 새로운 값을 할당하기 위해 dot 연산자를 사용할 수도 있습니다.

someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is now 1280"

 

Memberwise Initializers for Structure Types

모든 구조체는 새로운 구조체 인스턴스의 멤버 프로퍼티를 초기화하는 데 사용할 수 있는 자동으로 생성된 멤버별 initializer가 있습니다.

새로운 인스턴스에 프로퍼티 초기값은 이름으로 멤버별 초기화에 전달될 수 있습니다

let vga = Resolution(width: 640, height: 480)

구조체와 달리 클래스 인스턴스는 멤버별 initializer를 받지 않습니다.

 
Initializers are described in more detail in Initialization.

 

Structures and Enumerations Are Value types

값 타입은 변수나 상수에 할당될때 혹은 함수에 전달될때 복사되는 타입 입니다.

 

실제로 Swift에서 integers, floating-point numbers, Booleans, strings, arrays, dictionaries와 같은 기본 타입은 구조체로 구현되어 있습니다.

 

Swift에서 모든 구조체와 열거형은 값 타입입니다.

구조체나 열거형 인스턴스와 해당 인스턴스가 프로퍼티로 사용되는 모든 값 타입은 코드내에서 항상 복사되어 전달됩니다.

 

array, dictionaries, string 같이 표준 라이브러리에 정의된 콜렉션들은 복사 성능을 최적화합니다. 즉시 복사본을 만들지 않고 원본 인스턴스와 복사본 사이의 요소가 저장되는 메모리를 공유하고, 복사본 중 하나가 수정되면 수정 직전에 복사됩니다. 코드에서는 항상 복사가 즉시 수행된 것과 같이 보입니다.

 

이전 예제에서 Resolution 구조체를 사용하는 다음 예제를 살펴보면

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd

width 1920, height 1080으로 초기화 된 Resolution 인스턴스를 생성하고

cinema 변수를 선언하고 hd 의 현재 값을 설정합니다. Resolution 구조체의 복사본이 만들어지고 cinema에 할당됩니다. hd와 cinema는 같은 width, height 값을 가지지만 전혀 다른 인스턴스 입니다.

 

그리고 cinema의 width를 2040으로 변경하게 되면

 

cinema.width = 2048

cinema의 width 프로퍼티를 확인해보면 2048로 바뀌어 있습니다.

print("cinema is now \(cinema.width) pixels wide")
// Prints "cinema is now 2048 pixels wide"

 

그러나 기존 hd 인스턴스의 width 프로퍼티는 여전히 이전 값인 1920입니다.

print("hd is still \(hd.width) pixels wide")
// Prints "hd is still 1920 pixels wide"

 

cinema에 hd의 현재 값이 주어졌을때 hd에 저장된 값은 새로운 cinema 인스턴스로 복사됩니다. 그 결과는 같은 숫자 값을 가진 완전히 분리된 2개의 인스턴스 입니다. 분리된 인스턴스이기 때문에 아래 그림과 같이 cinema의 너비를 2048로 설정해도 hd에 저장된 너비 값에 영향을 주지 않습니다.

 

 

같은 동작을 열거형에 적용해보면

enum CompassPoint {
		case north, south, ease, west
		mutating func turnNorth() {
				self = .north
		}
var currentDirection = CompassPoint.west
let rememberedDirection = currentDirection
currentDirection.turnNorth()

print("The current direction is \(currentDirection)")
print("The remembered direction is \(rememberedDirection)")
// Prints "The current direction is north"
// Prints "The remembered direction is west"

rememberdDirection에 currentDirection 값이 할당되면 실제로는 해당 값의 복사본으로 설정됩니다.

이후 currentDirection의 값을 변경해도 rememberedDirection에 저장된 원래 값의 복사본에는 영향을 주지 않습니다.

 

Classes Are Reference Types

값 타입과 달리 참조 타입은 변수 또는 상수에 할당되거나 함수로 전달될때 복사되지 않습니다. 복사 되는 대신에 같은 존재하는 인스턴스에 대한 참조가 이루어 집니다.

 

위의 VideoMode 클래스 정의를 사용하는 예제입니다.

let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEIghty.frameRate = 25.0

 

이 예제에서 새로운 상수 tenEighty를 선언하고 새로운 Video 인스턴스 클래스를 참조하도록 설정합니다.

video mode는 HD resolution의 이전의 HD 해상도인 1920 x 1080 사본이 할당됩니다.

interlaced를 true로 이름을 “1080i”, frameRate를 초당 25.0 프레임으로 설정합니다.

그 다음 alsoTenEight라는 새로운 상수에 tenEight를 할당하고 alsoTenEight의 frame rate를 수정합니다.

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

클래스는 참조 타입이기 때문에 tenEighty와 alsoTenEighty는 실제로 같은 VideoMode를 참조하고 있습니다.

실제로는 아래 그림과 같이 같은 하나의 인스턴스에 다른 2개의 이름을 가지고 있습니다.

 

 

tenEight의 frameReate 프로퍼티를 확인해보면 VideoMode 인스턴스에서 30.0의 새로운 프레임 속도가 올바르게 설정된 것을 알 수 있습니다.

print("The frameRate property of tenEight is now \(tenEight.frameRate)")
// Prints "The frameRate property of tenEight is now 30.0"

 

이 예제에서 참조타입이 왜 추론하기 어려울 수 있는지 보여줍니다.

VideoMode가 참조하는 인스턴스들이 멀리 떨어져있는경우 어떤 것에 의해 VideoMode가 변경되었는지 모든 방법을 찾기 매우 어렵습니다.

예제에서는 tenEighty가 사용될때마다 alsoTenEighty가 사용되는 모든 부분도 생각해야 하고, 반대도 마찬가지로 고려되어야 합니다.

반대로 값 타입은 하나의 소스파일 안에 비교적으로 가깝게 작성되어 추론하기 쉽습니다.

 

tenEighty와 alsoTenEighty는 변수가 아닌 상수로 선언되었지만 내부 프로퍼티 frameRate는 변경이 가능합니다.

왜냐하면 tenEighty와 alsoTenEighty의 상수 값 자체는 변하지 않기 때문입니다.

tenEighty와 alsoTenEighty는 VideoMode 인스턴스로 "저장" 되지 않고 VideoMode 인스턴스를 참조합니다. 그렇기 때문에 VideoMode에 대한 상수 참조값이 변경되는 것이 아니라 내부에 변수로 선언된 frameRate가 변경되는 것입니다.

 

Identity Operators

클래스는 참조타입이기 때문에 여러개의 상수나 변수로 같은 하나의 인스턴스 참조가 가능합니다. (구조체나 열거형은 변수나 상수로 할당되거나 함수로 전달될때 항상 복사되기 때문에 똑같이 동작하지 않습니다.)

두개의 상수나 변수가 같은 인스턴스를 참조하는 지 확인이 필요한 경우가 있는데 이때 Identity Operator를 사용하여 비교할 수 있습니다. Swift는 두개의 Identity Operators(식별 연산자)를 제공합니다.

  • Identical to ( === )
  • Not identical to ( !== )

이 연산자들을 사용하여 2개의 변수 혹은 상수가 같은 하나의 인스턴스를 참조하는지 확인할 수 있습니다.

if tenEIghty === alsoTenEighty {
		print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// Prints "tenEIghty and alsoTenEighty refer to the same VideoMode instance."

identical(=== 로 표시)는 같음을 의미하는 equal to (==로 표시)와 같다는 의미가 아닙니다.

Identical은 두개의 상수 혹은 변수 클래스타입이 하나의 같은 클래스 인스턴스를 참조한다는 의미입니다.

Equal to는 두 인스턴스의 값이 동일하거나 동등하다는 의미입니다.

사용자 정의 구조체나 클래스를 정의할 때 두 인스턴스가 같은지의 여부를 결정하는 것은 사용자의 몫입니다.

 
The process of defining your own implementations of the == and != operators is described in Equivalence Operators.

 

Pointers

C, C++, 또는 Objective-C에 경험이 있다면 메모리의 주소를 참조하기 위해 포인터 (pointers) 를 사용한다는 것을 알고 있을 것입니다. 일부 참조 타입의 인스턴스를 참조하기 위한 Swift 상수 또는 변수는 C의 포인터와 유사하지만 메모리의 주소에 대한 직접적인 포인터가 아니며 포인터 표시를 위해 별표 (*)를 작성할 필요가 없습니다. 대신에 이러한 참조는 Swift의 다른 상수 또는 변수처럼 정의됩니다. 표준 라이브러리는 포인터와 직접 상호작용이 필요한 경우 사용할 수 있는 포인터와 버퍼 타입을 제공합니다. 자세한 내용은 수동 메모리 관리 (Manual Memory Management) 를 참고 바랍니다.

'Swift > 꼬꼬무' 카테고리의 다른 글

Methods  (0) 2023.05.23
PopLast & RemoveLast & DropLast  (0) 2023.05.22
Properties  (0) 2023.05.21
DispatchQueue with weak self  (0) 2023.05.20
Choosing Between Structures and Classes  (0) 2023.05.19