이 포스팅은 Swift 시리즈 20 편 중 5 번째 글 입니다.
목차
클래스 키워드
- 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