[TIL]04.25

rbw·2022년 4월 25일
0

TIL

목록 보기
13/97
post-thumbnail

Type Casting

Type Check Opertor

타입을 확인하는 연산자 expression is Type 으로 작성한다. 오른쪽 Type이 슈퍼클래스여도 True를 리턴한다.

let num = 123

num is Int // true
num is Double // false

Type Casting Operator

표현식이 오른쪽 형식과 호환된다면 오른쪽 형식으로 캐스팅된 인스턴스를 리턴합니다. 새로운 인스턴스가 리턴되는 것은 아니고, 오른쪽 피연산자 형식에 있는 멤버만 접근할 수 있는 임시 인스턴스를 리턴한다.

// 컴파일 때 판단함
expression as Type // Bridging : 서로 호환된다는 것을 캐스팅한다는 의미

// 런타임 때 판단함
expression as? Type // Conditional Cast 실패시 nil 리턴
expression as! Type // Forced Cast

// as를 활용해서 업캐스팅하는것도 가능하다
var upcasted: Figure = s
upcasted = s as Figure

Polymorphism

업캐스팅된 인스턴스를 통해서 메소드를 호출하여도 실제 형식에서 오버라이드한 메소드가 호출됩니다.

상속계층으로 이루어진 인스턴스들을 배열에 넣으면 배열의 타입은 제일 위 계층 형식을 따른다.

// 삼각형 직사각형 정사각형 원 의 인스턴스들, 배열의 타입은 Figure이다
let list = [t, r, s, c]

for item in list {
    // 각각의 draw를 호출함. --> 다형성
    item.draw()

    // 멤버에 접근하려면 해당 클래스로 다운 캐스팅이 되는지 체크한다.
    if let c = item as? Circle {
        c.radius
    }
}

Any and AnyObject

범용 자료형에 대해서 알아보겠습니다.

Any는 모든 형식을 저장할 수 있고, AnyObject는 모든 클래스형식을 저장 가능하다.

var data: Any = 1
data = 2.3 
data = "str"
data = [1, 2, 3]
// 다 허용 가능

var obj: AnyObject = NSStrign()
obj = 1 // error, 참조형식을 저장 가능하다.

// Any에서는 메소드나 멤버들을 제공 못하므로, 아래와 같이 타입 캐스팅을 활용해야한다
if let str = data as? String {
    print(str.count )
}

타입 캐스팅패턴 활용

위의 경우를 판단하기 쉽게 switch문을 활용한다

switch data {
case let str as String:
    print(str.count)
case let list as [Int]:
    print(list.count)
default:
    break
}

Overloading

오버로딩은 하나의 형식에서 동일한 이름을 가진 다수의 멤버를 구현할때 사용합니다.

// 파라미터가 다르므로 다른 함수라고 본다.
func process(value: Int) {
    ...
}

func process(value: String) {
    ...
}

오버로딩의 규칙

  1. 함수 이름이 동일하면 파라미터 수로 식별한다
  2. 함수 이름, 파라미터 수가 동일하면 파라미터 자료형으로 식별
  3. 함수 이름, 파라미터가 동일하면 인자라벨로 식별
  4. 3번까지 동일하다면, 리턴형으로 식별한다

Method Overloading

struct Rectangle {
    func area() -> Double {
        return 0.0
    }
    static func area() -> Double {
        return 0.0
    }
}

// 인스턴스이름과 형식이름으로 호출하는 메소드는 완전히 동일하여도 상관이없다.
let r = Rectangle()
r.area()
Rectangle.area()

Initializer and Deinitializer

초기화와 소멸자에 대해서 알아보겠습니다.

Initializer

초기화는 모든 속성을 기본값으로 설정하여서 인스턴스를 기본 상태를 만들어줍니다.

class Position {
    // 1. 선언과 동시에 기본값을 저장하는 방식
    var x = 0.0

    // 2. 생성자를 활용하는 방식
    var y: Double
    init() {
        y = 0.0
    }
    
    // 옵셔널 형식은 error가 없다. 값이 없다면 nil로 설정되기 때문이다.
    var z: Double? 

    var width = 0.0
    var height = 0.0
    // 생성자 안에서 생성자를 호출하는 패턴 initializer delegation이라고함
    init(width: Double, height: Double) {
        self.width = width
        self.height = width
    }
    convenience init(value: Double) {
        self.init(width: value, height: value)
    }
}

Memberwise Initializer

구조체가 제공하는 멤버와이즈 이니셜라이저에대해 알아보겠습니다.

struct Num {
    var a: Int
    var b: Int
    var c: Int
}

// 구조체는 초기화를 선언하지 않아도 속성에 초기화하는 기능을 제공해준다. (클래스는 x)s
let c = Num(a: 0, b: 1, c: 2)

// 위 기능인 Memberwise initializer와 커스텀 생성자를 같이 사용하려면 extension을 활용한다
extension Num {
    init(value: Int) {
        a = value
        b = value
        c = value
    }
}

Class Initialzier

클래스의 생성자에는 지정 생성자와 편리 생성자가 존재합니다.

// Designated Initializer, 모든 속성을 초기화 하여야 한다
init(parameters) {
    initialization
}
// Convenience Initializer
convenience init(parameters){
    initialization
}


class Position {
    var x: Double
    var y: Double
    
    init (x: Double, y: Double) {
        self.x = x
        self.y = y
    }
}

Initializer Inheritance

생성자의 상속의 조건에대해서 알아보겠습니다.

기본적으로 슈퍼클래스에서 구현한 생성자는 서브클래스로 상속되지 않습니다.

swift는 두가지 규칙에 의해서 이니셜라이저를 상속합니다

  1. 서브클래스의 모든 속성이 기본값으로 초기화 되어있고, 지정 생성자를 구현하지 않았다면, 슈퍼클래스에 있는 모든 지정 생성자가 상속됩니다.

  2. 서브클래스가 모든 지정 생성자를 상속받았거나 오버라이딩 했다면, 모든 편리 생성자가 상속됩니다.

class Figure {
    var name: String

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

    convenience init() {
        self.init(name: "unknown")
    }
}

class Retangle {
    var width: Double = 0.0
    var height: Double = 0.0

    // 상속계층에 포함되어있기 때문에, 슈퍼클래스의 지정 생성자를 호출해야한다
    // 호출하기 전에 상속받은 name에 접근하면 error
    init(name: String, width: Double, height: Double) {
        self.width = width
        self.height = height
        
        super.init(name: name)
    }

    override init(name: String){
        width = 0
        height = 0
        super.init(name: name)
    }

    // 편리 생성자는 항상 동일한 클래스의 다른 생성자를 호출합니다.
    // 그리고 슈퍼클래스의 생성자를 직접 호출하는것은 불가능 합니다.
    convenience init() {
        self.init(name: "unknown")
    }
}

Required Initializer

필수 생성자에 대해서 알아보겠습니다. 이를 선언하면 서브클래스에서 반드시 동일한 생성자를 직접 구현해야합니다.

// 문법
required init(parameters){
    initializaiton
}

class Figure {
    var name: String

    required init(name: String) {
        self.name: name
    }
}
class Rectangle: Figure {
    var width

    required init(name: String){
        width = 0.0
        super.init(name: name)
    }
}

Initializer Delegation

이는 중복을 최대한 제거하고 모든 코드를 효율적으로 초기화하기 위해 사용됩니다. 값 형식과 참조 형식에서 서로 다른 규칙으로 구현됩니다.

값 형식은 상속이 불가하고, 생성자 종류가 하나이므로 상대적으로 간단합니다.

struct Size {
    var width: Double
    
    init(w: Double) {
        width = w
    }
    // Delegation, 다른 이니셜라이저를 호출하는것을 의미
    // 두번째 생성자가 자신의 역할을 첫번째 생성자에게 위임한다는 뜻 
    // 유지보수 측면에서 장점이 있다, 문제가 있을시 해당 생성자만 바꾸면 된다.
    init(value: Double) {
        self.init(w: value)
    }
}

참조 형식에서는 상속을 할 수 있고, 구현할 수 있는 생성자가 두가지이므로 상대적으로 복잡합니다.

클래스의 생성자 위임은 세가지 규칙이 있습니다.

  1. 지정 생성자는 반드시 슈퍼 클래스의 지정 생성자를 호출해야 합니다 : Delegate Up 이라고 부름
  2. 편리 생성자는 동일한 클래스의 생성자를 호출해야한다 : Delegate Across
  3. 편리 생성자는 최종적으로 동일한 클래스에 있는 지정 생성자를 호출해야한다.
class Rectangle: Figure {
    var width = 0.0
    var height = 0.0

    init(w: Double, h: Double) {
        width = w
        hegiht = h
    }
    convenience init(value: Double){
        self.init(w: value, h:value)
    }
}
// 헷갈릴 수 있는 부분,
// 아무런 속성도 없으므로, 슈퍼클래스의 지정생성자를 상속받고, 그 생성자를 편리 생성자가 호출하고 있는 모습
class Square: Rectangle {
    convenience init(value: Double) {
        self.init(w:value, h: value)
    }
    
    // 이 편리 생성자는 위의 생성자를 호출한다.
    convenience init() {
        self.init(value: 0.0)
    }
}

클래스 초기화 방식

두 단계로 실행됩니다.

  1. 선언되어 있는 모든 속성이 초기화 됩니다, 초기화는 서브클래스부터 슈퍼클래스로 상속 계층을 따라올라갑니다.
  2. 슈퍼클래스에서 서브클래스로 내려옵니다. 1단계에서 할 수 없었던 부가적인 초기화 작업을 진행합니다.

코드를 살펴보면,

init(w: Double, h: Double) {
    width = w
    hegiht = h
    super.init() // 위에 있는 코드들이 1단계에서 실행되고 이 코드 아래가 2단계를 실행한다.
}

Failable initializer

실패가 가능한 생성자에 대해서 알아보겠습니다. 이러한 경우는 파일을 읽을 때 주로 사용합니다. 실패시 nil을 리턴합니다.

// 문법
init?(parameters) {
    initialization
}
// 강제 추출되어서 리턴한다. 실패시 크래쉬 발생
init!(parameters) {
    initialization
}

구현 규칙으로는, 동일한 파라미터를 가진 Non-Failable 생성자를 생성을 하면 에러가 난다. 슈퍼클래스의 Failable 생성자는 서브 클래스에서 어떠한 생성자로도 상속이 가능하다. 하지만, Non-Failable 상위 구현 호출을 할 때 강제 추출 연산을 사용해야 합니다.

Deinitializer

소멸자에 대해서 알아 보겠습니다. 이는 인스턴스가 해제되기 전에 호출 됩니다.

// 클래스 전용이고, 하나만 존재해야 하고, 자동으로 호출된다.
deinit {
    Deinitialization
}
profile
hi there 👋

0개의 댓글