개발하는 뚝딱이

[swift] 프로토콜 초기구현 본문

swift

[swift] 프로토콜 초기구현

개발자뚝딱이 2020. 9. 28. 14:37

 

책 <스위프트 프로그래밍 3판 ;야곰 지음> 을 정리한 글입니다.

 


 

 

 

 

1. 프로토콜 초기구현

//
//  main.swift
//  hello
//
//  Created by Jin on 2020/09/28.
//

import Foundation

protocol Receiveable {
    func received(data: Any, from: Sendable)
}

extension Receiveable {
    // 메시지를 수신합니다
    func received(data: Any, from: Sendable) {
        print("\(self) received \(data) from \(from)")
    }
}

// 무언가를 발신할 수 있는 기능
protocol Sendable {
    var from: Sendable { get }
    var to: Receiveable? { get }
    
    func send(data: Any)
    
    static func isSendableInstance(_ instance: Any) -> Bool
}

extension Sendable {
    // 발신은 발신 가능한 객체, 즉 Sendable 프로토콜을 준수하는 타입의 인스턴스여야 함
    var from: Sendable {
        return self
    }
    
    // 메시지를 발신
    func send(data: Any) {
        guard let receiver: Receiveable = self.to else {
            print("Message has no receiver")
            return
        }
        
        // 수신 가능한 인스턴스의 received 메서드를 호출
        receiver.received(data: data, from: from)
    }
    
    static func isSendableInstance(_ instance: Any) -> Bool {
        if let sendableInstance: Sendable = instance as? Sendable {
            return sendableInstance.to != nil
        }
        
        return false
    }
}

// 수신, 발신이 가능한 Message 클래스
class Message: Sendable, Receiveable {
    var to: Receiveable?
}

// 수신, 발신이 가능한 Mail 클래스
class Mail: Sendable, Receiveable {
    var to: Receiveable?
}

// 두 Message 인스턴스를 생성
let myPhoneMessage: Message = Message()
let yourPhoneMessage: Message = Message()

myPhoneMessage.send(data: "Hello") // Message has no receiver
myPhoneMessage.to = yourPhoneMessage
myPhoneMessage.send(data: "Hello") // Message received Hello from Message

// Mail 인스턴스를 두 개 생성합니다
let myMail: Mail = Mail()
let yourMail: Mail = Mail()

myMail.send(data: "Hi") // Message has no receiver

myMail.to = yourMail
myMail.send(data: "Hi") // Mail received Hi from Mail
myMail.to = myPhoneMessage
myMail.send(data: "Bye") // Message received Bye from Mail

// String은 Sendable 프로토콜을 준수하지 않음
Message.isSendableInstance("Hello") // false

// Message와 Mail은 프로토콜을 준수함
Message.isSendableInstance(myPhoneMessage) // true

// yourPhoneMessage는 to 프로퍼티가 설정되지 않아서 보낼 수 없는 상태
Message.isSendableInstance(yourPhoneMessage) // false
Mail.isSendableInstance(myPhoneMessage) // true
Mail.isSendableInstance(myMail) // true

 

- 프로토콜을 정의할 때는 그 프로토콜을 채택한 타입에서 구현해줘야 하는 프로토콜의 요구사항을 구현할 수 없음

- 단지 요구사항을 정의만 할 수 있음

- 그러나 프로토콜의 익스텐션에는 프로토콜이 요구하는 기능을 실제로 구현해줄 수 있음

- 다만 익스텐션에는 저장 프로퍼티를 구현할 수 없으므로 저장 프로퍼티는 각각의 타입에 직접 구현해줘야 함

- 프로토콜과 익스텐션을 결합하면 코드의 재사용이 월등히 증가함

 

- 프로토콜의 요구사항을 익스텐션을 통해 구현한 것을 프로토콜 초기구현 (Protocol Default Implementations)이라고 함

 

- 만약 프로토콜의 익스텐션에서 구현한 기능을 사용하지 않고 타입의 특성에 따라 조금 변경해서 구현하고 싶다면 재정의하면 됨

- 특정 프로토콜을 준수하는 타입에 프로토콜의 요구사항을 찾아보고 이미 구현되어 있다면 그 기능을 호출,

   그렇지 않다면 프로토콜 초기 구현의 기능을 호출

// 익스텐션을 통해 구현된 메서드 정의
class Mail: Sendable, Receiveable {
    var to: Receiveable?
    
    func send(data: Any) {
        print("Mail의 send 메서드는 재정의되었습니다")
    }
}

let mailInstance: Mail = Mail()
mailInstance.send(data: "Hello") // Mail의 send 메서드는 재정의되었습니다

 

- 제네릭, 프로토콜, 익스텐션을 통한 재사용 가능한 코드 작성

protocol SelfPrintable {
    func printSelf()
}

extension SelfPrintable where Self: Container {
    func printSelf() {
        print(items)
    }
}

protocol Container: SelfPrintable {
    associatedtype ItemType
    
    var items: [ItemType] { get set }
    var count: Int { get }
    
    mutating func append(item: ItemType)
    subscript(i: Int) -> ItemType { get }
}

extension Container {
    mutating func append(item: ItemType) {
        items.append(item)
    }
    
    var count: Int {
        return items.count
    }
    
    subscript(i: Int) -> ItemType {
        return items[i]
    }
}

protocol Popable: Container {
    mutating func pop() -> ItemType?
    mutating func push(_ item: ItemType)
}

extension Popable {
    mutating func pop() -> ItemType? {
        return items.removeLast()
    }
    
    mutating func push(_ item: ItemType) {
        self.append(item: item)
    }
}

protocol  Insertable: Container {
    mutating func delete() -> ItemType?
    mutating func insert(_ item: ItemType)
}

extension Insertable {
    mutating func delete () -> ItemType? {
        return items.removeFirst()
    }
    
    mutating func insert(_ item: ItemType) {
        self.append(item: item)
    }
}

struct Stack<Element>: Popable {
    var items: [Element] = [Element]()
}

struct Queue<Element>: Insertable {
    var items: [Element] = [Element]()
}

var myIntStack: Stack<Int> = Stack<Int>()
var myStringStack: Stack<String> = Stack<String>()
var myIntQueue: Queue<Int> = Queue<Int>()
var myStringQueue: Queue<String> = Queue<String>()

myIntStack.push(3)
myIntStack.printSelf() // [3]

myIntStack.push(2)
myIntStack.printSelf() // [3, 2]

myIntStack.pop() // 2
myIntStack.printSelf() // [3]

myStringStack.push("A")
myStringStack.printSelf() // ["A"]

myStringStack.push("B")
myStringStack.printSelf() // ["A", "B"]

myStringStack.pop() // "B"
myStringStack.printSelf() // ["A"]

myIntQueue.insert(3)
myIntQueue.printSelf() // [3]

myIntQueue.insert(2)
myIntQueue.printSelf() // [3, 2]

myIntQueue.delete() // 3
myIntQueue.printSelf() // [2]

myStringQueue.insert("A")
myStringQueue.printSelf() // ["A"]

myStringQueue.insert("B")
myStringQueue.printSelf() // ["A", "B"]

myStringQueue.delete()
myStringQueue.printSelf() // ["B"]

 

2. 맵, 필터, 리듀스 직접 구현해보기

// Array 타입의 맵 사용

let items: Array<Int> = [1, 2, 3]

let mappedItems: Array<Int> = items.map { (item: Int) -> Int in
    return item * 10
}

print(mappedItems) // [10, 20, 30]
struct Stack<Element>: Popable {
    var items: [Element] = [Element]()
    
    func map<T>(transform: (Element) -> T) -> Stack<T> {
        var transformedStack: Stack<T> = Stack<T>()

        for item in items {
            transformedStack.items.append(transform(item))
        }

        return transformedStack
    }

}

var myIntStack: Stack<Int> = Stack<Int>()
myIntStack.push(1)
myIntStack.push(5)
myIntStack.push(2)
myIntStack.printSelf() // [1, 5, 2]

var myStrStack: Stack<String> = myIntStack.map{ "\($0)" }
myStrStack.printSelf() // ["1", "5", "2"]

 

// Array 타입의 필터 사용
let filteredItems: Array<Int> = items.filter { (item: Int) -> Bool in
    return item % 2 == 0
}

print(filteredItems) // [2]
struct Stack<Element>: Popable {
    var items: [Element] = [Element]()
    
    func filter(includeElement: (Element) -> Bool) -> Stack<Element> {
        var filteredStack: Stack<ItemType> = Stack<ItemType>()
        
        for item in items {
            if includeElement(item) {
                filteredStack.items.append(item)
            }
        }
        return filteredStack
    }

}

let filteredStack: Stack<Int> = myIntStack.filter { (item: Int) -> Bool in
    return item < 5
}

filteredStack.printSelf() // [1, 2]

 

// Array 타입의 리듀스 사용

let items: Array<Int> = [1, 2, 3]

let combinedItems: Int = items.reduce(0) { (result: Int, next: Int) -> Int in
    return result + next
}

print(combinedItems) // 6

let combinedItemsDoubled: Double = items.reduce(0.0) { (result: Double, next: Int) -> Double in
    return result + Double(next)
}

print(combinedItemsDoubled) // 6.0

let combinedItemsString: String = items.reduce("") { (result: String, next: Int) -> String in
    return result + "\(next) "
}

print(combinedItemsString) // 1 2 3
struct Stack<Element>: Popable {
    var items: [Element] = [Element]()
    
    func reduce<T>(_ initialResult: T, nextPartialResult: (T, Element) -> T) -> T {
        var result: T = initialResult
        
        for item in items {
            result = nextPartialResult(result, item)
        }
        
        return result
    }
}

let combinedInt: Int = myIntStack.reduce(100) { (result: Int, next: Int) -> Int in
    return result + next
}

print(combinedInt) // 108

let combinedDouble: Double = myIntStack.reduce(100) { (result: Double, next: Int) -> Double in
    return result + Double(next)
}

print(combinedDouble) // 108.0

let combinedString: String = myIntStack.reduce("") { (result: String, next: Int) -> String in
    return result + "\(next)"
}

print(combinedString) // 152

 

3. 기본 타입 확장

protocol SelfPrintable {
    func printSelf()
}

extension SelfPrintable {
    func printSelf() {
        print(self)
    }
}

extension Int: SelfPrintable { }
extension String: SelfPrintable { }
extension Double: SelfPrintable { }

1024.printSelf() // 1024
3.14.printSelf() // 3.14
"hana".printSelf() // "hana"

 

 

 

 

 

 

 

 

 

'swift' 카테고리의 다른 글

[swift] 프로답게 스위프트로 코딩하기  (0) 2021.08.17
[swift] 스위프트를 더 스위프트스럽게 사용하기  (0) 2021.03.11
[swift] 프로토콜  (0) 2020.09.21
[swift] 상속  (0) 2020.09.05
[swift] 서브스크립트  (0) 2020.06.25