Swift-Language Guide 5.7 / Structures and Classes (야매 번역 + 정리)

Newon·2022년 8월 11일
1

Swift-Language Guide 5.7

목록 보기
7/7
post-thumbnail

초록

구조체(struct)클래스(class)는 범용적인 목적을 위해, 프로그래머의 코드를 유연하게 블록처럼 구성하는 것을 의미합니다. 프로그래머는 속성(properties)메소드(methods) 기존에 사용했던 문법과 동일하게 변수, 상수, 함수 문법을 사용하여 구조체나 클래스 내부에 정의할 수 있습니다.

다른 프로그래밍 언어들과 달리, 스위프트는 구조체와 클래스를 위해 별도의 인터페이스나 구현을 위한 별도의 파일을 요구하지 않습니다. 스위프트에서는 프로그래머가 구조체나 클래스를 하나의 파일 안에 정의하고나면 다른 코드에서도 사용할 수 있게끔 자동으로 외부 인터페이스를 만들어 제공해줍니다.


Note : 클래스의 인스턴스를 통상 오브젝트(object) 로 칭합니다. 하지만 스위프트의 구조체와 클래스는 조금 더 기능적인 면모 ( much closer in functionality ) 가 부각되며, 이번 챕터 역시 클래스나 구조체의 인스턴스에 적용하는 기능들을 묘사할 예정입니다. 이에 따라 instance 라는 용어도 사용할 것 입니다.


블로그장 : 사견으로, 오브젝트와 인스턴스의 차이를 굳이 묘사하자면 (물론 말이 굉장히 많겠지만) 오브젝트를 객체, 즉 설계도로 보고 인스턴스를 객체에 데이터를 넣어서 메모리로 적재한 것으로 이해하고 있습니다. 다만 이런 구분 자체를 굳이 엄격하게 지을 필요는 없다고 생각되며, 구분 행위가 기능보다는 철학적인 면모를 더 내포한다고 생각합니다. 앞으로 나오는 인스턴스는 구분지을 수 있는 하나의 종류 혹은 메모리에 적재된 데이터 묶음 를 혼용해서 사용하는 것으로 이해하고 야매 번역하였음을 미리 알려드립니다. 관련된 StackOverFlow 를 첨부합니다.

* 볼드체만 해석 : 인스턴스와 클래스, 그리고 오브젝트는 같은 의미이며 종종 상호교환적으로 사용됩니다.


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

스위프트에서 구조체와 클래스는 유사한 부분이 많습니다.

  • 속성을 정의하여 값을 저장할 수 있습니다.
  • 메소드를 정의하여 함수를 제공할 수 있습니다.
  • 서브스크립트를 정의하여 서브스크립트 문법으로
    구조체나 클래스의 값에 접근할 수 있습니다. ex: array[1]
  • 초기화(initializers) 를 정의하여 초기화 상태를 설정할 수 있습니다.
  • 기본 구현을 넘어 구조체나 클래스의 기능(함수) 를 확장할 수 있습니다.
  • 프로토콜을 준수하여 특정한 표준 함수를 제공하도록 할 수 있습니다.

클래스는 구조체는 할 수 없는 추가적인 능력들을 갖고 있습니다.

  • 상속이 가능하여, 하나의 클래스의 특징들을 다른 클래스로 상속할 수 있습니다.
  • 타입 캐스팅이 가능하여, 프로그래머가 런타임 때 클래스 인스턴스의 자료형을 확인하고 해석할 수 있습니다.
  • 초기화해제 Deinitializers 가 가능하여 인스턴스된 클래스 내 할당된 리소스를 메모리로부터 해제할 수 있습니다.
  • 참조 카운트 Reference counting 으로 클래스 인스턴스에 하나 이상의 참조를 허용합니다.

클래스가 추가적인 기능들로 지원하게 되면서 복잡도에 대한 비용이 증가하게 되었습니다.

구조체는 사용이 간단하기에 구조체를 사용하는 것을 권하며 클래스는 클래스의 사용이 적절하거나 필수적일 때만 사용할 것을 권합니다. 이는 프로그래머가 구조체나 열거형으로 커스텀 데이터 자료형을 정의하기를 추천드리는 것 입니다.
구조체와 클래스 중 하나를 선택하기


블로그장 : 클래스와 구조체의 선택에 대한 주석

클래스는 복사 시 주소를 참조하므로, 복사된 곳에서 클래스 속성의 변경이 원본에도 영향을 미치게 되어 전체적인 구조가 복잡해지게 됩니다.

class Cat {
	eye: String
}

var cheese = Cat(eye: "Gold")
var black = a
black.eye = "Black"

print(cheese.eye, black.eye)
// 출력 : Black, Black

반면 구조체는 복사 시 값만 전달할 뿐, 복사된 곳과 원본 사이에 연결성은 하나도 없습니다.

struct Cat {
	eye: String
}

var cheese = Cat(eye: "Gold")
var black = a
black.eye = "Black"

print(cheese.eye, black.eye)
// 출력 : Gold, Black

이를 쉽게 표현하기로는,
구조체는 복사본에게 원본을 사진을 인화하여 새롭게 준 것이고
클래스는 복사본에게 컴퓨터 내 사진 폴더 접속권을 준 것이라고 할 수 있습니다.

구조체는 인화한 사진을 주었으므로, 복사본에 낙서를 해도 영향을 안 받는 반면
클래스는 접속권을 준 사진 폴더 접속권을 주었기에, 사진에 낙서하거나 삭제하면 영향을 받습니다. Angela iOS 부트캠프 강의 (유데미)


또한 이외에도, 메모리에서 클래스의 저장공간은 인데 반해 구조체의 저장공간은 스택이 됩니다. 스택에서의 저장은 가장 마지막 데이터의 위에 바로 저장하면 되므로 저장 속도가 굉장히 빠르지만, 힙에서의 저장은 동적으로 메모리의 빈 공간을 찾아서 저장해야 하므로 힙을 전체 탐색 -> 데이터 저장이라는 하나의 단계가 더 포함됩니다.

즉, 구조체는 저장 시 메모리의 빈 공간 탐색 없이 찾아도 되지만
클래스는 저장 시 메모리의 빈 공간을 탐색 후 저장해야 하므로
속도면에서 더 느릴 수 밖에 없습니다.

메모리 해제에도 스택은 사용이 끝나면 자연스럽게 해제되는 반면,
클래스는 클래스를 참조하고 있는 인스턴스들이 모두 해제되어야 클래스도 해제되므로
속도, 안정성 면에서 더욱 불안전 할 수 밖에 없습니다.

그렇기에 스위프트에서는 매우 적합한 사례거나 반드시 필요한 사례가 아니라면 구조체를 사용하는 것을 권고하는 것입니다. 앨런 Swift문법 마스터 스쿨


애플의 문서 구조체와 클래스 중 하나를 선택하기 에서 구조체의 사용을
"Using structures makes it easier to reason about a portion of your code without needing to consider the whole state of your app." 로 표현한 것도, 이와 유사한 의미입니다.

구조체는 스택에 저장되는 인스턴스이자 값 타입 이므로, 구조체가 다른 구조체에 영향을 주지 않는 반면 클래스는 다른 클래스에 영향을 주기 때문에 평범한 종류의 데이터, common kinds of data 라면 구조체를 사용하는 것을 권하는 것 입니다.

애플에서 언급하는 클래스를 사용해야 하는 경우는

  • Object C 로 작성된 API 를 사용해서 데이터를 관리해야 하는 경우,
    API 가 상속받을 클래스를 요구하는 경우가 많으므로 클래스 사용 권장
  • 메모리 관점에서 두 인스턴스가 동일한 지 확인해야 하는 경우,
    즉 파일 관리, 네트워크, 하드웨어 기능 사용 등의 경우 클래스 사용 권장,
    대표적인 예시로 사운드와 관련된 AVFoundationAVAudioPlayer 로,
    사운드를 담당하는 객체가 사운드를 할당받을 때 마다 새로운 인스턴스를
    생성해버리면 오디오가 겹쳐서 들리게 됩니다.

정의 문법 (Definition Syntax)

구조체와 클래스는 정의하는 문법이 비슷합니다. '

struct SomeStructure {
    // 구조체 정의
}
class SomeClass {
	// 클래스 정의
}


// 실제 예시

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

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

Note : 프로그래머가 새로운 구조체나 클래스를 정의한다는 것은, 새로운 스위프트 자료형을 정의하는 것과 같습니다.

따라서 파스칼 케이스에 따라 대문자로 시작하고, 단어단위로 대문자를 붙여서 사용함으로써 기존의 스위프트 자료형들 (String, Int, Bool ...) 과 이름짓는 방식을 맞추는 것을 권해드립니다.

또한 속성이나 메소드에는 카멜 케이스에 따라 소문자로 시작하고, 단어단위로 대문자를 붙여서 자료형과 구분하는 것을 권합니다.


이때 속성들에는 기본값을 주거나, 안 줄 수도 있으며
기본값을 주지 않은 속성들은 초기화 시 반드시 값을 지정해야 합니다.
이때 자료형을 옵셔널로 지정하면 초기화 시 자동으로 nil 값이 적용됩니다.


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

상단의 예시로 든 , Resolution 구조체와 VideoMode 클래스는 각각 ResolutionVideoMode 가 어떤 모습이어야 하는지 설명한 것에 불과합니다. 실제 모습으로 적용하고, 데이터로써 사용하려면 구조체나 클래스의 인스턴스를 만들어야 합니다.

구조체나 클래스를 인스턴스하는 문법은 다음과 같습니다.

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

구조체와 클래스는 구조체(), 클래스() 형태로 인스턴스할 수 있으며, 자세한 사항은 Initialization 에서 다룹니다.

속성에 접근하기 (Accessing Properties)

인스턴스된 클래스나 구조체 바로 뒤에 . 을 사용하므로써 속성에 접근할 수 있습니다. 이때 속성이 또 다른 구조체나 클래스라면 다시 한번 . 를 사용하므로써 속성에 재접근할 수도 있습니다.

print("어떤 해상도의 넓이는 \(someResolution.width)")
// 출력: "어떤 해상도의 넓이는 0"

print("어떤 비디오의 넓이는 \(someVideoMode.resolution.width)")
// 출력: "어떤 비디오의 넓이는 is 0"

someVideoMode.resolution.width = 1280
print("어떤 비디오의 넓이는 이제 \(someVideoMode.resolution.width)")
// 출력: "어떤 비디오의 넓이는 이제 1280"

구조체 자료형의 똑똑한 속성 초기화 (Memberwise Initializers for Structure Type)

구조체는 클래스와 달리 구조체 내 초기화에 대한 내용이 안 적혀있다면 자동으로 memberwise initializer 를 생성합니다. 자세한 사항은 Initialization 에서 다룹니다.

구조체와 열거형은 값 타입이다. (Structures and Enumerations Are Value Types)

값 타입 (Value type) 은 변수나 상수에 할당하거나 함수에 인자로 전달될 때 그 값을 복사해서 주는 자료형을 의미한다.

사실 이전의 장들에서 당신은 여러번 값 타입들을 사용해왔습니다. 스위프트의 정수, 소수와 같은 숫자, Boolean, 스트링, 배열, 딕셔너리 등등은 모두 값 타입이고, 컴파일 뒷단에서는 구조체로써 적용되고 있습니다.

스위프트에서 모든 구조체와 열거형은 값타입입니다. 이 뜻은 어떤 자료형이나 열거형들을 인스턴스하고, 다른 코드에 전달할 때 언제나 그 값들을 복사해서 주는 것을 의미합니다.


Note : 표준 라이브러리로 정의되어 있는 배열, 딕셔너리, 스트링과 같은 컬렉션들은 복사를 위한 비용(cost) 를 줄이기 위해 최적화가 적용되어 있습니다. 이 컬렉션들은 바로 복사본을 만드는 것이 아니라, 각각의 원소들에 대해 동일한 메모리(인스턴스) 를 공유합니다. 이때 컬렉션 복사본들 중 하나가 수정된다면 모든 원소들이 수정 바로 전에 새롭게 값을 복사하고, 새롭게 메모리에 적재됩니다. 이 작업은 코드 상으로 항상 복사 이전에 수행됩니다.

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

위의 예시 코드에서 Resolution 는 구조체이므로 즉시 복사본 인스턴스가 만들어지고, 이렇게 만들어진 새로운 복사본이 cinema 에 할당됩니다. 비록 hdcinema 가 똑같은 width, 똑같은 height 를 가졌더라도 컴퓨터 내부적으로 둘은 완벽하게 다른 인스턴스입니다.

이 예시를 통해 확인할 수 있습니다.

cinema.width = 2048

print("시네마는 이제 \(cinema.width) 픽셀입니다.")
// 출력 : "시네마는 이제 2048 픽셀입니다."

print("hd 는 아직 \(hd.width) 픽셀입니다.")
// 출력 : "hd 는 아직 1920 픽셀입니다."

도식화하면 이와 같습니다.

cinemahd 값을 할당받았기 때문에, hd 는 자신과 동일한 값을 가진 새로운 인스턴스를 생성하여 cinema 에게 할당하였습니다. 이렇게 생성된 hdcinema 는 같은 값을 가진 것 처럼 보여도, 서로 다른 인스턴스이기때문에 cinema 에서 값 변경이 hd 에 영향을 미치지 않는 것 입니다.


열거형에서의 예시입니다.

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

print("현재 방향은 \(currentDirection)")
print("기억하고 있는 방향은 \(rememberedDirection)")
// 출력 : "현재 방향은 north"
// 출력 : "기억하고 있는 방향은 west"

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

참조타입은 변수나 상수에 할당되거나, 혹은 함수에 전달될 때 복사되지 않습니다. 복사 대신 현재 존재하는 인스턴스의 주소가 할당되거나, 전달됩니다.

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


let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

print(tenEighty.frameRate)
print(alsoTenEighty.frameRate)

// 출력 : 30
// 출력 : 30

클래스는 참조타입이기 때문에, tenEightyalsoTenEighty 는 사싱 같은 VideoMode 인스턴스를 참조하고 있습니다. 결과적으로, 둘은 다른 이름으로 불리는 하나의 인스턴스가 되었으며, 이를 표현하면 다음과 같습니다.

이때문에, 프로그래머는 tenEighty 를 사용할 때마다 alsoTenEighty 를 고려해서 사용해야 합니다. 이와 반대로, 값 타입은 소스 파일 내 다른 값들과 상호작용이 막혀있기 때문에, 더 쉽게 사용할 수 있게 됩니다.

한편 tenEightyalsoTenEightylet 으로 선언되었음에도, 그 속성을 바꿀 수 있는 것도 주목해야 합니다. tenEightyalsoTenEighty 가 상수로 저장하고 있는 것은 VideoMode 의 인스턴스, 그 자체가 아니라 VideMode 의 주소 를 저장하고 있는 것 입니다. 한편 VideoMode 의 속성은 let 이 아니므로, 이를 변경할 수 있게 되는 것 입니다.


식별 연산자 (Identity Operators)

클래스는 참조 타입이기 때문에, 다수의 상수와 변수가 하나의 클래스 인스턴스를 참조하는 것이 가능합니다. (구조체와 열겨형은 불가능합니다. 왜냐하면 구조체와 열거형은 언제나 새로운 인스턴스를 만들어서 할당하기 때문입니다.)

때때로 두 상수나 변수가 동일한 클래스 인스턴스를 참조하는지 확인하는 것이 유용할 때가 있는데, 이를 지원하기 위해 스위프트에서는 다음과 같은 연산자를 사용할 수 있습니다.

  • 동일함, ===
  • 동일하지 않음, !==

동일함은 두 상수나 변수가 동일한 클래스 인스턴스를 참조하고 있다는 것을 의미합니다. 여기서 동일이란 자료형의 제작자에 의해 두 인스턴스가 동일하거나, 동등한 값을 갖고 있음을 의미합니다.


포인터 (Pointers)

만약 당신이 C, C++, 혹은 Objective-C 경험이 있다면 이 언어들이 메모리의 주소를 언급하는 pointers 라는 개념을 사용하는 것을 알고 계실겁니다.

스위프트의 참조타입의 인스턴스를 갖고있는 상수나 변수는 C 의 포인터 개념과 유사하나, 그 상수나 변수 자체가 포인터임을 의미하진 않으며, 참조를 만들기 위해 * 작성을 필요로 하지 않습니다.

대신 이 참조들은 스위프트의 여느 다른 상수나 변수처럼 정의됩니다. 표준 라이브러리는 포인터와 버퍼 타입을 제공하고 있으며, 직접 포인터로 상호작용 해야한다면 Manual Memory Management 에서 확인할 수 있습니다.


참고

StackOverFlow - What is the difference between an Instance and an Object?

Angela iOS 부트캠프 강의 (유데미) : Advanced Swift Programming - Classes, Inheritance & Advanced Optionals

앨런 Swift문법 마스터 스쿨 : 클래스(Class)와 구조체(Struct)

profile
나만 고양이 없어

0개의 댓글