Swift 프로그래밍 입문 4

21강 상속

// 스위트프 상속은 클래스, 프로토콜에서만 가능
// 다중상속은 지원하지 않음

import Swift

class Person {
    var  name: String = ""
    func selfIntroduce() {
        print("저는 \(name)입니다.")
    } 
    // final 키워드를 사용하여 재정의하지 못하도록함
    final func sayHello() {
        print("hello")
    }
    // 자식클래스에서 재정의 불가능함
    static func typeMethod() {
        print("type method - statid")
    }
    // 자식클래스에섯 재성의 가능
    class func classMethod() {
        print("type method - class")
    }
    // 재정의 가능한 class 클래스라도 final 키워드를 사용하면 재정의 할 ㅜㅅ 없음
    final class func finalClassMethod() {
        print("type method - final class")
    }
}

class Student: Person {
    // 상속해서 받은 거기때문에 없어도 상관없음
    //var name: String = ""
    var major: String = ""
    override func selfIntroduce() {
        print("저는 \(name)이고, 전공은 \(major)입니다.")
    }
    override class func classMethod() {
        print("overried class method")
    }
}

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

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

yagom.selfIntroduce()
hana.selfIntroduce()

Person.classMethod()
Student.classMethod()

22강 인스턴스 생성/소멸

// 모든 인스턴스는 초기화와 동씨에 모든 프로터피에 유효 값이 할당되어 있어야함
// 프로퍼티에 미리 기본값을 할당하면 인스턴스가 생성됨과 동시에 초기값을 가지게됨
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
    }
}
class PersonC {
    var name: String
    var age: Int
    var nickName: String? //프러퍼티의 초기값이 꼭 필요없는 경우
    
    convenience init(name: String, age: Int, nickName: String){
        self.init(name: name, age: age)
        self.nickName = nickName
    }
    init(name: String, age: Int){
        self.name = name
        self.age = age
    }
}

let zoe: PersonB = PersonB(name: "zoe", age: 20, nickName: "zoe")
let kkk: PersonC = PersonC(name: "kkk", age: 10)

// 암시적 추출 옵셔널
class Puppy {
    var name: String
    var owner: PersonC! // 인스턴스 사용에 꼭 필요하지만 초기값 할당하지 않는 경우
    init(name: String) {
        self.name = name
    }
    func goOut() {
        print("\(name)가 주인 \(owner.name)와 산책합니다.")
    }
}
let happy: Puppy = Puppy(name: "happy")
happy.owner = kkk
happy.goOut()

// 실패가능한 이니셜라이저

class PersonD {
    var name: String
    var age: Int
    var nickName: String?
    
    init?(name: String, age: Int){
        if (0...120).contains(age) == false {
            return nil
        }
        // characters' is unavailable: Please use String directly
        if name.count == 0 {
            return nil
        }
        
        self.name = name
        self.age = age
    }
}

let john: PersonD? = PersonD(name: "john", age: 23)
let error: PersonD? = PersonD(name: "", age:130)
print(john)
print(error)

// 디이니셜라이저
// class 인스턴스가 메모리에서 해제되는 시점에 호출됨
// 인스턴스 해제되는 시점에 해야할일을 구현 가능

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 {
            self.pet?.owner = child
            print("\(name)\(child.name)에게 \(petName)를 인도합니다")
        }
    }
}

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

23강 옵셔널 체이닝과 nil 병합 연산자

// 옵셔널 체이닝은 옵셔널 요소 내부의 프로퍼티
// 또다시 옵녀설이 연속적으로 연결되는 경우 유용함

class Person {
    var name: String
    var job: String?
    var home: Apartment?

    init(name: String) {
    self.name = name
    }
}

class Apartment {
    var buildingNumber: String
    var roomNumber: String
    var `guard`: Person?
    var owner: Person?

    init(dong: String, ho: String) {
        buildingNumber = dong
        roomNumber = ho
    }
}


let yagom: Person? = Person(name: "yagom")
let apart: Apartment? = Apartment(dong: "101", ho: "202")
let superman: Person? = Person(name: "superman")

func guardJob(owner: Person?) {
    if let owner = owner {
        if let home = owner.home {
            if let `guard` = home.guard {
                if let guardJob = `guard`.job {
                    print("우리집 경비원의 직업은 \(guardJob)입니다")
                } else {
                    print("우리집 경비원은 직업이 없어요")
                }
            }
        }
    }
}

guardJob(owner: yagom)

func guardJobWithOptionalChaining(owner: Person?) {
    if let guardJob = owner?.home?.guard?.job {
        print("우리집 경비원의 직업은 \(guardJob)")
    }else{
        print("우리집 경비원은 직업이 없어요")
    }
}
guardJobWithOptionalChaining(owner: yagom)

var guardJob: String

// 앞에 값이 nil 이면 슈퍼맨 출력 
guardJob = yagom?.home?.guard?.job ?? "슈퍼맨"
print(guardJob)

24강 타입 캐스팅

// 인스턴슷의 타입을 확인하는 용도
// 클래스의 인스턴스를 부모,  자식 클래스 타입으로 사용할 수 있는지 확인하는 용도

class Person {
    var name: String  = ""
    func  breath() {
        print(" 숨을쉽니다.")
    }
}

class Studnet: Person {
    var school:  String = ""
    func goToSchool() {
        print("등교를 합니다.")
    }
}

class UniversityStudent: Studnet {
    var major: String = ""
    func goToMT() {
        print("멤버쉽 트레이닝을 갑니다. ")
    }
}

var yagom: Person = Person()
var hana: Studnet = Studnet()
var jason: UniversityStudent = UniversityStudent()

var result: Bool

// True (상속 받았기 때문에)
result = hana is Person

// 업 캐스팅
// 부모클래스의 인스턴스를 사용할 수 있도록 컴파일러에 정보를 알려주는 것
// person 행세 할 수 있도록 업캐스팅
var mike: Person = UniversityStudent() as Person
var jina: Any = Person() // as Any (생략)

// 다운캐스팅
// 자식 클래스의 인스턴스로 사용할 수 있도록 컴파일러에게 인스턴스 타입정보 전환해줌
var optitonalCasted: Studnet?

optitonalCasted = mike as? UniversityStudent
optitonalCasted = jina as? UniversityStudent

// 강제 다운 캐스팅 - 무조건 이 역할을 하도록
optitonalCasted = mike as! UniversityStudent
// optitonalCasted = jina as! UniversityStudent 런타임오류