클래스와 구조체는 프로그램의 코드를 조직화하기 위해 일반적으로 사용한다.
interface
파일과 implementation
파일을 분리해서 만들지 않아도 된다. 하나의 파일에 클래스와 구조체를 정의하면, Swift가 자동으로 해당 클래스와 구조체를 사용할 수 있는 인터페이스를 생성한다.
Swift에서 클래스와 구조체는 많은 공통점이 있다.
subscript
문법을 이용해 특정 값을 접근할 수 있는 subscript
정의initializer
정의구조체로는 가능하지 않고 클래스만 가능한 기능
구조체는 다른 코드로 전달될 때 항상 복사되서 전달되고, 참조 카운트를 사용하지 않는다.
클래스와 구조체 둘 다 비슷한 선언 문법을 갖고 있다.
클래스는 class
키워드, 구조체는 struct
키워드를 이름 앞에 적어 선언할 수 있다.
class SomeClass {
// 클래스 내용
}
struct SomeStructure {
// 구조체 내용
}
새로운 클래스나 구조체를 선언할 때마다 Swift 에서 완전 새로운 타입을 선언하는 것이다. 그래서 이름을 다른 표준 Swift 타입(String, Int, Bool)과 같이 UpperCamelCase 이름으로 선언한다. 반대로 프로퍼티나 메소드는 lowerCamelCase 이름으로 선언한다.
클래스와 구조체 선언의 예시
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
// 위 Resolution 구조체를 값으로 사용
var interlaced = false
var frameRate = 0.0
var name: String?
}
구조체 Resolution
의 프로퍼티 width
와 height
는 초기값으로 0을 할당 했기 때문에 Swift의 타입추론에 자동으로 Int
타입을 갖게 된다.
클래스와 구조체 이름 뒤에 빈 괄호를 적으면 각각의 인스턴스를 생성할 수 있다.
let someResolution = Resolution()
// 구조체 인스턴스 생성
let someVidroMode = VideoMode()
// 클래스 인스턴스 생성
점(.) 문법을 통해 클래스와 구조체 인스턴스의 프로퍼티에 접근할 수 있다.
print("The width of someResolution is \(someResolution.width)")
// "The width of someResolution is 0"
점문법을 이용해 값을 할당할 수 있다.
someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// "The width of someVideoMode is now 1280"
Swift에서는 하위레벨의 구조체 프로퍼티도 직접 설정할 수 있다.
모든 구조체가 초기화될 때 프로퍼티를 선언할 수 있는 초기자를 자동으로 생성해 제공한다.
아래와 같은 맴버의 초기화는 구조체 안에 width
와 heigth
프로퍼티만 정의했다면 자동으로 사용 가능하다는 의미이다.
let fhp = Resolution(width: 1280, height: 960)
값 타입이라는 뜻은, 이것이 함수에서 상수나 변수에 전달될 때 그 값이 복사되서 전달 된다는 의미이다. Swift에서 모든 구조체와 열거형 타입은 값 타입이다.
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
위 코드의 첫 줄에서 Resolution
구조체의 인스턴스 hd
를 선언한다. 그리고 hd
를 cinema
라는 변수에 할당한다. 그러면 이 cinema
는 hd
를 할당하는 순간 복사하는 것이 때문에 같지 않고 완전히 다른 인스턴스가 된다.
열거형에서의 동작도 동일하다.
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"
값 타입과 달리 참조 타입은 변수나 상수에 값을 할당을 하거나 함수에 인자로 전달할 때 그 값이 복사되지 않고 참조된다.
참조의 의미는 그 값을 갖고 있는 메모리를 바라보고 있다는 뜻이다.
let.tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0
tenEighty
라는 VideoMode
클래스 인스턴스를 생성하고 각 프로퍼티에 값을 할당한다.
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
alsoTenEighty
라는 상수를 만들고 위에서 선언한 tenEighty
클래스 인스터스를 할당한다. 그리고 alsoTenEighty
의 frameRate
값을 30으로 변경한다.
print("The frameRate property of tenEight is now \(tenEighty.frameRate)")
// "The frameRate property of tenEight is now 30.0"
결과가 30이 나오는 이유는 값을 복사한 것이 아니라 참조한 것이기 때문이다.
그래서 실제로 alsoTenEighty
와 tenEighty
는 동일한 메모리 주소를 바라보고 참조하는 것이다.
의아하게 생각할 수 있는 것은 let alsoTenEight = tenEighty
로 상수로 선언했는데 어떻게 값을 변경할 수 있었는지 인데, 이것은 alsoTenEighty
자체를 변경하는 것이 아니라 그것이 바라보는 값을 변경하는 것이기 때문에 가능하다.
클래스는 참조 타입이기 때문에 여러 상수와 변수에서 같은 인스턴스를 참조할 수 있다. 상수와 변수가 같은 인스턴스를 참조하고 있는지 비교하기 위해 식별 연산자를 사용한다.
if tenEighty === alsoTenEighty {
print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// "tenEighty and alsoTenEighty refer to the same VideoMode instance."
식별 연산자(===)는 비교 연산자(==)와 같지 않다.
식별 연산자는 참조를 비교하는 것이고, 비교 연산자는 값을 비교한다.
클래스와 구조체 모두 프로그램의 코드를 조직화 하고 특정 타입을 선언하는데 사용된다. 그리고 앞서 설명했던 것처럼 클래스 인스턴스가 인자로 사용될 때는 참조가 넘어가고 구조체는 값이 넘어간다.
언제 클래스와 구조체를 사용해야 할까?
일반적으로 다음 조건 중 1개 이상을 만족하면 구조체를 사용하는 것을 고려해 볼 수 있다.
위에 기술된 경우를 제외한 다른 모든 경우에는 구조체가 아니라 클래스를 사용하면 된다.
Swift에서는 String
, Array
, Dictionary
같은 기본 데이터 타입이 구조체로 구현 되어있다. 그렇다는 의미는 이 값을 다른 상수나 변수에 할당하거나 함수나 메소드에 인자로 넘길 때 이 값이 복사 된다는 것이다. 반면 Foundation
의 NSString
, NSArray
, NSDictionary
는 클래스로 구현 되어있다. 그래서 이 데이터들은 항상 할당 되거나 전달될 때 복사 되지 않고 참조가 사용된다.