본문 바로가기

iOS-Study

iOS Study : 6주차 - 타입 확장(프로토콜, 익스텐션)

프로토콜

  • 특정 역할을 수행하기 위한 메서드, 프로퍼티, 이니셜라이저 등의 요구사항을 정의한다.
  • 구조체, 클래스, 열거형은 프로토콜을 채택해서 특정 기능을 수행하기 위한 프로토콜의 요구사항을
    실제로 구현할 수 있다.
  • 어떤 프로토콜의 요구사항을 모두 따르는 타입은 그 프로토콜을 준수한다고 표현한다.
  • 프로토콜의  요구사항을 충족시키려면 프로토콜이 제시하는 기능을 모두 구현해야 한다.
  • 프로토콜은 기능을 정의하고 제시 할 뿐, 스스로 기능을 구현하지는 못한다.

정의 문법

protocol 프로토콜 이름 {
    /* 정의부 */
}

 

프로토콜 구현

protocol Talkable {
    // 프로퍼티 요구
    // 프로퍼티 요구는 항상 var 키워드를 사용합니다
    // get은 읽기만 가능해도 상관 없다는 뜻이며
    // get과 set을 모두 명시하면
    // 읽기 쓰기 모두 가능한 프로퍼티여야 합니다
    var topic: String { get set }
    var language: String { get }
    
    // 메서드 요구
    func talk()
    
    // 이니셜라이저 요구
    init(topic: String, language: String)
}

 

프로토콜 채택 및 준수

// Person 구조체는 Talkable 프로토콜을 채택했습니다
struct Person: Talkable {
    // 프로퍼티 요구 준수
    var topic: String
    let language: String
    
    // 읽기전용 프로퍼티 요구는 연산 프로퍼티로 대체가 가능합니다
//    var language: String { return "한국어" }

    // 물론 읽기, 쓰기 프로퍼티도 연산 프로퍼티로 대체할 수 있습니다
//    var subject: String = ""
//    var topic: String {
//        set {
//            self.subject = newValue
//        }
//        get {
//            return self.subject
//        }
//    }

    // 메서드 요구 준수
    func talk() {
        print("\(topic)에 대해 \(language)로 말합니다")
    }
    
    // 이니셜라이저 요구 준수
    init(topic: String, language: String) {
        self.topic = topic
        self.language = language
    }
}

 

프로토콜 상속

  • 프로토콜은 하나 이상의 프로토콜을 상속받아 기존 프로토콜의 요구사항보다 더 많은 요구사항을 추가할 수 있다.
  • 프로토콜 상속 문법은 클래스의 상속 문법과 유사하지만, 클래스와 다르게 다중상속이 가능하다.
protocol 프로토콜 이름: 부모 프로토콜 이름 목록 {
 /* 정의부 */
 }
 
protocol Readable {
    func read()
}
protocol Writeable {
    func write()
}
protocol ReadSpeakable: Readable {
    func speak()
}
protocol ReadWriteSpeakable: Readable, Writeable {	//read(), write() 포함
    func speak()
}
struct SomeType: ReadWriteSpeakable {	//모든 기능을 구현해야 함
    func read() {
        print("Read")
    }
    func write() {
        print("Write")
    }
    func speak() {
        print("Speak")
    }
}

 

클래스 상속과 프로토콜

클래스에서 상속과 프로토콜을 채택을 동시에 하려면 상속받으려는 클래스를 먼저 명시하고 뒤에 채택할 프로토콜 목록을 작성한다.

class SuperClass: Readable {
    func read() { }
}
class SubClass: SuperClass, Writeable, ReadSpeakable {
    func write() { }
    func speak() { }
}

 

프로토콜 준수 확인

is, as 연산자를 사용해서 인스턴스가 특정 프로토콜을 준수하는지 확인할 수 있다.

let sup: SuperClass = SuperClass()
let sub: SubClass = SubClass()
var someAny: Any = sup
someAny is Readable // true
someAny is ReadSpeakable // false
someAny = sub
someAny is Readable // true
someAny is ReadSpeakable // true
someAny = sup
if let someReadable: Readable = someAny as? Readable {
    someReadable.read()
} // read
if let someReadSpeakable: ReadSpeakable = someAny as? ReadSpeakable {
    someReadSpeakable.speak()
} // 동작하지 않음
someAny = sub
if let someReadable: Readable = someAny as? Readable {
    someReadable.read()
} // read

 

익스텐션

  • 익스텐션은 구조체, 클래스, 열거형, 프로토콜 타입에 새로운 기능을 추가할 수 있는 기능이다.
  • 기능을 추가하려는 타입의 구현된 소스 코드를 알지 못하거나 볼 수 없다 해도, 타입만 알고 있다면 그 타입의 기능을 확장할 수도 있다.
  • 스위프트의 익스텐션이 타입에 추가할 수 있는 기능
    • 연산 타입 프로퍼티 / 연산 인스턴스 프로퍼티
    • 타입 메서드 / 인스턴스 메서드
    • 이니셜라이저 
    • 서브스크립트
    • 중첩 타입
    • 특정 프로토콜을 준수할 수 있도록 기능 추가
  • 익스텐션은 타입에 새로운 기능을 추가할 수는 있지만, 기존에 존재하는 기능을 재정의할 수는 없다.

정의 문법

extension 확장할 타입 이름 {
    /* 타입에 추가될 새로운 기능 구현 */
}

익스텐션은 기존에 존재하는 타입이 다른 프로토콜을 더 채택할 수 있도록 확장할 수도 있다.

extension 확장할 타입 이름: 프로토콜1, 프로토콜2, 프로토콜3... {
    /* 프로토콜 요구사항 구현 */
}

 

익스텐션 구현

연산 프로퍼티 추가

xtension Int {
    var isEven: Bool {
        return self % 2 == 0
    }
    var isOdd: Bool {
        return self % 2 == 1
    }
}
print(1.isEven) // false
print(2.isEven) // true
print(1.isOdd)  // true
print(2.isOdd)  // false
var number: Int = 3
print(number.isEven) // false
print(number.isOdd) // true
number = 2
print(number.isEven) // true
print(number.isOdd) // false

 

메서드 추가

extension Int {
    func multiply(by n: Int) -> Int {
        return self * n
    }
}
print(3.multiply(by: 2))  // 6
print(4.multiply(by: 5))  // 20
number = 3
print(number.multiply(by: 2))   // 6
print(number.multiply(by: 3))   // 9

 

이니셜라이저 추가

extension String {
    init(int: Int) {
        self = "\(int)"
    }
    init(double: Double) {
        self = "\(double)"
    }
}
let stringFromInt: String = String(int: 100)
// "100"
let stringFromDouble: String = String(double: 100.0)
// "100.0"