[TIL]04.26

rbw·2022년 4월 26일
0

TIL

목록 보기
14/98
post-thumbnail

Extensions

형식을 확장하는 Extension에 대해 알아보겠습니다. 이 확장의 대상으로는 클래스, 구조체, 열거형, 프로토콜이 있습니다. 별도의 코드 블록을 붙이는 형식으로 구성합니다.

확장으로 멤버를 추가 하는 것은 가능하지만, 기존 멤버를 오버라이딩 하는 것은 불가능 합니다.

// 문법
// 저장 속성이나 프로퍼티 옵저버는 추가가 불가하다.
// 간편 생상자는 구현이 가능 나머지 생성자는 x
// 생성자 위임이 정상적으로 실행되도록 구현하는 것이 중요
extension Type {
    computedProperty
    computedTypeProperty
    ...
}

extension Type: Protocol, ... {
    requirements
}

// Size 구조체에 비교 연산을 추가해보겠습니다.
extension Size: Equatable {
    public static func == (lhs: Size, rhs: Size) -> Bool {
        return lhs.width == rhs.width && lhs.height == rhs.height 
    }
}

Adding Properties

저장 속성과 프로퍼티 옵저버, 기존 형식에 존재하는 속성을 오버라이딩은 불가능하지만, 계산속성은 확장이 가능하다.

// Date 형식에 년도를 리턴하는 속성을 추가해보겠습니다
extension Date {
    var year: Int {
        let cal = Calendar.current
        // Date 형식 내부에서 날짜 속성에 접근시에는 self로 접근한다
        return cal.component(.year, from: self)
    }
}

Adding Methods

// Double 형식에 섭씨/화씨 온도 변환 메소드 추가
extension Double {
    func toFahrenheit() -> Double {
        return self * 9 / 5 + 32
    }
    func toCelsius() -> Double {
        return (self - 32) * 5 / 9 
    }
    // 유지보수 시에 구현한 인스턴스 메소드만 변경하면 되므로, 더 효율적인 코드이다.
    static func converToFaherenheit(from: celsius: Double) -> Double {
        return celsius.toFahrenheit()
    }
}

Adding Initializers

// UIColor 클래스에 RGB 파라미터를 받는 생성자 추가
extension UIColor {
    convenience init(red: Int, green: Int, blue: Int) {
        self.init(red: CGFloat(red) / 255, green: CGFloat(green) / 255, blue: CGFloat(blue) / 255, alpha: 1.0)
    }
}

// 파라미터들의 범위를 검증 하는 코드 추가 버전
// assert는 else라면 종료시키고 메시지를 띄워준다. 프로그램에 영향을 끼치지 않는 장점  !
extension UIColor {
   convenience init(red: Int, green: Int, blue: Int) {
       assert(red >= 0 && red <= 255, "Invalid red component")
       assert(green >= 0 && green <= 255, "Invalid green component")
       assert(blue >= 0 && blue <= 255, "Invalid blue component")

       self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0)
   }
}

Adding Subscripts

// String 형식에 정수 인덱스 처리하는 서브스크립트 추가
extension String {
    subscript(idx: Int) -> String? {
        guard (0..<count).contains(idx) else {
            return nil
        }

        let target = index(startIndex, offsetBy: idx)
        return String(self[target])
    }
}

Protocol

이는 단순하게 설명하자면, 형식에서 공통적으로 제공하는 멤버 목록 입니다. 프로토콜을 채용한 형식은 프로토콜의 필수 멤버들을 구현해야만 한다. 이것들을 요구사항(Requirements)이라고 부르기도 한다.

// 문법
protocol ProtocolName {
    propertyRequirements
    methodRequirements
    initializerRequirments
    subscriptRequirements
}
protocol ProtocolName: Protocol, ... {}

protocol Something() {
    // 이 프로토콜을 채용하면 아래 함수를 구현해야만 하고, 프로토콜을 따른다고도 명시해야 한다
    func doSomething()
}

// 클래스에만 프로토콜을 적용하는 방법
// 해당 프로토콜은 클래스에서만 채용할 수 있다.
protocol ProtoclName: AnyObject {

}

Property Requirements

// 프로퍼티 선언 문법
protocol ProtocolName {
    // var로만 선언한다
    // set이 없다면 이 프로토콜을 채용한 곳에서는 읽기전용 속성을 선언하거나, 
    // 읽기 쓰기 모두 가능한 속성으로 선언해도 문제가 없다.
    var name: Type {get set}
    static var name: Type {get set}
}

// 예시 코드
protocol Figure {
    // 읽기와 쓰기가 가능해야한다 따라서 let = name = "hi" 는 에러
    var name: String {get set}

    // 형식 속성을 따라야 하는 경우
    // 이 프로토콜을 채용한 곳에서는 해당 변수에 static을 선언해야 한다
    // class인 경우 class 키워드로 선언하여도 문제는 없다.
    static var name: String {get set}
}

struct Circle: Figure {
    var name: String {
        get {
            return "Circle"
        }
        set {
            ...
        }
    }
}

// 문제 x
class Circle: Figure {
    class var name: String {
        get {
            
        }
        set {

        }
    }
}

Method Requirements

프로토콜에서 메소드를 선언할 때는 메소드의 헤드부분만 선언하고 바디는 필요없다.

// 문법
protocol ProtocolName {
    func name(param) -> ReturnType
    // 타입 메소드
    static func name(param) -> ReturnType

    // 값 형식에서 채용할 수 있고, 메소드 안에서 속성값을 변경해야 한다면 mutating 키워드 사용
    mutating func name(param) -> ReturnType
}

Initializer Requirements

// 문법
protocol ProtocolName {
    init(param)
    init?(param)
    init!(param)
}

class Circle: Figure {
    var name: String

    // 클래스는 상속을 고려해야하고, 모든 서브클래스에서 
    // 프로토콜의 요구 사항을 충족시켜야 하기 때문에 required 키워드를 추가 하여야한다.
    // 또는 final 클래스로 선언하면 상속을 하지 않으므로 error가 없다
    required init(n: String) {
        name = n
    }
}

Subscript Requirements

프로토콜에도 서브스크립트 구문을 추가할 수 있다.

// 문법
protocol ProtocolName {
    subscript(param) -> ReturnType {get set}
}

Protocol Types

프로토콜도 1급 객체 입니다. 따라서 독립적인 형식이므로 파라미터, 리턴형 등으로 사용이 가능합니다.

protocol Resettable {
    func reset()
}

class Size: Resettable {
    var width = 0.0
    var height = 0.0
    
    func reset() {
        width = 0.0
        height = 0.0
    }
}

// 업캐스팅과 유사하고, 프로토콜에서 선언된 것들만 사용이 가능하다.ㅋ₩
let r: Resettable = Size()

Protocol Conformance

프로토콜 적합성은 타입 캐스팅 연산자를 사용해서 확인합니다.

// 문법
instance is ProtocolName

instance as ProtocolName
instance as? ProtocolName
instance as! ProtocolNam

let r = Size() as Resettable // Size() 인스턴스이지만 r의 타입은 프로토콜 형식을 따른다

프로토콜을 사용하면 상속과 비슷한 패턴도 구현이 가능하다.

protocol Figure {
    ...
}
// 위 프로토콜을 채택하는 것들은 아래와 같이 하나에 다 저장할 수 있다.
// Figure 형식으로 캐스팅하여 저장한다
// 하지만 Figure 프로토콜의 멤버들에만 접근이 가능하다
let list: [Figure] = [t, r, c]

// 위의 Protocol 멤버 말고 접근을 하려면
// 원래 형식으로 캐스팅 해서 접근해야 한다 
for item in list {
    if let c = item as? Circle {
        c.radius
    }
}

Protocol composition

여러 프로토콜을 병합하는 것에 대해서 알아보겠습니다.

// 문법
protocol & protocol
class & protocol

// 두 프로토콜을 모두 충족시키는 임시 형식으로 선언된다 
// Size()는 해당 프로토콜 전부 충족해야한다.
var rpp: Resettable & Printable = Size() 

// 모든 서브클래스를 저장할 수 있게 해준다.
var rp: Circle & Resttable = Circle()

Optional Requirements

선택적 요구사항에 대해서 알아보겠습니다. 여기서 옵셔널은 선택적이라는 의미입니다.

// 문법
// @objc를 붙인 멤버들은 옵셔널 형식이다. 따라서 호출하려면 옵셔널 체이닝을 활용해야한다
@objc protocol ProtocolName {
    @objc optional requirements 
}
// 이는 AnyObject를 상속하므로 Class에서만 사용이 가능하다.

프로토콜에 멤버들을 필수로 구현하지 않아도 되게끔 만들어준다

Protocl Extension

프로토콜의 확장은, 이 프로토콜을 채용한 형식에 구현을 추가하는것과 같다. 코드의 양을 대폭 줄일 수 있다.

protocol Figure {
    var name: String { get }
    func draw()
}

extension Figure {
    func draw() {
        print("draw figure")
    }
}
// 확장을 하였으므로, draw가 구현되어 있다고 본다
// 형식에서 똑같은 이름 파라미터로 새로 구현하면 더 높은 우선순위를 가지므로,
// 프로토콜 확장에서 구현한 메소드는 무시가 된다.
struct Rectangle: Figure {
    var name = ""
}

// 조건을 부여해서 확장하는 방법
// 여기서 Self 는 프로토콜을 채용하는 형식을 의미한다
extension Figure where Self: Equatable {
    func draw {
        print("draw figure")
    }
}
// 이는, Equatable을 만족하는 형식에 이 프로토콜 확장을 적용시키므로 위의 구조체는 해당이 안된다
// 따라서 draw를 호출할 수 없다.

Equatable

매우 중요한 Equatable 프로토콜에 대해서 알아보겠습니다.

값의 동일성을 비교할 수 있는 타입이라면 반드시 구현해야 한다

연관 값이 선언되지 않은 열거형은 Equatable 구현이 자동으로 추가되고, 모든 연관 값의 형식이 Equatable을 구현한 형식인 경우에도 자동으로 추가한다. 연관 값이 선언되어 있다면 이 프로토콜을 채용하는것 만으로 컴파일러가 나머지를 알아서 구현 해준다.

구조체에 선언된 속성들의 형식이 Equtable을 따르는(Int, String...)형식이라면 Equtable을 추가하는 것으로 비교가 가능하다.

// 클래스에서 == 연산자 구현
extension Person {
    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.name == rhs.name && lhs.age == rhs.age
    }
}

Equtable을 구현할 때 주의점은 모든 속성을 비교해야 한다는 점이 있다

Hashable

해쉬란 단방향 암호 기법이고, 어떤 값을 고정된 길이의 문자열로 바꾸거나 고유한 정수값으로 바꾸는 것이다. 이런 행위를 해싱이라고 한다. 사용자 인증 등에 주로 사용합니다.

이 프로토콜을 채용하면 값의 유효성, 검색 속도의 향상의 장점이 있다.

자동으로 구현되는 조건에 대해서 알아보겠습니다. 먼저, 열거형 선언에 연관값이 포함되어 있지 않다면, 자동으로 구현된다. 이 때는 선언도 필요 x. 연관값이 포함되어 있는 경우에는, 선언을 해야 한다.

구조체의 선언된 속성들이 기본 형식이면 Hashable을 자동으로 구현해준다 이 때는 선언해줘야함.

클래스는 직접 구현을 해야한다. Hashable은 Equatable을 상속하므로 == 연산자도 구현 해줘야함.

extension Person: Hashable {
    // == 구현 생략
    func hash(into hasher: inout Hasher) {
        hasher.combine(name)
        hasher.combine(age)
    }
}

Comparable

이는 값의 크기를 비교하거나 정렬할 떄 필수로 구현해야하는 프로토콜입니다.

열거형 선언에서 연관값이 없다면 프로토콜을 채용한다고 선언만 하면 자동으로 구현이 된다. 내부 형식이 위 프로토콜을 구현하고 있는 경우에도 선언만 하면 된다.

이 프로토콜도 Equatable을 상속하고 있다.

// 직접 구현
extension Weekdat: Comparable {
    static func < (lhs: Weekday, rhs: Weekday) -> Bool {
        return lhs.rawValue < rhs.rawValue
    }
}
profile
hi there 👋

0개의 댓글