Swift – 구조체 클래스

오늘의 주제

  • 구조체
  • 클래스

안녕하세요, 야곰입니다. 

지난 포스팅에서는 스위프트의 함수와 함께 스위프트에서 제공하는 컬렉션 타입에 대해 알아봤습니다.

2017/01/23 - [Swift] - Swift란 어떤 언어인가?
2017/01/25 - [Swift] - Swift 기초문법 - 변수, 상수, 기초 데이터 타입
2017/02/06 - [Swift] - Swift - 함수, 콜렉션 타입

이번에는 스위프트의 구조체와 클래스에 대해 알아보겠습니다 🙂

구조체와 클래스

구조체와 클래스는 프로그래머가 데이터를 용도에 맞게 묶어 표현하고자 할 때 용이합니다.
구조체와 클래스는 프로퍼티와 메서드를 사용하여 구조화된 데이터와 기능을 가질 수 있습니다. 하나의 새로운 사용자정의 데이터 타입을 만들어 주는 것입니다. 

객체지향 프로그래밍 패러다임을 알고 있다면 클래스라는 용어를 들어봤을 겁니다.

객체지향 프로그래밍 패러다임이 아니더라도 데이터를 구조화하여 관리하는 데 구조체를 사용해봤을 겁니다.

스위프트에서는 구조체와 클래스의 모습과 문법이 거의 흡사합니다.
다만, 구조체의 인스턴스는 값 타입이고, 클래스의 인스턴스는 참조 타입이라는 것이 이 둘을 구분하는 가장 큰 차이점입니다. 

이제까지 우리가 알아본 스위프트의 데이터 타입과 열거형은 모두 값 타입니다. 그러나 구조체와 함께 배워볼 클래스는 참조 타입입니다.
C 언어와 Objective-C의 포인터와 유사한 개념입니다. 
참고로 스위프트는 참조 타입보다는 값 타입에 친절한 언어입니다.

구조체

구조체는 struct 키워드로 정의합니다.

구조체 명명법
구조체를 정의한다는 것은 새로운 타입을 생성해주는 것과 마찬가지이므로 기본 타입 이름(Int, String, Bool 등등)처럼 대문자 카멜케이스를 사용하여 이름을 지어줍니다. 프로퍼티와 메서드는 소문자 카멜케이스를 사용하여 이름을 지어줍니다. 

struct [구조체 이름] { 
    [프로퍼티와 메서드들]
}

아래에 사람의 기본 정보를 구성하는 구조체를 정의해봤습니다.
BasicInformation 이라는 이름으로 정의했으며 이 구조체는 String 타입인 nameInt 타입인 age라는 저장 프로퍼티를 가집니다.

struct BasicInformation {
    var name: String
    var age: Int
}

구조체 인스턴스의 생성 및 초기화

구조체 정의를 마친 후, 구조체의 인스턴스를 생성하고 초기화하고자 할 때에는 기본적으로 생성되는 멤버와이즈 이니셜라이저를 사용합니다.
구조체에 기본 생성된 이니셜라이저의 매개변수는 구조체의 프로퍼티 이름으로 자동 지정됩니다. 

인스턴스가 생성되고 초기화된 후 프로퍼티 값에 접근하고 싶다면 마침표(.)를 사용하면 됩니다.

구조체를 상수 let으로 선언하면 인스턴스 내부의 프로퍼티 값을 변경할 수 없고, 변수 var로 선언하면 내부의 프로퍼티가 var로 선언된 경우에 값을 변경해줄 수 있습니다.

// 프로퍼티 이름(name, age)으로 자동 생성된 이니셜라이저를 사용하여 구조체를 생성합니다. 
var yagomInfo: BasicInformation = BasicInformation(name: "yagom", age: 99)
yagomInfo.age = 100 // 변경 가능!
yagomInfo.name = "Bear" // 변경 가능!

// 프로퍼티 이름(name, age)으로 자동 생성된 이니셜라이저를 사용하여 구조체를 생성합니다.
let hanaInfo: BasicInformation = BasicInformation(name: "hana", age: 99)
hanaInfo.age = 100 // 변경 불가! 

클래스

클래스를 어떻게 정의하고 클래스의 인스턴스를 어떻게 생성하는지 그리고 클래스를 어떻게 활용하는지에 대해 알아보겠습니다.

스위프트의 클래스는 부모클래스가 없더라도 상속 없이 단독으로 정의가 가능합니다. 

클래스 명명법
클래스를 정의한다는 것은 새로운 타입을 생성해주는 것과 마찬가지이므로 기본 타입 이름(Int, String, Bool 등등)처럼 대문자 카멜케이스를 사용하여 이름을 지어줍니다. 프로퍼티와 메서드는 소문자 카멜케이스를 사용하여 이름을 지어줍니다.

클래스를 정의할 때에는 class라는 키워드를 사용합니다. 

class [클래스 이름] { 
    [프로퍼티와 메서드들]
} 

클래스를 정의하는 방법은 구조체와 흡사합니다. 다만, 클래스는 상속받을 수 있기 때문에 상 속받을 때에는 클래스 이름 뒤에 콜론(:)을 써주고 부모클래스 이름을 명시합니다.

class [클래스 이름]: [부모클래스 이름] { 
    [프로퍼티와 메서드들]
}

사람의 기본정보를 프로퍼티로 갖는 클래스를 정의해보겠습니다.

class Person {
    var height: Float = 0.0
    var weight: Float = 0.0
}

위에 정의된 클래스는 Float 타입인 heightweight 저장 프로퍼티가 있는 Person 클래스입니다. 

클래스 인스턴스의 생성과 초기화

클래스를 정의한 후, 클래스의 인스턴스를 생성하고 초기화하고자 할 때에는 기본적인 이니셜라이저를 사용합니다.

위 코드의 Person 클래스에서는 프로퍼티의 기본값이 지정되어 있으므로 전달인자를 통하여 따로 초깃값을 전달해주지 않아도 됩니다. 
인스턴스가 생성되고 초기화된 후(이니셜라이즈된 후) 프로퍼티 값에 접근하고 싶다면 마침표(.)를 사용하면 됩니다.

구조체와는 다르게 클래스의 인스턴스는 참조 타입이므로 클래스의 인스턴스를 상수 let으로 선언해도 내부 프로퍼티 값을 변경할 수 있습니다.
구조체 코드와 클래스 코드를 비교해보세요.

var yagom: Person = Person()
yagom.height = 123.4
yagom.weight = 123.4

let hana: Person = Person()
hana.height = 123.4
hana.weight = 123.4 

클래스 인스턴스의 소멸

클래스의 인스턴스는 참조 타입이므로 더는 참조할 필요가 없을 때 메모리에서 해제됩니다.
이 과정을 소멸이라고 칭하는데 소멸되기 직전 deinit라는 메서드가 호출됩니다.
클래스 내부에 deinit 메서드를 구현해주면 소멸되기 직전 deinit 메서드가 호출됩니다. 이렇게 호출 되는 deinit 메서드는 디이니셜라이저(Deinitializer)라고 부릅니다. deinit 메서드는 클래스당 하나만 구현할 수 있으며, 매개변수와 반환 값을 가질 수 없습니다. deinit 메서드는 매개변수를 위한 소괄호도 적어주지 않습니다. 또, 프로그래머가 직접 deinit 메서드를 호출할 수도 없습니다.

아래 코드에서 Person 클래스에 구현된 deinit 메서드 를 확인할 수 있습니다.

class Person {
    var height: Float = 0.0
    var weight: Float = 0.0
    deinit {
        print("Person 클래스의 인스턴스가 소멸됩니다.")
    }
}
var yagom: Person? = Person()
yagom = nil // Person 클래스의 인스턴스가 소멸됩니다. 

보통 deinit 메서드에는 인스턴스가 메모리에서 해제되기 직전에 처리할 코드를 넣어줍니다.

예를 들어 인스턴스 소멸 전에 데이터를 저장한다거나 다른 객체에 인스턴스 소멸을 알려야 할 때는 특히 deinit 메서드를 구현해야 합니다. 

구조체와 클래스의 차이

구조체와 클래스는 서로 비슷하거나 같은 점도 많습니다.

다음은 같은 점입니다. 

  • 값을 저장하기 위해 프로퍼티를 정의할 수 있습니다. 
  • 기능 수행을 위해 메서드를 정의할 수 있습니다. 
  • 서브스크립트 문법을 통해 구조체 또는 클래스가 가지는 값(프로퍼티)에 접근하도록 서브스크립트를 정의할 수 있습니다. 
  • 초기화될 때의 상태를 지정하기 위해 이니셜라이저를 정의할 수 있습니다. 
  • 초기구현과 더불어 새로운 기능 추가를 위해 익스텐션을 통해 확장할 수 있습니다. 
  • 특정 기능을 수행하기 위해 특정 프로토콜을 준수할 수 있습니다.

그러나 확연히 다른 점도 존재합니다. 

  • 구조체는 상속할 수 없습니다.
  • 타입캐스팅은 클래스의 인스턴스에만 허용됩니다.
  • 디이니셜라이저는 클래스의 인스턴스에만 활용할 수 있습니다.
  • 참조 횟수 계산(Reference Counting)은 클래스의 인스턴스에만 적용됩니다.

구조체와 클래스는 겉보기엔 정의하는 방법도, 인스턴스화하는 방법도, 프로퍼티와 메서드를 갖는다는 점을 비롯해 많은 부분에서 비슷해보입니다. 그러나 이 두 타입을 구분 짓는 가장 큰 차이점은 값 타입과 참조 타입이라는 것입니다. 그래서 참조 횟수 계산은 클래스의 인스턴스에만 해당됩니다.

스위프트의 기본 데이터 타입은 모두 구조체

public struct String {
    /// An empty 'String'.
    public init()
} 

위의 코드는 스위프트 표준 라이브러리에 포함되어 있는 스위프트의 String 타입의 기본 정의 코드입니다.

public은 잠시 제쳐두고, struct 키워드는 익숙하지 않으신가요?
네, 스위 프트의 다른 기본 타입(Bool, Int, Array, Dictionary, Set 등등)도 String 타입과 마찬 가지로 모두 구조체로 구현되어 있습니다. 그렇다는 의미는, 기본 데이터 타입은 모두 값 타입 이라는 뜻입니다.
전달인자를 통해 데이터를 전달하면 모두 값이 복사되어 전달될 뿐, 함수 내부에서 아무리 전달된 값을 변경해도 기존의 변수나 상수에는 전혀 영향을 미치지 못한다는 의미입니다.
이런 점을 더욱 확실히 하기 위해 스위프트의 전달인자는 모두 상수로 취급되어 전달되는 것일지도 모릅니다. 

구조체와 클래스 선택해서 사용하기 

구조체와 클래스는 모두 새로운 데이터 타입을 정의하고 기능을 추가한다는 점에서는 같습니다. 하지만 구조체 인스턴스는 항상 값 타입이고, 클래스 인스턴스는 참조 타입입니다.
그 의미는 생긴 것은 비슷하지만 용도는 다르다는 의미입니다. 프로젝트의 성격에 따라, 데이터의 활용 용도에따라, 특정타입을 구현할 때 구조체와 클래스 둘 중 하나를 선택해서 사용해야 합니다. 

애플은 가이드라인에서 다음 조건 중 하나 이상에 해당된다면 구조체를 사용하기를 권합니다. 

  • 연관된 간단한 값의 집합을 캡슐화 하는 것만이 목적일 때 
  • 캡슐화된 값이 참조되는 것보다 복사되는 것이 합당할 때
  • 구조체에 저장된 프로퍼티가 값 타입이며 참조되는 것보다 복사되는 것이 합당할 때
  • 다른 타입으로부터 상속받거나 자신이 상속될 필요가 없을 때 

구조체로 사용하기에 가장 적합한 예로는 좌표계가 있습니다. x, y 좌표 등을 표현하고 싶을 때 Int 타입으로 x, y 프로퍼티를 가질 수 있으며, 물건의 크기를 표현하고자 할 때는 부동소수 표현인 Double 또는 Float 타입을 사용하여 width, height, depth 등으로 묶어 표현해 줄 수 있습니다. 

이런 몇 가지 상황을 제외하면 클래스로 정의하여 사용합니다.
대다수 사용자정의 데이터 타입은 클래스로 구현할 일이 더 많을 수 있습니다. 

이번에는 스위프트의 구조체와 클래스에 대해 알아봤습니다.
다음 포스팅에서는 프로토콜과 익스텐션에 대해 알아보겠습니다. 
다음 번에 또 뵈어요~ 고맙습니다 😀

본 글의 일부내용은 필자의 저서 [스위프트 프로그래밍](2017, 한빛미디어)(http://www.hanbit.co.kr/store/books/look.php?p_code=B5682208459)에서 요약, 발췌하였음을 알립니다.

스위프트의 문법에 대해 더 알아보고 싶다면 애플의 Swift Language Guide[https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html]를 참고해도 많은 도움이 됩니다.

by yagom


facebook : https://www.facebook.com/yagompage
facebook group : https://www.facebook.com/groups/yagom/

p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.
RSS Feed 받기

댓글 남기기

Close