Swift/Swift 공식문서

[Swift] 공식문서 09 - Structures and Classes(구조체와 클래스)

말차프라푸치노 2021. 10. 16. 18:36

 

 

Structures and Classes — The Swift Programming Language (Swift 5.5)

Structures and Classes Structures and classes are general-purpose, flexible constructs that become the building blocks of your program’s code. You define properties and methods to add functionality to your structures and classes using the same syntax you

docs.swift.org

1차 수정 (2022 / 02 / 26) : 의역 및 오타, 스타일 수정 

 

 

Structures and Classes

구조체(Structure)와 클래스(Class) 는 프로그램 코드의 블럭이 되는 일반적인 구성요소입니다. 상수, 변수, 함수를 정의하는데 사용한 구문과 똑같이 프로퍼티와 메소드를 사용해 구조체와 클래스에 기능을 추가할 수 있습니다.

 

다른 프로그래밍 언어와 달리, Swift는 구조체나 클래스를위한 분리된 인터페이스와 실행 파일을 만들 필요가 없습니다. Swift에서는 구조체와 클래스를 하나의 파일에 정의하고, 클래스와 구조체에 대한 외부 접근은 다른 코드가 사용할 수 있도록 자동으로 제공됩니다.

(C언어 처럼 헤더파일이랑 소스파일 분리한다음 include 하는 것을 안하고 그냥 사용이 가능합니다.)

 

일반적으로 클래스의 인스턴스를 객체라고 생각합나디. 그러나 Swift의 구조체와 클래스는 다른 언어에 비해 기능적으로 가깝고, 이 챕터의 대부분은 클래스나 구조체 타입의 인스턴스에 사용되는 기능을 설명합니다. 따라서 더 일반적이 용어 instance가 사용됩니다.

 

 

 

Comparing Structures and Classes(구조체와 클래스의 차이)

구조체와 클래스는 많은 공통접이 있습니다.

  • 값을 저장하기 위한 프로퍼티 정의
  • 기능을 제공하기 위한 메소드 정의
  • 첨자(subscript) 구문을 사용해 각자의 값에 접근하기 위해 첨자 정의
  • 초기 값을 위한 이니셜라이저 정의
  • 기본 구현 이상으로 기능 확장
  • 특정 종류의 표준 기능을 제공하는 프로토콜 준수

클래스는 구조체가 가지지 못하는 더 많은 능력이 있습니다.

  • 상속을 사용해 다른 클래스가 다른 클래스의 속성을 사용할 수 있습니다.
  • 런타임에서 타입 캐스팅이 클래스 인스턴스 타입을 확인하고 해석할 수 있습니다.
  • 디이니셜라이저(Deinitializers) 를 사용하면 클래스 인스턴스가 할당된 모든 리소스를 비울 수 있습니다.
  • 참조 카운팅을 사용하면 클래스 인스턴스에 대한 참조가 두 개 이상 허용됩니다.

 

클래스의 추가적인 기능은 복잡성의 증가를 감수해야 합니다. 일반적으로 더 쉽게 추론할 수 있는 구조체를 선호하고, 꼭 필요할 때만 클래스를 사용합니다. 실제로 데이터 타입을 정의할 때 대부분 구조체와 열겨형을 사용합니다.

 

 

Definition Syntax(구문 정의)

구조체와 클래스는 비슷한 정의 구문을 가지고 있다. 구조체는 struct 키워드를 사용하고, 클래스는 class 키워드를 사용한다. 둘 다 { } 안에 정의를 한다.

struct SomeStructure {
    // structure definition goes here
}
class SomeClass {
    // class definition goes here
}

 

새로운 구조체와 클래스를 정의하면, 새로운 Swift 타입을 정의하는 것이다. Swift 기본 타입에 맞춰 UpperCamelCase를 사용해 이름을 짓는다 (ex. SomeStructure, SomeClass). 프로퍼티와 메소드에는 lowerCalmelCase를 사용한다 (ex. frameRate, incrementCount).

 

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라는 두 프로퍼티를 가집니다. 저장된 프로퍼티는 변수나 상수입니다.  두 개의 프로터티는 Int 타입을 가지고 초기값 0으로 설정되었습니다.

 

비디오를 보여주기 위해 VideoMode 라는 클래스를 정의했습니다. 이 클래스는 4개의 변수 프로퍼티를 가집니다. 첫 번째 resolution은 새로운 Resolution() 구조체 인스턴스에 의해 초기화됩니다. interlaced 는 false, frameRate는 0.0, name은 옵셔널 문자열로 초기화 됩니다. name 프로퍼티는 자동으로 nil 의 기본 값을 가집니다.

 

 

 

Structure and Class Instances(구조체와 클래스 인스턴스)

Resolution 구조체 정의와 VideoMode 클래스 정의는 어떤 형태인지만 설명합니다. 그 자체로는 특정한 Resolution이나 VideoMode를 설명하지 않습니다. (즉 껍데기만 있는 상태입니다. ) 클래스나 구조체의 인스턴스를 만들어 이 둘을 사용합니다.

 

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

구조체와 클래스 모두 새로운 인스턴스를 만들기 위해 이니셜라이져 구문을 사용합니다. 가장 기본적인 이니셜라이저 구문은 이름 뒤에 클래스와 구조체이름 뒤에 `()` 이 옵니다. 이것은 기본값(default value)로 초기화된, 클래스와 구조체를 생성합니다.

 

 

Accessing Properties(프로퍼티 접근)

점 구문 . 를 사용해 인스턴스의 프로퍼티에 접근할 수 있습니다. 

print("The width of someResolution is \(someResolution.width)")
// Prints "The width of someResolution is 0"

예제에서 someResolution.width로 someResolution 인스턴스의 프로퍼티 width에 접근했습니다. 리턴값은 기본값인 0 인 걸 확인할 수 있습니다. 

 

.을 여러번 사용해 하의 속성으로 내려가 VideoMode의 resolution 프로퍼티의 width 프로퍼티에 접근할 수 있습니다.

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

 

또한 변수 프로퍼티에 새로운 값을 할당할 수 있습니다.

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(구조체 타입을 위한 멤버별 이니셜라이져)

모든 구조체는 자동적으로 생성되는 Memberwise 이니셜라이져를 가집니다. 이를 통해 새로운 구조체 인스턴스의 멤버 프로퍼티를 초기화 할 수 있습니다. 새 인스턴스 프로퍼티의 초기값은 아래 예제와 같이 이름으로 memberwise 이니셜라이져에 전달 될 수 있습니다.

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

클래스는 memberwise 이니셜라이져를 사용할 수 없습니다.

 

 

 

Structure and Enumerations Are Value Types(구조체와 열거형은 값 타입)

값(value) 타입은 변수나 상수에 할당되거나, 함수에 전달될 때 값이 복사되는 타입입니다.

 

앞선 챕터에서 값타입은 광범위하게 사용되었습니다. 사실 Swift의 모든 기본 타입(integer, float, Booleans, strings, arrays, dictionaries)은 모두 `값타입`이고, 뒤에서 구조체로써 실행됩니다.

 

Swift 의 모든 구조체와 열거형은 값타입 입니다. 이것은 생성한 구조체 및 열겨형 인스턴스(+ 프로퍼티를 가지는 모든 값타입) 가 코드로 전달 될 때 항상 복사된다는 것을 의미합니다.

 

Array, Dictionary, String 과 같은 표준 라이브러리로 정의된 컬렉션은 최적화를 통해 복사비용을 줄입니다. 이러한 컬렉션은 즉시 복사본을 만드는 대신 요소가 원본 인스턴스와 복사본 사이에 저장되는 메모리를 공유합니다. 만약 컬렉션 복사본 중 하나가 수정되며, 수정 직전에 복사가 됩니다. 코드에 표시되는 동작은 항상 복사가 즉시 수행되는 것과 같습니다.

 

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

위 예제는 hd라는 상수를 선언하고 Resolution이라는 인스턴스로 설정합니다.

그런 다음 cinema라는 변수를 선언하고 현재 hd 값으로 설정합니다. Resolution은 구조체이기 때문에 (이미 만들어진 인스턴스의 복사본이라는 뜻), 새로운 복사본이 cinema에 할당됩니다. hd와 cinema가 똑같은 width와 height를 가져도 두 인스턴스는 전혀 다릅니다.

 

cinema의 width를 수정해보겠습니다.

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

 

hd 의 width 프로퍼티는 그대로 입니다.

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

 

cinema가 hd의 값을 받았을 때, hd 에 저장된 값은 새로운 cinema 인스턴스에 복사가 됩니다. 결좌적으로 동일한 숫자 값을 가지는 완전히 다른 두개의 인스턴스입니다. 분리된 인스턴스이기 때문에 cinema의 width 값을 바꿔도 hd의 width값에는 영향이 없습니다.

출처 : Swift 공식 문서

 

열겨형에도 동일하게 적용됩니다.

enum CompassPoint {
    case north, south, east, 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"

 

 

 

Classes Are Reference Types(클래스는 참조 타입)

값타입과 자르게 참조 타입은 변수 또는 상수에 할당되거나 함수에 전달될 때 복사되지 않습니다. 복사보다는 동일한 기존 인스턴스를 사용합니다.

 

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

예제는 tenEighty라는 새로운 상수를 선언하고, VideoMode 클래스로 설정해주었습니다. 

 

그다음 tenEighty는 새로운 상수 alsoTenEighty에 할당되었고, framRate가 수정되었습니다.

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

 

클래스는  참조 타입이기 때문에 tenEighty와 alsoTenEighty는 사실 동일한 VideoMode 인스턴스를 참조합니다. 사실 상 하나의 인스턴스에 대해 두개의 서로 다른 이름을 가질 뿐입니다.

출처 : Swift 공식 문서

tenEigthy의 framRate프로퍼티를 확인해보면 새로운 frameRate인 30.0 입니다.

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

 

이 예제는 얼마나 참조 타입이 추론하기 어려운지 보여줍니다. 만약 tenEighty와 alsoTenEight가 프로그래밍 코드 상에서 멀리 떨어져 있다면, videMode가 바뀌는 모든 방법을 찾는 것은 어려울 것입니다. tenEighty를 사용할 때 코드가 alsoTenEighty를 사용하고 있다는 걸 생각해야 하며 그 반대도 마찬가지입니다. 반면, 동일한 값에 대해 접근하는 모든 코드가 소스 파일내에서 가깝기 때문에 값타입은 추론하기 더 쉽습니다.

 

tenEighty와 alsoTenEighty가 변수가 아닌 상수로 선언되어 있습니다. 그럼에도 tenEight.framRate 와 alsoTenEighty.frameRate 라는 프로퍼티를 바꿀 수 있는데, tenEighty와 alsoTenEighty 상수의 값은 실제로 변하지 않습니다.  tenEighty와 alsoTenEighty는 videoMode 인스턴스에 저장되어 있지않습니다. 대신에 videoMode를 참조하고 있습니다. 변경된 것은 기본 VideoMode의 frameRate 프로퍼티지, VideoMode 에 대한 상수 참조 값이 아닙니다.

 

 

Identity Operators(ID 연산자)

클래스가 참조 타입이기 때문에, 여러개의 상수와 변수가 하나 클래스의 똑같은 단일 인스턴스를 참조하는 것이 가능합니다.

 

상수와 변수가 클래스의 똑같은 인스턴스를 참조하고 있는지 알아내는데 가끔씩 유용할 수 있습니다. Swift는 두 가지 연산자를 제공합니다

  • Identical to (===) : 동일
  • Not identical to(!==) : 동일하지 않음

두 연산자를 사용해 상수 또는 변수가 동일한 단일 인스턴스를 잠조하는지 확인합니다.

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

=== 이 ==와 같지 않다는 것을 명심해야 합니다. ===(동일하다) 는 클래스 타입의 두 상수 혹은 변수가 똑같은 단일 인스턴스를 참조하고 있다는 것을 의미합니다. == 는 두 인스턴스의 값이 같다는 것을 의미합니다. 

 

사용자 정의 구조체와 클래스를 정의할 때, 두 인스턴스가 동일하다고 할 수 있는지 결정하는 것은 사용자의 책입입니다. 

 

 

똑같은 값을 참조하는 지 확인 하는 것 ===

값이 똑같은지 비교하는 것 ==

 

 

Pointers(포인터)

C, C++, Objective-C를 경험했더라면, 이 언어들은 메모리의 주소를 참조하는 포인터를 사용합니다. 어떤 참조 타입을 참조하는 Swift 의 상수 혹은 변수는, C의 포인터와 비슷합니다. 하지만 메모리 주소에 직접 접근하는 포인터가 아니며, 참조하고 있다는 것을 나타내기 위해 *을 사용할 필요가 없습니다. 대신에 이 참조는 Swift의 다른 상수 혹은 변수처럼 정의됩니다. 표준 라이브러리는 포인터와 직접 상호작용해야할 때 사용할 수 있는 포인터와 버퍼 타입을 제공합니다. 자세한 사항은 Manual Memory Management 참조

 

 

 

 

 

 

 

 

이번 장을 간단하게 정리하면

  • 특별한 경우가 아니면 구조체를 사용한다.
  • . 을 사용해 프로퍼티에 접근한다
  • 구조체(와 열거형은)  값 타입이다 -> 인스턴스가 복사된다
  • 클래스는 참조 타입이다.

이 정도 인 것 같습니다.