Swift의 프로토콜 이해.

Feb 17 2023
내 마지막 게시물 이후 너무 오래되었습니다. 매체에서 나를 팔로우하는 사람들에게 좀 미안합니다.

내 마지막 게시물 이후 너무 오래되었습니다. 매체에서 나를 팔로우하는 사람들에게 좀 미안합니다. ㅋㅋㅋ. 마지막 게시물 이후로 많이 향상되었습니다. 돌이켜 보면 당시에 작성한 많은 코드와 개념이 매우 쓰레기처럼 보입니다. 하하. 어쨌든 이전보다 훨씬 더 많이 알게 된 것 같아서 그 과정에서 배운 것을 공유하기로 합니다. 그리고 그것이 당신에게 도움이 될 수 있기를 바랍니다.

이 기사에서는 Swift에서 프로토콜을 사용하는 방법에 대해 설명합니다 . 유사한 개념을 다른 OOP 프로그래밍 언어에 적용할 수 있습니다.

1. 프로토콜 준수

기본적인 것부터 시작하세요. 프로토콜을 준수하는 모든 개체는 해당 속성/메소드를 구현해야 합니다.

protocol Example {
  func A()
}

struct AObject: Example {
// Compiler will ask you to implement Exmample protocol's function
// Type 'AObject' does not conform to protcol 'Example'
  func A() {}
}

꽤 자명하다. 프로토콜은 " 추상화 "입니다. 객체가 어떻게 생겼는지 보여줍니다. 그러나 실제 구현은 개체 자체에 따라 다릅니다.
프로토콜은 또한 시스템을 보다 유연하게 만듭니다.

class HTTPService {
    func sendRequest() {
        // do API request
    }
}

class ViewController {
    let service: HTTPService
    init(service: HTTPService) {
        self.service = service
        service.sendRequest()
    }
}

DIP(Dependency Inversion Principle)는 상위 수준 모듈이 하위 수준 모듈에 의존해서는 안 된다고 명시합니다. 둘 다 추상화에 의존해야 합니다 .

protocol HTTPService {
    func sendRequest()
}

class ProductionAPIService: HTTPService {
    func sendRequest() {
        // do API request
    }
}

class StubTestingService: HTTPService {
    func sendRequest() {
        // For testing
    }
}

class ViewController {
    let service: HTTPService
    init(service: HTTPService) {
        self.service = service
        service.sendRequest()
    }
}

3. 프로토콜은 인터페이스다!!!!

제 생각에는 이것이 아마도 가장 중요한 개념일 것입니다. 많은 초보자가 이전 개념에 익숙하지만 프로토콜에 무엇을 작성해야 하는지 모릅니다.

운전자라고 상상해보십시오. 인터페이스는 브레이크, 가속 페달 및 스티어링 휠이어야합니다 (자동 운전 방법 만 알고 있으므로 레버에 대해 묻지 마십시오). 이 인터페이스는 프로토콜입니다. 후드 아래에서 일어난 일(문자 그대로)은 운전자에게 중요하지 않습니다. 드라이버는 인터페이스에만 노출되어야 합니다.

protocol AutomaticCar {
    func stepOnGas()
    func hitTheBreak()
    func steerTheWheel()
}

class Driver {
    let car: AutomaticCar
    init(car: AutomaticCar) {
        self.car = car
    }
    
    func goDriving() {
        // The driver should only be exposed to the interface and nothing else.
        car.stepOnGas()
        car.steerTheWheel()
        car.hitTheBreak()
    }
}

protocol Loader {
    func load()
}

class RemoteSource: Loader {
    func load() {
        // Do remotely load
    }
    private func doOtherOperation() {}
}

class LocalSource: Loader {
    func load() {
        // Do locally load
    }
    private func doSomethingElse() {}
}

class ViewController: UIViewController {
    let service: Loader
    init(service: Loader) {
        self.service = service
        super.init(nibName: nil, bundle: nil)
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    override func viewDidLoad() {
        super.viewDidLoad()  
        service.load()
    }
}

      
                

이것은 직장에서 나에게 많이 일어났습니다. 우리 회사의 레거시 코드베이스에는 위임을 사용하는 많은 사용자 지정 보기가 있습니다. 다음과 같이 보입니다.

protocol CustomViewDelegate {
    func doA()
    func doB()
    func doC()
    func doD()
}
// A customView with delegation that can be used to notify or pass data
class CustomView: UIView {
    
    var delegate: CustomViewDelegate?
}
// VieweController has to conform to CustomViewDelegate becuase
// when we write "customView.delegate = self"
// we appoint the delegation to this ViewController
class ViewController: UIViewController, CustomViewDelegate {
    let customView = CustomView()
    
    init(){
        super.init(nibName: nil, bundle: nil)
        customView.delegate = self
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

// And hence, we are forced to implement all these methods
    func doA() {
        <#code#>
    }
    func doB() {
        <#code#>
    }
    func doC() {
        <#code#>
    }
    func doD() {
        <#code#>
    }
}

그러나 이들은 모두 선택 사항입니다. 컴파일러는 이러한 메서드를 구현하도록 강요하지 않습니다. 이 "선택적 프로토콜 방법"을 달성하기 위해 프로토콜에 대한 확장을 작성할 수 있습니다.

extension CustomViewDelegate {
  func doA() {}
  func doB() {}
}
// Now that doA & doB have a defualt implemntation in the extension, we no longer need to implement these two. (You still can if needed.)

프로토콜에 두 개 이상의 메서드(또는 속성)가 있는 경우. 냄새야. Liskov 대체 원칙 및 인터페이스 분리 원칙을 위반했을 수 있습니다 . 예를 들어 보겠습니다.

protocol Developer {
    func writeCode()
    func beHappy()
}

struct FrontEndDev: Developer {
    func writeCode() {}
    
    func beHappy() {}
}

struct BackEndDev: Developer {
    func writeCode() {}
    
    func beHappy() {
        fatalError("I'm not happy")
    }
}

인터페이스 분리 원칙: 어떤 코드도 사용하지 않는 메서드에 강제로 종속되어서는 안 됩니다.

class Company {
    let devs: Developer
    init(devs: Developer) {
        self.devs = devs
    }
}

let companyA = Company(devs: BackEndDev())
companyA.devs.beHappy() // Leads to crash. The BackEndDev can't beHappy()

Liskov 대체 원칙: 상위 클래스의 개체는 응용 프로그램을 중단하지 않고 해당 하위 클래스의 개체로 교체할 수 있어야 합니다.

모든 것을 말하면서 메소드 또는 속성이 필요한지 확인하십시오.

6. 프로토콜은 다른 프로토콜을 따를 수 있습니다.

protocol BaseProtocol {
    func cancel()
}

protocol SpecialTask: BaseProtocol {
    func stopOperation()
    func pauseOperation()
    func startOperation()
}

struct SpecialForce: SpecialTask {
    func stopOperation() {
        <#code#>
    }
    func pauseOperation() {
        <#code#>
    }
    func startOperation() {
        <#code#>
    }   
    func cancel() {
        <#code#>
    }
}

요청을 취소할 수 있어야 하는 API 요청에 대해 이와 같은 일부 구현을 보았습니다. (이 경우 자신의 프로토콜을 다른 프로토콜에 맞추는 대신 무료로 취소할 수 있는 Combine 프레임워크를 사용하는 것이 좋습니다 .)

7. 일반 프로토콜

개발자로서 우리는 항상 코드를 재사용할 수 있도록 노력합니다. 프로토콜도 예외는 아닙니다. 우리는 protocol generic을 만들기 위해 associatedType을 사용할 수 있습니다 .

// The Loader protocol doesn't know about the type that load function is going to complete with.
protocol Loader {
    associatedtype Item
    func load(completion: @escaping (Item) -> Void)
}
// Specify the type in implementation
class Service: Loader {
    typealias Item = String
    func load(completion: @escaping (Item) -> Void) {    
    }
}
// Or, you can just specify the type in the implemented function
class ServiceA: Loader {
    func load(completion: @escaping (String) -> Void) {
    }
}

모두 좋은 하루 보내시기 바랍니다. 앞으로 그리고 위로. 갑시다.