본문 바로가기

Swift 공부

Protocol Oriented Programming(POP) - With RxSwift Mvvm

 

https://developer.apple.com/videos/play/wwdc2015/408/

 

Protocol-Oriented Programming in Swift - WWDC15 - Videos - Apple Developer

At the heart of Swift's design are two incredibly powerful ideas: protocol-oriented programming and first class value semantics. Each of...

developer.apple.com

Swift의 프로토콜 지향 프로그래밍

Swift 디자인의 핵심에는 프로토콜 지향 프로그래밍과 일급 가치 의미론이라는 두 가지 매우 강력한 아이디어가 있습니다. 이러한 각 개념은 예측 가능성, 성능 및 생산성에 이점이 있지만 함께 프로그래밍에 대한 생각을 바꿀 수 있습니다. 작성하는 코드를 개선하기 위해 이러한 아이디어를 어떻게 적용할 수 있는지 알아보십시오.

 

 

Swift를 개발하면서 수많은 Protocol을 채택하고 , 만들고 사용하였지만 정확한 이유는 몰르고 제대로된 사용법 조차 모르고 사용을 하고있었고 항상 내가 쓰던방식대로 사용하면 이걸 도대체 왜 쓰는거지? 라는 의문점을 가지고 있었다.

 

그러다 개발과제를 하던중 POP라는 키워드를 개발과제 키워드로 받게되었고 개발과제 기간이 끝나고

(시간이 부족하여 POP를 적용시키지 못함 ㅠㅠ 개인적으로 너무 아쉽다.)

POP를 찾아 공부를 해보았는데 가려운 부분을 긁어주는 아주 시원한 공부가 되었다.

마침 타이밍도 좋게 TDD / UnitTest에 대한 공부를 메인으로 하고있어서 서로 맞물려서 이해가 되다보니 공부 효과가 펑펑 터져버렸다.

 

POP에 대한 공부자료는 다른분들이 정리를 잘 해주어 따로 작성은 하지않고 참고하였던 블로그 하나를 첨부드립니다.

 

https://medium.com/@Alpaca_iOSStudy/protocol-oriented-programming-pop-2db7d4d02747 

 

Protocol Oriented Programming(POP)

POP에 대한 얕은 공부

medium.com

 

 

 

그래서 저는 어떻게 개선이 되었냐? 

 

변경 전 

 

import UIKit
import RxSwift
import RxCocoa
import RxRelay
import Moya

protocol DommyViewModelProtocol {
   
    func userProfileGet() -> ( Observable<Profile> , Observable<ProfileSubData> )
}

class DommyViewModel : ViewModelProtocol , DommyViewModelProtocol{
  
    struct Input {
        let changeValue = PublishSubject<(MetaDataType , String)>()
    }
    
    struct Output {
        let userProfile = BehaviorRelay<Profile>(value : Profile(gender: .F))
        var metaData = BehaviorRelay<ProfileSubData?>(value : nil)
    }
    
    
    let networkAPI : NetworkingAPI
    
    var coordinator : DommyViewCoordinator?
    
    let userManager : UserDefaultsManager = UserDefaultsManager.shared
    
    
    let input = Input()
    let output = Output()
    let disposeBag = DisposeBag()
    
    init(networkAPI : NetworkingAPI = NetworkingAPI.shared){
        self.networkAPI = networkAPI

        inputBinding()
        outputBinding()
        
      
    }
    
    
    func inputBinding() {
        input.changeValue
            .withLatestFrom(output.userProfile) {
                var profile = $1
                switch $0.0 {
                case .bodyTypes :
                    profile.bodyType = BodyType(rawValue: $0.1)
                case.educations :
                    profile.education = Education(rawValue: $0.1)
                case .heightRange:
                    profile.height = Int($0.1)
                case .company:
                    profile.company = $0.1
                case .job:
                    profile.job = $0.1
                case .school:
                    profile.school = $0.1
                case .introduction:
                    profile.introduction = $0.1
                }
                return profile
            }
            .bind(to: output.userProfile)
            .disposed(by: disposeBag)
    }
    
    func outputBinding() {
        userProfileGet().0
            .bind(to: output.userProfile)
            .disposed(by: disposeBag)
        
        userProfileGet().1
            .bind(to: output.metaData)
            .disposed(by: disposeBag)
        

    }
}



extension DommyViewModel : DommyViewModelProtocol  {
    
    func userProfileGet() -> ( Observable<Profile> , Observable<ProfileSubData> ){
        
        let network = networkAPI.request(type : ProfileResModel.self , .profile)
                .asObservable()
        
        let Profile = network
            .map{ $0.data }
            .compactMap{ $0 }

        let Meta = network
            .map{
                $0.meta
            }
            .compactMap{ $0 }
         
     
        return  (Profile , Meta)
    }
}

 

 

변경 후

 

import UIKit
import RxSwift
import RxCocoa
import RxRelay
import Moya

protocol DommyViewModelProtocol {
    associatedtype Input
    
    associatedtype Output
    
    var networkAPI : NetworkingAPI { get }
    
    var coordinator : ProfileViewCoordinator? { get }
    
    var userManager : UserDefaultsManager { get }
    
    var input : Input { get }
    var output : Output { get }
    
    var disposeBag : DisposeBag { get }
    
    func userProfileGet() -> ( Observable<Profile> , Observable<ProfileSubData> )
}


class DommyViewModel : ViewModelProtocol , DommyViewModelProtocol{
  
    struct Input {
        let changeValue = PublishSubject<(MetaDataType , String)>()
    }
    
    struct Output {
        let userProfile = BehaviorRelay<Profile>(value : Profile(gender: .F))
        var metaData = BehaviorRelay<ProfileSubData?>(value : nil)
    }
    
    
    let networkAPI : NetworkingAPI
    
    var coordinator : DommyViewCoordinator?
    
    let userManager : UserDefaultsManager = UserDefaultsManager.shared
    
    
    let input = Input()
    let output = Output()
    let disposeBag = DisposeBag()
    
    init(networkAPI : NetworkingAPI = NetworkingAPI.shared){
        self.networkAPI = networkAPI

        inputBinding()
        outputBinding()
        
      
    }
    
    
    func inputBinding() {
        input.changeValue
            .withLatestFrom(output.userProfile) {
                var profile = $1
                switch $0.0 {
                case .bodyTypes :
                    profile.bodyType = BodyType(rawValue: $0.1)
                case.educations :
                    profile.education = Education(rawValue: $0.1)
                case .heightRange:
                    profile.height = Int($0.1)
                case .company:
                    profile.company = $0.1
                case .job:
                    profile.job = $0.1
                case .school:
                    profile.school = $0.1
                case .introduction:
                    profile.introduction = $0.1
                }
                return profile
            }
            .bind(to: output.userProfile)
            .disposed(by: disposeBag)
    }
    
    func outputBinding() {
        userProfileGet().0
            .bind(to: output.userProfile)
            .disposed(by: disposeBag)
        
        userProfileGet().1
            .bind(to: output.metaData)
            .disposed(by: disposeBag)
        

    }
}


extension DommyViewModelProtocol {
    
    
    func userProfileGet() -> ( Observable<Profile> , Observable<ProfileSubData> ){
        
        let network = networkAPI.request(type : ProfileResModel.self , .profile)
                .asObservable()
        
        let Profile = network
            .map{ $0.data }
            .compactMap{ $0 }

        let Meta = network
            .map{
                $0.meta
            }
            .compactMap{ $0 }
         
     
        return  (Profile , Meta)
    }
}

 

이렇게 간단한 함수로 봤을때는 크게 변한게 없어보이고 굳이? 라는 생각이 드는데 해당 프로토콜을 여러곳에서 사용된다면?

지금은 특정 뷰모델의 함수 하나를 프로토콜화로 사용하였지만 다양한곳에서 사용하는 함수라면? 

그리고 extension의 가장 강력한 기능중 하나인 제네릭한 함수를 구성할 수 있다는점 그리고 동일한 기능의 뷰모델을 만들었을때

해당 프로토콜을 채택을하게된다면 해당 프로토콜을 점검하는 또 다른 UnitTest 코드를 작성하지 않아도 되는? 상당히 여러므로 

장점이 많은 방식인 것 같다. 아직 POP에 대해 조사한지 얼마 되지않았고 부족한 점이 많지만 POP를 공부했을때 상당히 매력적이라고

정리하게 되었다.  

 

'Swift 공부' 카테고리의 다른 글

MVVM RxSwift , Combine  (0) 2022.03.12
Swift MVVM  (0) 2022.03.09
WKWebView MemoryLeak 해결  (0) 2021.11.12
타입캐스팅 is , as 와 Any , AnyObject  (0) 2021.08.05
Swift 함수형 프로그래밍  (1) 2021.08.05