Posted:      Updated:

구조체

스위프트의 대부분의 타입은 구조체로 이루어져 있다.
구조체는 값(value) 타입이며, 타입 이름은 대문자 카멜케이스를 사용하여 정의한다.

문법

정의

struct 이름 {
    /* 구현부 */
}

구현

struct Sample {
    // 가변 프로퍼티(값 변경 가능)
    var mutableProperty: Int = 100 
    
    // 불변 프로퍼티(값 변경 불가능)
    let immutableProperty: Int = 100 
    
    // 타입 프로퍼티
    static var typeProperty: Int = 100 
    
    // 인스턴스 메서드
    func instanceMethod() {
        print("instance method")
    }
    
    // 타입 메서드
    static func typeMethod() {
        print("type method")
    }
}

사용

// 가변 인스턴스
var mutable: Sample = Sample()
mutable.mutableProperty = 200

// 불변 인스턴스
let immutable: Sample = Sample()

// 타입 프로퍼티, 메서드
Sample.typeProperty = 300
Sample.typeMethod()

예제

struct Student {
    var name: String = "unknown"
    var `class`: String = "Swift" // 키워드를 `` 로 묶어서 이름으로 사용
    
    static func selfIntroduce() {
        print("학생타입입니다")
    }
    
    func selfIntroduce() {
        print("저는 \(self.class)\(name)입니다")
    }
}

Student.selfIntroduce()

// 가변 인스턴스
var better: Student = Student()
better.name = "better"
better.class = "스위프트"
better.selfIntroduce()   // 저는 스위프트반 better입니다

// 불변 인스턴스
let k: Student = Student()
k.selfIntroduce() // 저는 Swift반 unknown입니다

클래스

문법

정의

class 이름 {
    /* 구현부 */
}

구현

class Sample {
    // 가변 프로퍼티
    var mutableProperty: Int = 100 

    // 불변 프로퍼티
    let immutableProperty: Int = 100 
    
    // 타입 프로퍼티
    static var typeProperty: Int = 100 
    
    // 인스턴스 메서드
    func instanceMethod() {
        print("instance method")
    }
    
    // 타입 메서드
    // 상속시 재정의 불가 타입 메서드 - static
    static func typeMethod() {
        print("type method - static")
    }
    
    // 상속시 재정의 가능 타입 메서드 - class
    class func classMethod() {
        print("type method - class")
    }
}

사용

var mutableReference: Sample = Sample()
mutableReference.mutableProperty = 200

let immutableReference: Sample = Sample()
immutableReference.mutableProperty = 200

// 컴파일 오류 발생
//immutableReference = mutableReference

// 타입 프로퍼티 및 메서드
Sample.typeProperty = 300
Sample.typeMethod()

예제

class Student {
    var name: String = "unknown"
    var `class`: String = "Swift"
    
    class func selfIntroduce() {
        print("학생타입입니다")
    }

    func selfIntroduce() {
        print("저는 \(self.class)\(name)입니다")
    }
}

Student.selfIntroduce()  // 학생타입입니다

var yagom: Student = Student()
yagom.name = "better"
yagom.class = "스위프트"
yagom.selfIntroduce() // 저는 스위프트반 better입니다

let jina: Student = Student()
jina.name = "jina"
jina.selfIntroduce() // 저는 Swift반 jina입니다

열거형

유사한 종류의 여러 값을 한 곳에 모아서 정의한 것이다.
eum 자체가 하나의 데이터 타입으로, 대문자 카멜케이스를 사용하여 이름을 정의한다.
각 case는 소문자 카멜케이스로 정의하며, 각 case는 그 자체가 고유의 값이다.

다른 언어의 열거형과는 많이 다르며, 강력한 기능을 지니고 있어 잘 알아두고 사용하면 좋다.

enum 이름 {
    case 이름1
    case 이름2
    case 이름3, 이름4, 이름5
}

enum BoostCamp {
    case iosCamp
    case androidCamp
    case webCamp
}

사용

타입이 명확한 경우 열거형의 이름을 생략할 수 있다.
switch 구문에서 사용하면 좋다.

enum Weekday {
    case mon
    case tue
    case wed
    case thu, fri, sat, sun
}

var day: Weekday = Weekday.mon
day = .tue
print(day) // tue

switch day {
    case .mon, .tue, .wed, .thu:
        print("평일입니다")
    case Weekday.fri:
        print("불금")
    case .sat, .sun:
        print("주말")
}

rawValue (원시값)

C언어의 enum 처럼 정수값을 가질 수 있다.
rawValue는 case 별로 각각 다른 값을 가져야 하며, 자동으로 1이 증가된 값이 할당된다.
반드시 지닐 필요가 없다면 굳이 만들지 않아도 된다.

enum Fruit: Int {
    case apple = 0
    case grape = 1
    case peach
}

print("Fruit.peach.rawValue == \(Fruit.peach.rawValue)")
// Fruit.peach.rawValue == 2

정수 타입 말고도 Hashable 프로토콜을 따르는 모든 타입을 원시값의 타입으로 지정할 수 있다.

enum School: String {
    case elementary = "초등"
    case middle = "중등"
    case high = "고등"
    case university
}

print("School.middle.rawValue == \(School.middle.rawValue)")
// School.middle.rawValue == 중등

print("School.university.rawValue == \(School.university.rawValue)")
// School.middle.rawValue == university

원시값을 통한 초기화

rawValue를 통해 초기화 할 수 있다.
rawValue가 case에 해당하지 않을 수 있으므로, rawValue를 통해 초기화한 인스턴스는 옵셔널 타입이다.

let apple: Fruit? = Fruit(rawValue: 0)

// if let 구문 사용
if let orange: Fruit = Fruit(rawValue: 5) {
    print("rawValue 5에 해당하는 케이스는 \(orange)입니다")
} else {
    print("rawValue 5에 해당하는 케이스가 없습니다")
}

메서드

메서드를 추가할 수 있다.

enum Month {
    case dec, jan, feb
    case mar, apr, may
    case jun, jul, aug
    case sep, oct, nov
    
    func printMessage() {
        switch self {
        case .mar, .apr, .may:
            print("따스한 봄~")
        case .jun, .jul, .aug:
            print("여름 더워요~")
        case .sep, .oct, .nov:
            print("가을은 독서의 계절!")
        case .dec, .jan, .feb:
            print("추운 겨울입니다")
        }
    }
}

Month.mar.printMessage()

클래스 vs 구조체/열거형

값 타입과 참조 타입

값 타입(Value Type): 데이터를 전달 할 때 값을 복사하여 전달한다.
참조 타입(Reference Type): 데이터를 전달할 때 값의 메모리 위치를 전달한다.

클래스는 참조타입이고, 열거형과 구조체는 값타입이다.
클래스는 상속이 가능하고, 열거형과 구조체는 상속이 불가능하다.

struct ValueType {
    var property = 1
}

class ReferenceType {
    var property = 1
}

let firstStructInstance = ValueType()
var secondStructInstance = firstStructInstance
secondStructInstance.property = 2

print("first struct instance property : \(firstStructInstance.property)")    // 1
print("second struct instance property : \(secondStructInstance.property)")  // 2


let firstClassReference = ReferenceType()
let secondClassReference = firstClassReference
secondClassReference.property = 2

print("first class reference property : \(firstClassReference.property)")    // 2
print("second class reference property : \(secondClassReference.property)")  // 2

값 타입을 사용하는 경우

  • 연관된 몇몇의 값들을 모아서 하나의 데이터 타입으로 표현하고 싶은 경우
  • 다른 객체 또는 함수 등으로 전달될 때 참조가 아니라 복사할 경우
  • 자신을 상속할 필요가 없거나 다른 타입을 상속 받을 필요가 없는 경우

스위프트에서의 사용

스위프트의 기본 데이터 타입은 모두 구조체로 구현되어 있으며, 구조체와 열거형 사용을 선호한다.
Apple 프레임워크는 대부분 클래스를 사용한다.

클로저

실행 가능한 코드 블럭을 말한다.
매개변수 전달과 반환값이 존재하지만, 함수와 다르게 이름 정의는 필요하지 않다.
일급 객체로 전달인자, 변수, 상수 등에 저장 및 전달이 가능하다.

문법

중괄호 {} 로 감싸져 있으며, 괄호를 이용해 파라미터를 정의한다.
-> 를 이용해 반환 타입을 명시하고, in 키워드를 이용해 실행 코드와 분리한다.

{ (매개변수 목록) -> 반환타입 in
    실행 코드
}

사용

let sum: (Int, Int) -> Int = { (a: Int, b: Int) in
    return a + b
}

let sumResult: Int = sum(1, 2)
print(sumResult) // 3
let add: (Int, Int) -> Int
add = { (a: Int, b: Int) in
    return a + b
}

let substract: (Int, Int) -> Int
substract = { (a: Int, b: Int) in
    return a - b
}

let divide: (Int, Int) -> Int
divide = { (a: Int, b: Int) in
    return a / b
}

func calculate(a: Int, b: Int, method: (Int, Int) -> Int) -> Int {
    return method(a, b)
}

var calculated: Int
calculated = calculate(a: 50, b: 10, method: add)
print(calculated) // 60
calculated = calculate(a: 50, b: 10, method: substract)
print(calculated) // 40
calculated = calculate(a: 50, b: 10, method: divide)
print(calculated) // 5

// 함수를 호출할 때 클로저를 작성하여 전달
calculated = calculate(a: 50, b: 10, method: { (left: Int, right: Int) -> Int in
    return left * right
})
print(calculated) // 500

클로저 표현

기본

func calculate(a: Int, b: Int, method: (Int, Int) -> Int) -> Int {
    return method(a, b)
}

var result: Int

후행 클로저

클로저가 함수의 마지막 전달인자일 때, 마지막 매개변수 이름을 생략한 후 함수 소괄호 외부에 클로저를 구현할 수 있다.

result = calculate(a: 10, b: 10) { (left: Int, right: Int) -> Int in
    return left + right
}

print(result) // 20

반환타입 생략

result = calculate(a: 10, b: 10, method: { (left: Int, right: Int) in
    return left + right
})
print(result) // 20

// 후행 클로저와 함께 사용
result = calculate(a: 10, b: 10) { (left: Int, right: Int) in
    return left + right
}
print(result) // 20

단축 인자이름

클로저의 매개변수 이름이 불필요하다면 단축 인자이름을 활용할 수 있다.
매개변수 순서대로 $0 , $1 , $2 처럼 표현한다.

result = calculate(a: 10, b: 10, method: {
    return $0 + $1
})
print(result) // 20

result = calculate(a: 10, b: 10) {
    return $0 + $1
}
print(result) // 20

암시적 반환 표현

클로저가 반환하는 값이 있다면 클로저의 마지막 줄의 결과값은 암시적으로 반환값으로 취급한다.

result = calculate(a: 10, b: 10) {
    $0 + $1
}
print(result) // 20

// 간결하게 한 줄로 표현
result = calculate(a: 10, b: 10) { $0 + $1 }
print(result) // 20

프로퍼티

종류

  • 인스턴스 저장 프로퍼티
  • 타입 저장 프로퍼티
  • 인스턴스 연산 프로퍼티
  • 타입 연산 프로퍼티
  • 지연 저장 프로퍼티

정의와 사용

struct Student {
    // 인스턴스 저장 프로퍼티
    var name: String = ""
    var `class`: String = "Swift"
    var koreanAge: Int = 0
    
    // 인스턴스 연산 프로퍼티
    var westernAge: Int {
        get {
            return koreanAge - 1
        }
        
        set(inputValue) {
            koreanAge = inputValue + 1
        }
    }
    
    // 타입 저장 프로퍼티
    static var typeDescription: String = "학생"
    
    // 읽기전용 인스턴스 연산 프로퍼티
    var selfIntroduction: String {
        get {
            return "저는 \(self.class)\(name)입니다"
        }
    }
    
    // 읽기전용 타입 연산 프로퍼티 (get 생략 가능)
    static var selfIntroduction: String {
        return "학생타입입니다"
    }
}

// 타입 연산 프로퍼티 사용
print(Student.selfIntroduction)
// 학생타입입니다

// 인스턴스 생성
var better: Student = Student()
better.koreanAge = 10

// 인스턴스 저장 프로퍼티 사용
better.name = "better"
print(better.name)
// better

// 인스턴스 연산 프로퍼티 사용
print(better.selfIntroduction)
// 저는 Swift반 better입니다

print("제 한국나이는 \(better.koreanAge)살이고, 미쿡나이는 \(better.westernAge)살입니다.")
// 제 한국나이는 10살이고, 미쿡나이는 9살입니다.

응용

struct Money {
    var currencyRate: Double = 1100
    var dollar: Double = 0
    var won: Double {
        get {
            return dollar * currencyRate
        }
        set {
            dollar = newValue / currencyRate
        }
    }
}

var moneyInMyPocket = Money()
moneyInMyPocket.won = 11000
print(moneyInMyPocket.won)
// 11000

moneyInMyPocket.dollar = 10
print(moneyInMyPocket.won)
// 11000

지역변수 및 전역변수

저장 프로퍼티와 연산 프로퍼티의 기능은 함수, 메서드, 클로저, 타입 등의 외부에 위치한 지역/전역 변수에도 모두 사용 가능하다.

var a: Int = 100
var b: Int = 200
var sum: Int {
    return a + b
}

print(sum) // 300

프로퍼티 감시자

프로퍼티의 값이 변경될 때 원하는 동작을 수행하도록 할 수 있다.
값이 변경되기 직전에 willSet 블럭이, 값이 변경된 직후에 .didSet 블럭이 호출된다.
willSet 블럭에서는 암시적 매개변수 newValue , didSet 블럭에서는 oldValue 를 사용할 수 있다.
연산 프로퍼티에는 사용할 수 없으며, 함수, 메서드, 클로저, 타입 등의 지역/전역 변수에는 모두 사용 가능하다.

struct Money {
    var currencyRate: Double = 1100 {
        willSet(newRate) {
            print("환율이 \(currencyRate)에서 \(newRate)으로 변경될 예정입니다")
        }
        
        didSet(oldRate) {
            print("환율이 \(oldRate)에서 \(currencyRate)으로 변경되었습니다")
        }
    }

    var dollar: Double = 0 {
        willSet {
            print("\(dollar)달러에서 \(newValue)달러로 변경될 예정입니다")
        }
        
        didSet {
            print("\(oldValue)달러에서 \(dollar)달러로 변경되었습니다")
        }
    }

    var won: Double {
        get {
            return dollar * currencyRate
        }
        set {
            dollar = newValue / currencyRate
        }
    }    
}

var moneyInMyPocket: Money = Money()

// 환율이 1100.0에서 1150.0으로 변경될 예정입니다
moneyInMyPocket.currencyRate = 1150
// 환율이 1100.0에서 1150.0으로 변경되었습니다

// 0.0달러에서 10.0달러로 변경될 예정입니다
moneyInMyPocket.dollar = 10
// 0.0달러에서 10.0달러로 변경되었습니다

print(moneyInMyPocket.won)
// 11500.0

상속

스위프트의 클래스는 단일 상속으로, 다중 상속을 지원하지 않는다.
열거형, 구조체는 상속이 불가능하며, 클래스, 프로토콜 등에서 가능하다.

문법

정의

class 이름: 상속받을 클래스 이름 {
    /* 구현부 */
}

사용

class 키워드를 사용해 타입 메서드를 만들면 재정의가 가능하다.
final 키워드, static 키워드를 사용하면 재정의할 수 없다.
override 키워드를 사용해 부모 클래스의 메서드를 재정의한다.

class Person {
    var name: String = ""
    
    func selfIntroduce() {
        print("저는 \(name)입니다")
    }
    
    final func sayHello() {
        print("hello")
    }

    static func typeMethod() {
        print("type method - static")
    }

    class func classMethod() {
        print("type method - class")
    }

    final class func finalCalssMethod() {
        print("type method - final class")
    }
}

class Student: Person {
    var major: String = ""
    
    override func selfIntroduce() {
        print("저는 \(name)이고, 전공은 \(major)입니다")
    }
    
    override class func classMethod() {
        print("overriden type method - class")
    }
}

실행

let better: Person = Person()
let hana: Student = Student()

better.name = "better"
hana.name = "hana"
hana.major = "Swift"

better.selfIntroduce()
// 저는 better입니다

hana.selfIntroduce()
// 저는 hana이고, 전공은 Swift입니다

Categories:

댓글남기기