참고 사이트: iOS 프로그래밍을 위한 스위프트 기초(edwith)
참고 문서: The Swift Language Guide (한국어)
실행 환경: Xcode 12 실행 → Create a new Xcode project → macOS → Command Line Tool 선택 → Product Name, Organization Identifier 입력 후 프로젝트 생성

 

목차

  1. 스위프트(Swift) 5 배워보기 - 1. 다양한 타입
  2. 스위프트(Swift) 5 배워보기 - 2. 함수, 제어문, 옵셔널
  3. 스위프트(Swift) 5 배워보기 - 3. 클래스, 구조체, 열거형
  4. (계속)

클래스와 구조체

  • 클래스와 구조체는 객체 지향 프로그램(OOP)을 위한 필요한 요소들이다.
  • 코드를 한 묶음(클래스/구조체)으로 만들어 다양하게 활용할 수 있다.
  • 여기서는 클래스와 구조체의 기본 문법과 비교점만 다룬다.

 

클래스와 구조체의 비교

아래에 언급되는 다양한 정보는 추후 자세히 다룰 예정

공통점

  • 값을 저장하기 위한 프로퍼티(Properties)를 정의할 수 있음
  • 기능을 제공하기 위한 메소드(Methods)를 정의할 수 있음
  • 초기 상태를 설정할 수 있는 이니셜라이저(Initializer)를 정의할 수 있음
  • 기본 구현상태에서 기능 확장할 수 있음
  • 특정한 종류의 표준 기능을 제공하기 위한 프로토콜(Protocols)을 따름

 

클래스에서만 가능한 기능

  • 상속(Inheritance): 클래스의 여러 속성을 다른 클래스에서 사용할 수 있음
  • 타입 캐스팅(Type casting): 런타임 시점에 클래스 인스턴스의 타입을 확인함
  • 소멸자(Deinitializers): 할당된 자원을 해제시킴
  • 참조 카운트(Reference counting): 클래스 인스턴스에 하나 이상의 참조가 가능

 

기본 형태

  • 클래스는 class 키워드를 사용
  • 구조체는 struct 키워드를 사용
  • 변수/상수를 선언할 때와는 다르게 클래스/구조체 선언 시에는 대문자로 시작(UpperCamelCase)한다.
// 구조체 선언
struct Resolution {
    // 프로퍼티 선언
    var width = 0  // 타입을 지정하지 않았지만 초기 값으로 0을 할당 했기 때문에,
    var height = 0  // 타입추론에 의해 자동으로 Int 타입을 가지게 됨
}

// 클래스 선언
class VideoMode {
    // 프로터피 선언
    var resolution = Resolution() // 구조체를 값으로 사용할 수 있다.
    var interlaced = false
    var frameRate = 0.0
    var name: String? // 옵셔널
}

 

인스턴스

  • 클래스와 구조체를 선언 후, 이를 사용하기 위해서는 인스턴스를 생성해야 한다.
  • 클래스와 구조체 이름 뒤에 빈 괄호()를 붙이면 인스턴스가 생성된다.
// 구조체 인스턴스 생성
let someResolution = Resolution()

// 클래스 인스턴스 생성
let someVideoMode = VideoMode()

 

프로퍼티 접근

  • 생성된 인스턴스의 프로퍼티에 접근값 할당을 할 수 있다.
// 클랙스/구조체 인스턴스에 점(.)을 붙여 안에 있는 프로퍼티에 접근할 수 있다.

// Resolution 구조체의 width 프로퍼티에 접근
print("someResolution의 가로 길이는 \(someResolution.width)이다.")
// someResolution의 가로 길이는 0이다. 가 출력

// VideoMode 클래스 안에 값으로 사용된 Resolution 구조체 안의 프로퍼티에 접근
print("someVideoMode의 가로 길이는 \(someVideoMode.resolution.width)이다.")
// someVideoMode의 가로 길이는 0이다. 가 출력

// 값을 할당 할 때에는 프로퍼티에 접근 후 원하는 값을 할당하면 된다.
someVideoMode.resolution.width = 1280
print("someVideoMode의 현재 가로 길이는 \(someVideoMode.resolution.width)이다.")
// someVideoMode의 현재 가로 길이는 1280이다.

 

클래스와 구조체의 타입

  • 구조체값(value) 타입이다. 이는 함수에서 상수나 변수로 값이 전달될 때 값 그대로 복사된다는 뜻이다.
// Resolution 구조체의 인스턴스(hd) 생성
let hd = Resolution(width: 1920, height: 1080)

// 생성된 인스턴스 hd를 변수 cinema에 할당
var cinema = hd

// cinema의 width 프로퍼티 값 변경
cinema.width = 2048

// hd와 cinema의 width 프로퍼티 확인
print("cinema의 현재 가로 길이 \(cinema.width)")
print("hd의 가로 길이 \(hd.width)")

// cinema의 현재 가로 길이 2048
// hd의 가로 길이 1920

// 서로의 값이 다른 것을 확인할 수 있다.
// hd가 cinema에 할당되는 순간 복사되었기 때문에,
// cinema와 hd는 서로 다른 인스턴스가 된다.

 

  • 반면, 클래스참조(Reference) 타입이다. 변수나 상수에 값을 할당하거나 함수에 인자로 전달할 때, 값이 복사되는 것이 아닌 값이 저장되어 있는 주소만 복사(참조)된다.
    • 클래스는 참고 타입이기 때문에, 상수와 변수가 같은 인스턴스를 참조하고 있는지 비교(식별 연사자)할 수 있다.
    • === : 두 상수나 변수가 같은 인스턴스를 참조하고 있는 경우에 true
    • !== : 두 상수나 변수가 다른 인스턴스를 참조하고 있는 경우에 true
// VideoMode 클래스의 tenEighty 인스턴스 생성 후 각 프로퍼티에 값 할당
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

// anotherTenEighty 상수를 하나 만들고, tenEighty 인스턴스를 할당
// anotherTenEighty의 frameRate 프로퍼티의 값을 변경
let anotherTenEighty = tenEighty
anotherTenEighty.frameRate = 30.0

// tenEighty 인스턴스와 anotherTenEighty 인스턴스의 frameRate 프로퍼티 값 확인
print("tenEighty의 frameRate 값은 \(tenEighty.frameRate)")
print("anotherTenEighty의 frameRate 값은 \(anotherTenEighty.frameRate)")
// tenEighty의 frameRate 값은 30.0
// anotherTenEighty의 frameRate 값은 30.0

// 여기서 보면, 처음에 tenEighty.frameRate에서 할당한 25.0이 아니라
// anotherTenEighty.frameRate에서 할당한 30.0으로 값이 출력되는 것을 볼 수 있다.
// 이는 anotherTenEighty 상수가 tenEighty의 값을 복사한 것이 아니라,
// 바라보고 있는 tenEighty의 값을 변경했기 때문이다.

 

그래서, 언제 클래스를 사용하고 언제 구조체를 사용해야 하나?

일반적으로 아래 조건 중 1개 이상을 만족하면 구조체를 사용하는 것을 고려해볼 수 있다.

  • 간단한 값을 캡슐화(encapsulate)하기 위한 경우
  • 인스턴스 또는 프로퍼티가 참조되기 보다 그대로 복사되기를 원하는 경우
  • 프로퍼티나 메소드 등을 상속할 필요가 없는 경우

위 경우를 제외하고는 클래스를 사용하면 된다.

 

 

열거형

  • 열거형은 유사한 종류의 여러 값한 곳에 모아서 정의한 것(예, 요일, 계절 등)이다.
  • 스위프트의 열거형은 C나 Objective-C와 다르게 Integer뿐만 아니라 string, character, floating 값들을 사용할 수 있다.
  • 클래스/구조체와 마찬가지로 선언 시에는 대문자로 시작(UpperCamelCase)한다.
  • 단, enum 내의 각 case는 소문자로 시작(LowerCamelCase)한다.

 

기본 형태

enum CompassPoint {
    case north  // ≠ 0  스위프트는 타 언어와 다르게 내부적으로 정수값을 가지지 않는다.
    case south  // ≠ 1
    case east   // ≠ 2
    case west   // ≠ 3
}


// 여러 case를 콤마(,)로 한줄로 적을 수도 있다.
enum Planet {
    case mercury, venus, earth
}

 

열거형의 사용

  • 각 열거형의 값을 Switch 문에서 매칭할 수 있다.
// 열거형의 값을 새로운 타입으로 할당할 수 있다.
// CompassPoint의 west를 directionToHead 변수에 할당
var directionToHead = CompassPoint.west

// directionToHead에 CompassPoint 타입으로 한번 정의되고 나면,
// 다음에 다른 값을 할당 할 때는 CompassPoint를 생략할 수 있다.
directionToHead = .south

// Switch 구문으로 열거형 값 매칭
// 열거형의 모든 case를 포함해야 한다. 이 경우 default를 생략할 수 있다.
// 만약 모든 case를 포함하지 않는다면, default를 적어 빠진 case가 없도록 해야 한다.
switch directionToHead {
case .north:
    print("북쪽입니다.")
case .south:
    print("남쪽입니다.")
case .east:
    print("동쪽입니다.")
case .west:
    print("서쪽입니다.")
}  
// 남쪽입니다. 가 출력된다.

 

Raw 값

  • 앞서, 스위프트의 열거형은 각각의 case에 정수 값을 할당하지 않는다고 했다.
  • 그러나 Raw 값 지정을 통해 각 case에 Integer를 비롯하여 String, Character 등의 값을 할당할 수 있다.
    • Integer 값을 지정한 경우 은 C 언어의 enum과 마찬가지로 자동으로 1씩 증가한다.
// 열거형의 case에 Character 형을 정의한 경우
// 단, Raw 값은 중복되면 안된다.
enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}

// Integer 값을 정의한 경우

enum Planet: Int {
    case mercury = 1 // mercury에만 명시적으로 1을 할당함.
    case venus
    case earth
}
// 위에서 보면 venus와 earth에는 명시적으로 값을 할당하지 않았지만,
// 자동으로 mercury에서 값이 증가된 2와 3이 할당된다.

 

Raw 값을 통한 초기화

  • rawValue를 통해 열거형 변수를 초기화할 수 있다.
  • Raw 값을 통한 초기화는, 열거형에 지정된 Raw 값이 없을 경우 초기화에 실패하여 nil이 된다.
    • 모든 raw값에 대해 열거형 case 반환이 보장되지 않으므로 실패할 수 있는 초기자(failable initializer)이다.
    • rawValue를 통해 초기화한 인스턴스는 옵셔널 타입을 가진다.
// 예를 들어 아래와 같이 열거형을 생성했다고 가정한다.
enum Planet: Int {
    case mercury = 0
    case venus = 1
    case earth = 2
}

// 값이 존재하는 earth를 열거형 변수의 초기 값으로 지정하면
// 정상적으로 초기 값이 지정된다.
let possiblePlanet = Planet(rawValue: 2)
print(possiblePlanet)
// Optional(test.Planet.earth) 이 출력됨.

// 만약, 존재하지 않는 값(rawValue: 3)을 초기 값으로 지정하면
let possiblePlanet = Planet(rawValue: 3)
print(possiblePlanet)
// nil 이 출력 됨.


// 따라서 옵셔널 변수 처리할 때와 같이
// if let 문을 사용해서 처리하면 된다.
if let somePlanet = Planet(rawValue: 3) {
    switch somePlanet {
    case .earth:
        print("지구입니다.")
    default:
        print("지구가 아닙니다.")
    }
} else {
    print("해당 위치의 행성이 존재하지 않습니다.")
}
// 해당 위치의 행성이 존재하지 않습니다. 이 출력 됨.

 

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기

댓글을 달아 주세요

">