이 포스팅은 Swift 시리즈 20 편 중 5 번째 글 입니다.

  • Part 1 - 01: Optional, Any, AnyObject, nil
  • Part 2 - 02: struct, class, enum
  • Part 3 - 03: Closure
  • Part 4 - 04: Property
  • Part 5 - This Post
  • Part 6 - 06: 옵셔널 체이닝과 nil 병합
  • Part 7 - 07: 타입 캐스팅
  • Part 8 - 08: assert, guard
  • Part 9 - 09: Protocol
  • Part 10 - 10: Extention
  • Part 11 - 11: 오류처리
  • Part 12 - 12: 고차함수
  • Part 13 - 13: ARC(Automatic Reference Counting)
  • Part 14 - 14: Access control, Access modifier
  • Part 15 - 15: Generics
  • Part 16 - 16: Optional에 대한 깊은 이해
  • Part 17 - 17: Lazy Variables
  • Part 18 - 18: Enumeration
  • Part 19 - 19: Initialization
  • Part 20 - 20: Concurrency
▼ 목록 보기

클래스 키워드

  • final : 재정의를 막을 수 있다.
  • static : 이걸 사용해서 타입 메서드를 만들면 재정의 불가능
  • class : 이걸 사용해서 타입 메서드를 만들면 재정의 가능
  • final class : 이런식으로 쓰면 타입 메서드 인데, 재정의 하지마 = 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 finalClassMethod() {
        print("type method - final class")
    }
}


class Student: Person {
    var major: String = ""
    
    override func selfIntroduce() {
        print("저는 \(name)이고, 전공은 \(major)입니다.")
    }
    
//    재정의 불가
//    override func sayHello() { }
    
//    재정의 불가
//    override static func typeMethod(){ }
    
//    재정의 가능
    override class func classMethod() {
        print("inheritance type method - class")
    }
    
//    재정의 불가
//    override final class func finalClassMethod() { }
}
  • final 키워드를 중간에 상속받다가 특정 클래스에 붙여버리면, 그 자식들은 모두 상속 받을 수 없다.
let yagom: Person = Person()
let hana: Student = Student()

yagom.name = "wansik"
hana.name = "hana"
hana.major = "Swift"

yagom.selfIntroduce()
// 저는 wansik입니다

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

Person.classMethod()
// type method - class

Person.typeMethod()
// type method - static

Person.finalClassMethod()
// type method - final class


Student.classMethod()
// overriden type method - class

Student.typeMethod()
// type method - static

Student.finalClassMethod()
// type method - final class

initializer

  • 인스턴스가 생성됨과 동시에 초기값을 지정할 수 있도록 하는 방법
  • 생성자에는 두가지 방법이 있다.
    • 지정 이니셜라이저
      • 클래스의 모든 저장 프로퍼티를 초기화한다.
      • 부모 클래스의 생성자를 호출할 수 있다.
      • 클래스 내부에는 반드시 한개 이상의 지정 이니셜라이저가 있어야 한다.
      • 즉, 대부분의 언어서 기본적으로 사용하는 생성자를 말한다.
    • 편의 이니셜라이저
      • 근데 모든 저장 프로퍼티를 저장하는 것은 떄로 불편하다.
      • 그렇기 때문에 일부 매개 변수의 기본값을 설정하여 초기화하는 방법이다.
      • convenience라는 키워드를 사용하여 구현할 수 있다.

초기값이 꼭 필요한 경우

class PersonA {
    var name: String
    var age: Int
    var nickName: String
    
    // 생성자
    init(name: String, age: Int, nickName: String) {
        self.name = name
        self.age = age
        self.nickName = nickName
    }
}

let wansik: PersonA = PersonA(name: "wansik", age: 20, nickName: "wansook")

초기값이 꼭 필요하지 않은 경우

  • 값이 없을 수 있다는 것이니, 없는 상태도 반영해줄 수 있는 optional 사용
class PersonB {
    var name: String
    var age: Int
    var nickName: String?
    
    // 초기값이 모두 있는 생성자
    init(name: String, age: Int, nickName: String) {
        self.name = name
        self.age = age
        self.nickName = nickName
    }
    
    // 초기값이 몇개 없는 생성자
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

let wansik2: PersonB = PersonB(name: "wansik2", age: 21)
  • 결국 두개의 생성자를 만들어 주어야 함

암시적 추출 옵셔널(!) 사용 예

class Puppy {
    var name: String
    var owner: PersonB! // !는 옵셔널은 맞는데, 이거 nil이면 컴파일러가 못잡음. 즉 무조건 있어야 한다는 것
    
    init(name: String) { // 그런데 당장 초기화할 때, 값은 할당하기 싫어
        self.name = name
    }
    
    func goOut() {
        print("\(name)가 주인 \(owner.name)와 산책을 합니다")
    }
}

let happy: Puppy = Puppy(name: "happy") // 강아지는 주인없이 산책하면 안돼요!
//happy.goOut() // 주인이 없는 상태라 오류 발생
happy.owner = wansik2 // 무조건 넣어주어야 함
happy.goOut()
// happy가 주인 wansik2와 산책을 합니다

실패할 수 있는 생성자

매개변수로 전달하는 초기값이 잘못되면 생성에 실패할 수 있다. 실패할 경우 nil을 반환한다. 이렇게 초기화에 실패할 경우를 대비할 경우 초기화 함수 자체도 init? 과 같이 선언한다. 당연히 반환 타입은 옵셔널이다.

class PersonC {
    var name: String
    var age: Int
    
    // 초기값이 모두 있는 생성자
    init?(name: String, age: Int) {
        if (0...120).contains(age) == false{
            return nil // 나이가 120살을 넘거나 음수인 경우
        }
        if name.count == 0 {
            return nil // 이름이 빈 스트링인 경우
        }
        self.name = name
        self.age = age
    }
}

let wansik3: PersonC? = PersonC(name: "", age: 21) // nil
let wansik4: PersonC? = PersonC(name: "wansik4", age: -1) // nil

Deinitializer

  • 인스턴스가 메모리에서 해제되는 시점에 호출됨
  • 해제 되면서 해야하는 일을 구현할 수 있음
  • 매개변수를 가질 수 없음
  • 자동으로 호출되는 것. 내가 임의로 호출할 수 없음
  • Class 타입에서만 구현할 수 있음
  • 메모리에서 해제되는 시점은 ARC(Automatic Reference counting) 규칙에 의해 결정됨
class PersonE {
    var name: String
    var pet: Puppy?
    var child: PersonC
    
    init(name: String, child: PersonC) {
        self.name = name
        self.child = child
    }
    
    // 인스턴스가 메모리에서 해제되는 시점에 자동 호출
    deinit {
        if let petName = pet?.name {
            print("\(name)\(child.name)에게 \(petName)를 인도합니다")
            self.pet?.owner = child
        }
    }
}

var donald: PersonE? = PersonE(name: "donald", child: jenny)
donald?.pet = happy
donald = nil

Reference

iOS 프로그래밍을 위한 스위프트 기초