[최종 수정일 : 2018.08.27]

원문 : 애플 개발 문서 Swift 4.2 Language Guide - Structures and Classes

구조체와 클래스(Structures and Classes)

구조체(structures)와 클래스(classes)는 범용적인 목적(general-purpose)이며, 프로그램의 코드 블록(blocks)를 만드는데 유연한 구조입니다. 구조체와 클래스에 기능을 추가하기 위해 상수, 변수 함수와 같은 문법을 사용하여 프로퍼티(properties)와 메소드(methods)를 정의합니다.

다른 프로그래밍 언어와 다르게, Swift는 사용자정의 구조체나 클래스에 대해 인터페이스와 구현 파일을 구분해서 만드는게 필요하지 않습니다. Swift에서, 하나의 파일 안에서 구조체나 클래스를 정의하고, 클래스나 구조체에 대한 외부 인터페이스는 다른 코드에서 사용할수 있도록 자동으로 만듭니다.

주의
클래스의 인스턴스는 전통적으로 객체(object)라고 합니다. 하지만, Swift 구조체와 클래스는 다른 언어에 비해 기능적으로 훨씬 더 가깝고(closer), 이번 챕터(chapter)에서 클래스나 구조체 타입의 인스턴스에 적용하는 기능을 설명합니다. 이 때문에, 좀 더 일반적인 용어인 인스턴스(instance)가 사용됩니다.

구조체와 클래스 비교하기(Comparing Structures and Classes)

Swift에서 구조체와 클래스는 공통정인 점이 많이 있습니다. 다음은 둘다 할수 있는 것들입니다.

  • 값을 저장하기 위한 프로퍼티 정의
  • 기능을 제공하기 위한 메소드 정의
  • 서브스크립트(subscript) 문법을사용하여 값을 접근하는 것을 제공하기 위한 서브스크립트 정의
  • 초기 상태로 설정하기 위한 초기화 정의
  • 기본 적은 구현을 넘어서는 기능을 확장하기 위한 확장(extended)
  • 특정 종류의 표준 기능을 제공하는 프로토콜을 준수(conform)

더 자세한 정보는, 프로퍼티(Properties)메소드(Methods)서브스크립트(Subscripts)초기화(Initialization)확장(Extensions)프로토콜(Protocols)를 보세요.

클래스는 가능하지만 구조체는 할 수 없는 것:

  • 다른 특성(characteristics)을 상속받기 위해 하나의 클래스를 상속하는게 가능함.
  • 실행중에 클래스 인스턴스 타입을 확인하고 해석(interpret)하기 위해 타입을 변경(Type casting) 가능
  • 클래스의 인스턴스가 할당한 모든 리소스를 메모리에서 해제하기 위한 해제(Deinitializer) 가능
  • 클래스 인스턴스에 하나 이상의 참조가 가능한 참조 개수(Referenc counting) 허용

더 자세한 정보는, 상속(Inheritance)타입 변환(Type Casting)해제(Deinitalization)자동 참조 개수(Automatic Reference Counting)을 보세요.

복잡성이 증가함에 따라 클래스가 지원하는 것이 추가 기능은 다음과 같습니다. 일반적인 지침서(guideline) 처럼, 쉽게 사용하기 위해 구조체와 열거형을 선호하고, 적절하거나 필요한 경우에 클래스를 사용합니다. 실제로(in preactics), 대부분의 사용자정의 데이터 타입을 구조체와 열거형으로 정의하게 될 것을 의미합니다. 더 자세한 비교를 위해서는, 구조체와 클래스 사이에서 선택하기(Choosing Between Structures and Classes)를 보세요.

정의 문법(Definition Syntax)

구조체와 클래스는 비슷한 정의 문법을 가집니다. 구조체는 struct 키워드로, 클래스는 class 키워드로 표시합니다. 둘다 한쌍의 중괄호({})로 전체를 정의합니다.

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

주의
새로운 구조체나 클래스를 정의할때마다, 새로운 Swift 타입을 정의합니다. 표준 Swift 타입(String, Int, Bool)의 대문자와 같도록 UpperCamelCase 타입 이름(SomeStructure와 SomeClass) 을 사용합니다. 프로퍼티와 메소드는 타입 이름과 다르게 lowerCamelCase 이름(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를 가지고 있습니다. 저장 프로퍼티(Stored properties)는 상수나 변수는 번들로 묶어 구조체나 클래스의 일부로 저장됩니다. 이러한 두개의 프로퍼티는 초기 정수형 값 0설정으로 부터 Int 타입으로 추론됩니다.

위의 예제는 비디오 화면에 대한 특정 비디오 모드를 설명하기 위해, 새 클래스 VideoMode 로 정의됩니다. 클래스는 4개의 변수 저장 프로퍼티가 있습니다. 첫번째, Resolution의 프로퍼티 타입으로 추론되는 resolution은 새로운 Resolution 구조체 인스턴스로 초기화 됩니다. 다른 3개의 프로퍼티는, 새로운 VideoMode 인스턴스에서 interlaced 는 false(noninterlaced 비디오를 의미)로, 프레임 재생 비율은 0.0이고, name은 옵셔널 String값으로 초기화됩니다. name 프로퍼티는 옵셔널 타입이기 때문에, 자동적으로 nil값 또는 name 값이 없음이 주어집니다.

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

Resolution 구조체는 정의와 VideoMode클래스 정의는 단지 어떤 Resolution또는 VideoMode가 무엇인지를 설명합니다. 그것들은 특정 해상도나 비디오 모드를 설명하지 않습니다. 그렇게 하려면 구조체나 클래스의 인스턴스를 만들어야 합니다.

구조체와 클래스 모두 인스턴스를 생성하는 문법은 매우 간단합니다.

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

구조체와 클래스 모두 새로운 인스턴스에 대해 초기화 문법을 사용합니다. 초기화 문법의 가장 간단한 형식은 Resolution() 또는 VideoMode()처럼, 클래스나 구조체의 이름 다음에 빈 괄호를 붙이는 것입니다. 이러게 만드는 클래스나 구조체의 새로운 인스턴스는 기본값으로 모든 프로퍼티가 초기화 됩니다. 클래스와 구조체 초기화에 대해 더 자세한 설명은 초기화(Initialization)에 설명되어 있습니다.

프로퍼티에 접근하기(Accessing Properties)

점 문법(dot syntax)을 사용하여 인스턴스의 프로퍼티에 접근할수 있습니다. 점 문법(dot syntax)에서, 인스턴스 이름 바로 뒤에 프로퍼티 이름을 작성하며, 공백없이 마침표(period)(.)로 구분됩니다.

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

예제에서, someResolution.width는 someResolution의 width프로퍼티를 참조하고, 기본 초기값 0을 반환합니다.

VideoMode의 resolution프로퍼티에서 width프로퍼티처럼 하위프로퍼티(subproperties)처럼 아래로(drill down) 내려갈 수 있습니다.

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

또한, 변수 프로퍼티에 새로운 값을 할당하기 위해 점 문법(dot syntax)를 사용할 수 있습니다.

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 initializer)가 생성됩니다. 새로운 인스턴스의 프로퍼티에 대한 초기 값은 이름으로 멤버단위 초기화에 의해 전달됩니다.

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

구조체와 다르게. 클래스 인스턴스는 기본 멤버단위 초기화를 제공하지 않습니다. 초기화는 초기화(Initialization)에서 더 자세히 설명되어 있습니다.

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

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

실제로 이전 챕터(chapter)에서 값 타입을 광범위하게(extensively) 사용했었습니다. 사실상, Swift의 모든 기본 타입-정수형, 부동소수점 숫자, Boolean, 문자열, 배열과 딕셔너리-들은 값 타입(value types) 이고, 구조체 처럼 구현 화면(scenes) 뒤에서 구현됩니다.

Swift에서 모든 구조체와 열거형은 값 타입입니다. 이는 모든 구조체와 열거형 인스턴스가 코드에서 전달할때 항상 복사되어 생성(프로퍼티에 있는 모든 값 타입)되는 것을 의미합니다.

주의
컬렉션(Collections)은 배열, 딕셔너리, 문자열 처럼 표준 라이브러리에 의해 정의되며, 복사하는 성능 비용을 줄이기 위해 최적화를 사용합니다. 즉시 복사본을 만드는 대신에, 컬렉션은 옵셔널 인스턴스와 복사본 사이에 저장된 요소들을 메모리에 공유합니다. 컬렉션의 복사본중 하나가 수정되면, 그 요소들은 수정되기 전에 복사됩니다. 이 동작은 코드에서 항상 복사본이 즉시 생성되는 것으로 볼수 있습니다.

이전 에제로 부터 Resolution구조체를 사용하는 예제를 생각해보세요.

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

Resolution인스턴스를 풀 HD 비디오(1920 픽셀 폭과 1080 픽셀 높이)의 가로 세로로 초기화되도록 설정하고 hd 상수로 선언하는 예제 입니다.

cinema 변수를 선언하고 hd의 현재 값을 설정합니다. Resolution이 구조체 이기 때문에, 기존 인스턴스의 복사본이 만들어지고, 새 복사본은 cinema가 할당됩니다. hd와 cinema는 이제 같은 넓이와 높이를 가지며, 둘은 완전히 다른 인스턴스 입니다.

다음으로, cinema의 width 프로퍼티는 디지털 영화 영사기(2048 픽셀 폭과 1080 픽셀 높이)에 사용되는 약간 더 넓은 대한 2K 표준 넓이가 되도록 수정(amended) 됩니다.

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에 저장된 값(values)은 새로운 cinema 인스턴스로 복사되어 저장됩니다. 결과적으로 동일한 숫자 값을 가지는 두개의 인스턴스로 완전히 구분됩니다. 하지만, 아래 그림에서 보는 것 처럼, 그것들은 별도의 인스턴스이기 때문에, cinema의 width가 2018로 설정되는 것이 hd에 저장된 width에 영향을 미치지 않습니다.

열거형에 대해서도 동일하게 적용합니다.

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"

rememberedDirection이 currentDirection의 값이 할당될때, 실제로 그 값의 복사본이 설정됩니다. currentDirection의 값을 변경할때 rememberedDirection에 저장된 원래 값의 복사본은 아무런 영향이 없습니다.

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

값 타입과 다르게, 참조 타입(reference types)은 변수나 상수에 할당되거나 함수에 전달될때 복사되지 않습니다(not copied). 복사하는 대신에, 기존 인스턴스에 대한 참조가 사용됩니다.

다음은 위에서 정의했던 VideoMode 클래스를 사용한 예제입니다.

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

하나의 새로운 상수 tenEighty를 선언하고 VideoMode클래스의 새로운 인스턴스를 참조하도록 설정한 예제입니다. 비디오 모드는 이전의 1920 x 1080 HD 해상도의 복사본이 할당됩니다. 그것은 interlaced되도록 설정되었고, "1080i" 이름을 가집니다. 마지막으로, 초당 프레임 비율을 25.0으로 설정합니다.

다음으로, tenEighty를 새로운 상수 alsoTenEighty로 할당하고, alsoTenEighty의 프레임 비율은 수정됩니다.

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

클래스는 참조타입이기 때문에, tenEighty와 alsoTenEighty 모두 같은(same) VideoMode 인스턴스를 참조합니다. 실제로(Effectively), 아래 그림에서 보는 것처럼, 동일한 하나의 인스턴스에 대해 두개의 다른 이름을 가질뿐입니다.

tenEighty의 frameRate프로퍼티가 VideoMode인스턴스에서 새로운 프레임 비율 30.0을 정확하게 보고 있는지 확인하세요.

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

참조타입이 추론하기 어려울 수 있는 것을 보여주는 예제입니다. tenEighty와 alsoTenEighty가 프로그램 코드에서 멀리 떨여져 있는 경우에, 비디오 모드가 변경된 모든 방법을 찾기 어려울수 있습니다. tenEighty를 사용할때마다, alsoTenEighty를 사용하는 코드에 대해서도 생각해야하고, 그 반대도 있습니다. 이와는 대조적으로, 값 타입은 같은 값과 상호작용하는 모든 코드가 소스파일에서 가깝기 때문에 추론하기 쉽습니다.

tenEighty와 alsoTenEighty가 변수가 아닌 상수(constants)로 선언되는 것을 주의합니다. 하지만, tenEighty와 alsoTenEighty상수의 값이 실제 변경되는 것이 아니기 때문에, tenEighty.frameRate와 alsoTenEighty.frameRate를 여전히 변경할수 있습니다. tenEighty과 alsoTenEighty는 VideoMode 인스턴스를 저장하지 않습니다. - 대신에, VideoMode 인스턴스를 참조(refer)합니다. VideoMode 기반의 frameRate프로퍼티는 변경되며, VideoMode를 참조하는 상수의 값은 변경되지 않습니다.

식별 연산자(Identity Operators)

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

가끔식 두개의 상수나 변수가 클래스의 동일한 인스턴스를 참조하는지 찾는 것이 유용할 때가 있습니다. 이러한 기능을 사용하기 위해, Swift는 두개의 식별 연산자를 제공합니다.

  • === 동일한 것을 식별(Identical)
  • !== 동일하지 않는 것을 식별(Not identical)

이 연산자들은 두개의 상수나 변수가 동일한 하나의 인스턴스를 참조하는 것을 확인하기 위해 사용합니다.

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

똑같다(identical to) (3개의 같음(equal) 기호로 표현 ===)는 같다(equal to)(2개의 같음(equal) 기호로 표현, ==)와는 같은 의미가 아닙니다. 똑같다(identical to)는 클래스 타입의 두개의 상수나 변수가 정확히 같은 클래스 인스턴스를 참조하는 것을 의미합니다. 동등하다(equal)의 적절한 의미로써, 같다(equal to)는 두개의 인스턴스가 값이 같거나 같은것으로 간주되는 것을 의미하며, 타입 설계에서 정의됩니다.

자신만의 사용자정의 구조체와 클래스를 정의할때, 두 인스턴스가 같은 것으로 여기도록 결정하는 것은 사용자의 책임입니다. 같다(equal to)와 같지 않다(not equal to) 연산자의 구현을 정의하는 과정은 동등 연산자(Equivaience Operators)에서 설명되어 있습니다.

포인터(Pointers)

C, C++, Objective-C 경험이 있다면, 이러한 언어가 메모리 주소를 참조하기 위해 포인터(pointers)를 사용하는 것을 알고 있을 것입니다. Swift 상수나 변수가 일부 참조 타입의 인스턴스를 참조하는 것이 C에서의 포인터와 비슷하지만, 메모리 주소를 포인터로 직접 사용하지 않고, 참조 생성을 나타내는 별표(asterisk *)를 작성할 필요가 없습니다. 대신에, Swift에서 이러한 참조는 다른 상수나 변수처럼 정의됩니다. 표준 라이브러리는 직접 포인터와 상호작용할 필요가 있을대 사용할 수 있는 포인터(pointer)와 버퍼 타입(buffer types)을 제공합니다. - 수동으로 메모리 관리하기(Manual Memory Management)를 보세요.

'Swift > Language Guide' 카테고리의 다른 글

Methods  (0) 2018.08.30
Properties  (0) 2018.08.30
Structures and Classes  (0) 2018.08.30
Enumerations  (0) 2018.08.30
Closures  (0) 2018.08.30
Functions  (0) 2018.08.30
Posted by 까칠코더