구조체와 클래스는 하나의 큰 코드 블록이다. 이 안에 변수나 상수를 넣어 값을 저장할 수도, 함수를 넣어 기능을 저장할 수도 있다. 값을 저장할 수는 없지만 특정 기능을 실행할 수 있는 함수와, 값을 저장할 수 있지만 혼자서 특정 기능을 실행할 수 없는 변수 상수의 특성을 모아놓은 것이라고 이해하면 쉽다.
구조체와 클래스 내에서 정의된 변수와 상수, 그리고 함수는 부르는 명칭이 조금 다르다. 변수, 상수가 구조체나 클래스 내부에 정의되면 프로퍼티(Property)라는 이름을 가지게 된다. 마찬가지로 함수 또한 구조체와 클래스 내부에 정의되면 펑션(Function)이 아니라 메소드(Method)라고 불린다. 그 이유는 변수와 상수, 함수가 구조체나 클래스 내에 들어가면 특별한 성격을 가지기 때문이다.
프로퍼티와 메소드를 합해서 구조체나 클래스의 멤버(Member)라고 표현하는데, 이는 프로퍼티와 메소드가 구조체, 클래스를 이루는 핵심요소가 되기 때문이다.
스위프트는 객체지향 언어이다. 필요한 기능을 객체로 구현하여 사용한다는 것이 객체지향 언어의 핵심인데, 이 때 객체를 만들어내는 주요 대상이 구조체와 클래스이다. 그렇다면 구조체와 클래스는 무엇이 같고 무엇이 다른지 살펴보자.
✅ 구조체와 클래스의 공통점
- 프로퍼티 변수나 상수를 사용해 값을 저장하는 프로퍼티를 정의할 수 있다.
- 메소드 함수를 사용하여 기능을 제공하는 메소드를 정의할 수 있다.
- 서브스크립트 속성값에 접근할 수 있는 방법을 제공하는 서브스크립트를 정의할 수 있다.
- 초기화 블록 객체를 원하는 초기 상태로 설정해주는 초기화 블록을 정의할 수 있다.
- 확장 객체에 함수적 기능을 추가하는 확장(extends) 구문을 사용할 수 있다.
- 프로토콜 특정 형식의 함수적 표준을 제공하기 위한 프로토콜을 구현할 수 있다.
✅ 구조체와 클래스의 차이점
- 상속 클래스의 특성을 다른 클래스에게 물려줄 수 있다.
- 타입 캐스팅 실행 시 컴파일러가 클래스 인스턴스의 타입을 미리 파악하고 검사할 수 있다.
- 소멸화 구문 인스턴스가 소멸되기 전에 처리해야 할 구문을 미리 등록해놓을 수 있다.
- 참조에 의한 전달 클래스 인스턴스가 전달될 때에는 참조형식으로 제공되며, 참조 가능한 개수에 제약은 없다.
보통 클래스의 기능 범위가 구조체보다 더 크다. 구조체는 할 수 없지만, 클래스는 할 수 있는 기능들이 몇 가지 더 존재하는 것을 확인할 수 있다. 더 자세한 것은 추후 포스팅에서 다루겠다.
정의 구문
// 구조체 정의
struct Resolution {
// 구조체 내용
}
// 클래스 정의
class VideoMode {
// 클래스 내용
}
객체의 이름을 작성할 때에는 표준 스위프트 객체 코딩 형식에 따라 구조체 이름과 클래스 이름의 첫 글자는 대문자로, 나머지는 소문자로 작성하는 것이 원칙이다. 이러한 표기법을 카멜(Camel) 표기법 이라고 하는데 이 표기법을 준수해주는 것이 좋다.
메소드와 프로퍼티
구조체와 클래스에서는 변수나 상수를 정의해 내부에 값을 저장할 수 있다. 이렇게 구조체와 클래스 내부에서 정의된 변수나 상수를 프로퍼티(Property), 또는 속성이라고 한다. 또한 함수를 정의하여 특정 기능을 정의할 수도 있는데, 이를 메소드(Method)라고 한다.
struct Resolution {
var width = 0
var height = 0
func desc() -> String {
return "Resolution 구조체"
}
}
class VideoMode {
var interlaced = false
var frameRate = 0.0
var name : String?
func desc() -> String {
return "VideoMode 클래스"
}
}
Resolution 구조체에는 width와 height라는 두 개의 저장 프로퍼티가 있다. 여기서 저장 프로퍼티란 특정값을 저장하기 위해 클래스나 구조체 내부에 정의된 변수나 상수를 말한다. 이 두 프로퍼티는 초기값으로 0이 대입되었으므로 타입 추론 규칙에 의해 Int 타입의 데이터 형식으로 추론된다.
VideoMode 클래스에는 세 개의 저장 프로퍼티가 정의되어 있고, 초기화 되어있다. name 프로퍼티의 경우 옵셔널 문자열로 정의 되고 초기값일 할당되지 않았는데, 옵셔널 타입 프로퍼티에 초기값이 할당되지 않으면 자동으로 nil이라는 기본값으로 초기화된다.
또한 각각 desc라는 이름의 메소드를 가지고 있다. 이 메소드의 역할은 각각의 객체에 대한 문자열을 반환하는 것이다. 구조체와 클래스 내부에 작성된 함수라고 보면된다.
인스턴스
우리가 정의한 구조체나 클래스를 그대로 사용해서 값을 저장하거나 메소드를 실행할 수는 없다. 이들 자체는 단순히 객체의 정의일 뿐, 실제로 값을 저장하고 메소드를 호출하는 데에 필요한 메모리 공간을 할당받지 못했기 때문이다.
구조체나 클래스는 실행할 수 있는 객체가 아니라, 값을 담을 수 있는 실질적인 그릇들을 만들기 위한 일종의 틀로 생각 해야한다. 원형(Origin)이라는 것이다. 이 때 원형 틀을 이용해 찍어낸 그릇을 인스턴스(Instance)라고 한다. 타입의 설계도를 사용해 메모리 공간을 할당받은 것이 인스턴스인 것이다. 그리고 우리가 이 설계도를 이용해 실질적인 값을 담을 수 있는 것이 바로 인스턴스다.
예제에서 본 Resolution 구조체를 바탕으로 찍어낸 인스턴스는 'Resolution 구조체의 인스턴스'라고 하고 VideoMode 클래스를 바탕으로 찍어낸 인스턴스는 'VideoMode 클래스의 인스턴스'라고 한다. 우리가 클래스나 구조체를 실제로 사용하라면 이처럼 인스턴스를 만들고, 인스턴스를 이용해 값을 저장하거나 처리해야한다.
구조체와 클래스의 인스턴스를 생성하는 방식은 다음과 같다.
// Resolution 구조체에 대한 인스턴스를 insRes에 할당
let insRes = Resolution()
// VideoMode 클래스에 대한 인스턴스를 생성하고 상수 insMode에 할당
let insVMode = VideoMode()
이렇게 구조체나 클래스의 이름 뒤에 빈 괄호를 붙여주면 된다. 이 때 빈 괄호는 클래스나 구조체를 초기화하여 인스턴스를 생성하는 '인스턴스 생성 연산자'가 된다.
위에서 선언된 프로퍼티는 인스턴스를 통해서만 접근이 가능하다. 사실 인스턴스가 생성되지 않은 상태에선 프로퍼티도 존재하지 않는 것이나 마찬가지이다. 메모리에 할당을 받은 것이 아니기 때문이다. 이 때 프로퍼티에 접근하ㅏ는 방법은 점 문법(Dot Syntax)를 이용하면 된다.
let width = insRes.width
print("insRes 인스턴스의 width 값은 \(width)입니다")
이런 식으로 Resolution 구조체의 인스턴스인 insRes와 프로퍼티 width를 점 문법으로 연결하면 속성의 값을 불러올 수 있다.
점 문법은 프로퍼티에 값을 대입할 때도 사용된다.
class VideoMode {
var interlaced = false
var frameRate = 0.0
var name : String?
var res = Resolution() // Resolution 구조체의 인스턴스
...(중략)...
}
// VideoMode 클래스에 대한 인스턴스 생성
let vMode = VideoMode()
// 점 문법을 이용해 인스턴스의 프로퍼티에 값 할당
vMode.name = "Sample"
vMode.res.width = 1200
print("\(vMode.name!)의 인스턴스의 width 값은 \(vMode.res.width)입니다")
/* 실행결과
Sample 인스턴스의 width 값은 1280입니다
초기화
구조체나 클래스 이름 뒤에 빈 괄호를 붙이면 기본적인 인스턴스가 만들어진다. 필요에 따라선 빈 괄호가 아니라 인자값을 넣어주는 경우도 있다. 이 때 입력된 인자값들은 대부분 객체의 프로퍼티를 초기화(Initialize)하기 위해 반드시 필요한 값들이다.
스위프트에서 옵셔널 타입으로 선언되지 않은 모든 프로퍼티는 명시적으로 초기화해 주어야 한다. 초기화되지 않은 프로퍼티가 있는 경우 컴파일러는 이를 컴파일 오류로 처리한다. 여기서 명시적 초기화란 다음 둘 중 하나를 의미한다.
- 프로퍼티를 선언하면서 동시에 초기값 지정하는 경우
- 초기화 메소드 내에서 프로퍼티의 초기값을 지정하는 경우
쉽게 말하자면 클래스나 구조체의 모든 프로퍼티는 적어도 인스턴스가 생성되는 시점까지는 반드시 초기화되어야 한다는 것이다.
구조체는 모든 프로퍼티의 값을 인자값으로 입력받아 초기화하는 기본 초기화 구문을 자동으로 제공한다. 프로퍼티를 보통 멤버 변수라고 부르는 까닭에, 이 초기화 구문을 멤버와이즈 초기화 구문(Memberwise Initializer)라고 부르기도 한다. 아래 구문을 보자.
// width와 height를 매개변수로 하여 Resolution 인스턴스 생성
let defaultRes = Resolution(width: 1024, height: 768
print("width: \(defaultRes.width), height: \(defaultRes.height)")
/* 실행결과
width: 1024, height: 768
처음 Resolution 구조체를 정의할 때 width와 height 프로퍼티의 값은 0이었다. 하지만 멤버와이즈 초기화 구문을 이용해 인스턴스를 생성한 위 구문에서 프로퍼티의 값은 각각 1024와 768로 초기화 된 것을 확인할 수 있다. 이처럼 멤버와이즈 초기화 구문은 인스턴스를 생성하는 형식을 정의할 뿐만 아니라, 입력된 인자값을 이용해 프로퍼티를 초기화하는 과정까지 알아서 처리한다.
이와 달리, 클래스는 멤버와이즈 형식의 초기화 구문을 따레 제공하지 않는다. 클래스에서 제공되는 것은 오직 빈 괄호 형태의 기본 초기화 구문뿐이다. 이마저도 모든 프로퍼티가 선언과 동시에 초기화 되어 있을 때만 사용이 가능하다. 만약 초기화되지 않은 프로퍼티가 있다면 기
본 초기화 구문은 사용할 수 없으며, 직접 초기화 구문을 정의해 내부에서 해당 프로퍼티를 초기화 해주어야 한다.
구조체의 값 전달 방식 : 복사에 의한 전달
구조체는 인스턴스를 생성한 후 이를 변수나 상수에 할당하거나 함수의 인자값으로 전달할 때 값을 복사해 전달하는 방식을 사용한다. 이를 값 타입(Value Type), 또는 복사에 의한 전달이라고 한다.
스위프트에서 모든 구조체는 값 타입 이다. 이 말은 우리가 생성하는 모든 구조체 인스턴스들이 상수나 변수에 할당될 때 복사된다는 뜻이다. 물론, 구조체로 정의된 인스턴스들이 함수의 인자값으로 사용될 때도 마찬가지이다.
구조체 인스턴스를 변수에 대입하면 기존의 인스턴스가 그대로 대입되는 것이 아니라 이를 복사한 새로운 값이 대입된다. 따라서 변수에 대입된 인스턴스와 기존의 인스턴스는 서로 독립적이다. 인스턴스를 할당한 후 기존 인스턴스나 할당된 쪽의 인스턴스에 무언가 변경이 발생하더라도 서로에게 전혀 영향을 미치지 않는다. 예제를 통해 확인해보자.
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
hd라는 인스턴스를 만들고 cinema 변수에 할당하였다. Resolution은 구조체이므로 hd를 cinema에 대입하는 시점에서 기존 인스턴스의 복사본이 하나 더 만들어져 이 복사본이 cinema 변수에 대입된다. 즉 hd와 cinema는 같은 width, height 값을 가졌지만 값만 같을 뿐 실제로는 별개인 인스턴스가 대입되어 있는 것이다. 예제를 살펴보면 쉬울 것이다.
cinema.width = 2048
print("cinema 인스턴스의 width 값은 \(cinema.width)입니다")
// cinema 인스턴스의 width 값은 2048입니다
print("hd 인스턴스의 width 값은 \(hd.width)입니다")
// hd 인스턴스의 width 값은 1920입니다
cinema 인스턴스의 width값은 2048로 변했지만 hd 인스턴스의 width 값은 1920 그대로 인 것을 볼 수 있다. 한마디로 cinema 인스턴스의 width 값과 hd 인스턴스의 width 값이 별개라는 것이다. 이를 통해 구조체는 복사에 의한 전달이 이루어진다는 것을 확인할 수 있다.
클래스의 값 전달 방식 : 참조에 의한 전달
이어서 클래스의 값 전달 방식을 알아보자. 값의 복사에 의해 전달되는 구조체와는 달리, 클래스는 메모리 주소 참조에 의한 전달 방식을 사용한다. 이를 참조 타입(Reference Type)이라고 한다. 참조 타입은 변수나 상수에 할당될 때 또는 함수의 인자값으로 전달될 때 값의 복사가 이루어지는 것이 아니라 인스턴스의 주소에 접근해 참조가 전달된다.
이 때 주고받는 값 자체는 메모리 주소이지만, 이 주소에 저장된 인스턴스를 가져오는 것에 다른 구문을 쓸 필요는 없다.(C언어의 *연산자 같은) 왜냐하면 스위프트 내부적으로 참조에 대한 객체를 직접 불러오기 때문이다. 예제를 통해 확인해보자.
let video = VideoMode()
video.name = "Original Video Instance"
print("video 인스턴스의 name 값은 \(video.name!)입니다.")
/* 실행결과
video 인스턴스의 name 값은 Original Video Instance입니다.
VideoMode 클래스를 초기화해 인스턴스를 생성하고 video 상수에 할당했다. 그리고 인스턴스의 name 프로퍼티에 "Original Video Instance"라는 값을 입력해 주었다. 결과를 보면 제대로 값이 설정된 것을 알 수 있다. 이제 이 인스턴스 변수를 다른 상수에 할당해 보겠다.
let dvd = video
dvd.name = "DVD Video Instance"
print("video 인스턴스의 name 값은 \(video.name!)입니다.")
/* 실행결과
video 인스턴스의 name 값은 DVD Video Instance입니다.
video 상수를 dvd에 다시 할당하고, dvd 상수의 name 프로퍼티에 "DVD Video Instance"라는 값을 입력해 주었다. 그런 다음, 다시 한 번 video 상수의 속성값을 출력해 주었는데 따로 변경하지 않았떤 video 상수의 프로퍼티에서도 값이 변경되었음을 확인할 수 있다. 만약 이게 구조체였다면 복사로 전달이 되어 처음에 설정한 "Original Video Instance"가 출력되었을 것이다.
이제 이 인스턴스의 값을 함수의 인자값으로 넣어 수정해보자.
func changeName(v: VideoMode) {
v.name = "Function Video Instance"
}
changeName(v: video)
print("video 인스턴스의 name 값은 \(video.name!)입니다.")
/* 실행결과
video 인스턴스의 name 값은 Function Video Instance입니다.
changeName 함수를 정의하고, 함수를 호출하면서 인자값을 video 인스턴스 변수를 전달하였다. 이 함수는 전달받은 VideoMode 타입의 매개변수 v의 name 프로퍼티의 값을 변경하는 기능을해서 다음과 같은 값이 출력되었다.
여기서 주목해야할 점이 하나 있는데 바로 func changeName(v: VideoMode) 이다.
우리가 사용한 인자값 타입은 Int, String, Bool, String과 같은 기본적인 자료형이 전부였다. 이를 원시 타입(primitive type)이라고 부른다. 하지만 클래스나 구조체를 통해 앞으로는 임의의 자료형을 만들어 사용할 수 있다. 이번 장의 처음에 정의한 클래스와 구조체인 VideoMode와 Resolution은 모두 새로운 자료형으로 사용 가능한 객체들이다.
원래대로 라면 함수의 매개변수에 inout 키워드 인자에 &를 붙여 줘야 했지만, 전달한 값이 클래스 타입이기 때문에 원본 인스턴스의 참조가 전달되었다. 따라서 함수 내부에서 매개변수 v의 프로퍼티를 수정하는 것은 곧 video 인스턴스를 직접 수정하는 것과 같다.
이로써 스위프트에서의 클래스와 구조체의 전체적인 기본 개념들을 살펴보았다. 더 자세한 개념은 추후 포스팅에서 다루도록 하겠다.
출처 : 꼼꼼한 재은씨의 Swift 문법편
'Swift > 문법' 카테고리의 다른 글
스위프트(Swift) - 구조체와 클래스 Ⅱ. 프로퍼티 - 연산 프로퍼티 (0) | 2022.06.02 |
---|---|
스위프트(Swift) - 구조체와 클래스 Ⅱ. 프로퍼티 - 저장프로퍼티 (0) | 2022.06.01 |
스위프트(Swift) - 함수(Fuction) Ⅹ. @escaping, @autoescape (0) | 2022.05.26 |
스위프트(Swift) - 함수(Function) Ⅸ. 트레일링 클로저(Trailing Closure) (0) | 2022.05.25 |
스위프트(Swift) - 함수(Function) Ⅷ. 클로저(Closure) (0) | 2022.05.24 |