간단 한 사용자 목록 인터페이스 로 보기: iOS 에서 MVP 사용 하기 (번역)

원문: '바보 UI 는 좋 은 UI 입 니 다: 빠 른 iOS 에서 MVP 사용' 링크:http://iyadagha.com/using-mvp-ios-swift/
iOS 응용 프로그램 을 개발 할 때 Model - View - Controller 는 흔히 볼 수 있 는 디자인 모델 이다.일반적으로 View 층 은 UIKit 의 요소 로 구성 되 는데 이러한 요 소 는 코드 정의 나 xib 파일 로 정의 되 고 Model 층 은 응용 프로그램의 업무 논 리 를 포함 하 며 UIViewController 류 가 표시 하 는 Controller 층 은 Model 과 View 간 의 접착제 이다.
이 모델 의 좋 은 부분 은 업무 논리 와 업무 규칙 을 Model 층 에 밀봉 하 는 것 이다.그러나 UIViewController 는 UI 와 관련 된 논 리 를 포함 하고 있 습 니 다.
  • 업무 논 리 를 호출 하고 결 과 를 View
  • 에 연결 합 니 다.
  • 관리 View 요소
  • Model 계층 에서 온 데 이 터 를 UI 우호 형식 으로 변환
  • navigation 논리
  • 사용자 UI 상태 관리
  • 더...
  • 이러한 모든 책임 을 지면 ViewController 는 항상 커 지고 유지 하기 어려워 지 며 테스트 를 한다.
    그래서 이 제 는 MVC 를 개선 해 이런 문 제 를 처리 할 때 가 됐다.Model - View - Presenter MVP 의 개선 이 라 고 합 니 다.
    MVP 모델 은 1996 년 마이크 포 텔 이 처음 도 입 했 으 며 수년 간 여러 차례 논 의 된 바 있다.그의 글 GUI 구조 에서 Martin Fowler 는 이러한 모델 에 대해 논 의 했 고 UI 코드 를 관리 하 는 다른 모델 과 비교 했다.MVP 는 많은 변체 가 있 는데, 그들 사이 의 차 이 는 매우 작다.이 글 에서 나 는 현재 응용 프로그램 개발 에 자주 사용 되 는 상용 프로그램 을 선택 했다.이 변체 의 특징 은:
  • MVP 의 보기 부분 은 UIViews 와 UIViewController 로 구성
  • View 가 presenter 에 의뢰 한 사용자 상호작용
  • presenter 는 사용자 의 상호작용 을 처리 하 는 논 리 를 포함한다
  • presenter 는 Model 층 과 통신 하여 데 이 터 를 UI 우호 형식 으로 변환 하고 보기 업데이트
  • presenter 는 UIKit 에 대한 의존성 이 없다
  • 보 기 는 passiv (덤 프)
  • 입 니 다.
    다음 예제 에 서 는 작업 중 MVP 를 사용 하 는 방법 을 보 여 줍 니 다.
    우리 의 예 는 매우 간단 한 프로그램 으로 간단 한 사용자 목록 을 표시 합 니 다.Github 에서 완전한 소스 코드 를 얻 을 수 있 습 니 다: https: / github. com / iyadagha / iOS - mvp - sample.(Swift + OC 두 버 전 은 문장 끝 에 예 시 됩 니 다)
    사용자 정보의 간단 한 데이터 모델 부터 시작 합 니 다.
    struct User {
        let firstName: String
        let lastName: String
        let email: String
        let age: Int
    }
    

    그리고 저 희 는 간단 한 UserService 를 실현 합 니 다. 사용자 목록 을 다른 단계 로 되 돌려 줍 니 다.
    class UserService {
     
        //the service delivers mocked data with a delay
        func getUsers(callBack:([User]) -> Void){
            let users = [User(firstName: "Iyad", lastName: "Agha", email: "[email protected]", age: 36),
                         User(firstName: "Mila", lastName: "Haward", email: "[email protected]", age: 24),
                         User(firstName: "Mark", lastName: "Astun", email: "[email protected]", age: 39)
                        ]
     
            let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(2 * Double(NSEC_PER_SEC)))
            dispatch_after(delayTime, dispatch_get_main_queue()) {
                callBack(users)
            }
        }
    }
    

    다음 단 계 는 User Presenter 를 작성 하 는 것 입 니 다.우선 사용자 의 데이터 모델 이 필요 합 니 다. 보기 에서 직접 사용 할 수 있 습 니 다.필요 에 따라 보기 에서 정확하게 포맷 된 데 이 터 를 포함 합 니 다:
    struct UserViewData{   
        let name: String
        let age: String
    }
    

    그 다음 에 우 리 는 보 기 를 추상 적 으로 해 야 한다. 이것 은 presenter 가 UIViewController 를 모 르 는 상황 에서 사용 할 수 있다.우 리 는 프로 토 콜 UserView 를 정의 함으로써 이 점 을 할 수 있다.
    protocol UserView: NSObjectProtocol {
        func startLoading()
        func finishLoading()
        func setUsers(users: [UserViewData])
        func setEmptyUsers()
    }
    

    이 프로 토 콜 은 presenter 에서 사용 되 며, 잠시 후 UIViewController 에서 실 현 됩 니 다.기본적으로 프로 토 콜 은 presenter 에서 보 기 를 제어 하 는 함수 에 포함 되 어 있 습 니 다.
    사용자 자체 가 다음 과 같 습 니 다:
    class UserPresenter {
        private let userService:UserService
        weak private var userView : UserView?
         
        init(userService:UserService){
            self.userService = userService
        }
         
        func attachView(view:UserView){
            userView = view
        }
         
        func detachView() {
            userView = nil
        }
         
        func getUsers(){
            self.userView?.startLoading()
            userService.getUsers{ [weak self] users in
                self?.userView?.finishLoading()
                if(users.count == 0){
                    self?.userView?.setEmptyUsers()
                }else{
                    let mappedUsers = users.map{
                        return UserViewData(name: "\($0.firstName) \($0.lastName)", age: "\($0.age) years")
                    }
                    self?.userView?.setUsers(mappedUsers)
                }
                 
            }
        }
    }
    

    루트 는 함수 attachView (view: UserView) 와 attachView (view: UserView) 를 UIViewContoller 생명주기 방법의 더 많은 제어 에 사용 합 니 다. 우 리 는 뒤에서 볼 수 있 습 니 다.사용 자 를 UserView Data 로 전환 하 는 것 은 presenter 의 책임 입 니 다.또한 userView 는 보존 주 기 를 피하 기 위해 약 해 야 합 니 다.
    실현 의 마지막 부분 은 UserView Controller 입 니 다.
    class UserViewController: UIViewController {
     
        @IBOutlet weak var emptyView: UIView?
        @IBOutlet weak var tableView: UITableView?
        @IBOutlet weak var activityIndicator: UIActivityIndicatorView?
     
        private let userPresenter = UserPresenter(userService: UserService())
        private var usersToDisplay = [UserViewData]()
     
        override func viewDidLoad() {
            super.viewDidLoad()
            tableView?.dataSource = self
            activityIndicator?.hidesWhenStopped = true
     
            userPresenter.attachView(self)
            userPresenter.getUsers()
        }
    }
    

    사용자 목록 을 표시 하 는 tableView 가 있 습 니 다. empty View 는 사용자 가 사용 할 수 없 으 면 activity Indicator 가 프로그램 에서 사용 자 를 불 러 올 때 표 시 됩 니 다.이 밖 에 userPresenter 와 사용자 목록 도 있 습 니 다.
    viewdLoad 방법 에서 UserViewController 는 자신 을 presenter 에 추가 합 니 다.이것 은 가능 하 다. 왜냐하면 우 리 는 곧 UserView Controller 가 UserView 협 의 를 실현 하 는 것 을 볼 수 있 기 때문이다.
    extension UserViewController: UserView {
     
        func startLoading() {
            activityIndicator?.startAnimating()
        }
     
        func finishLoading() {
            activityIndicator?.stopAnimating()
        }
     
        func setUsers(users: [UserViewData]) {
            usersToDisplay = users
            tableView?.hidden = false
            emptyView?.hidden = true;
            tableView?.reloadData()
        }
     
        func setEmptyUsers() {
            tableView?.hidden = true
            emptyView?.hidden = false;
        }
    }
    

    우리 가 본 바 와 같이 이 기능 들 은 복잡 한 논 리 를 포함 하지 않 고 순 보기 관 리 를 하고 있 을 뿐이다.
    마지막 으로 UITableView DataSource 의 실현 은 매우 기본 적 이 며 다음 과 같다.
    extension UserViewController: UITableViewDataSource {
        func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return usersToDisplay.count
        }
     
        func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            let cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "UserCell")
            let userViewData = usersToDisplay[indexPath.row]
            cell.textLabel?.text = userViewData.name
            cell.detailTextLabel?.text = userViewData.age
            cell.textLabel
            return cell
        }
    }
    

    유닛 테스트
    MVP 를 하 는 장점 중 하 나 는 UI 논리의 가장 큰 부분 을 테스트 할 수 있 고 UIViewController 자 체 를 테스트 할 필요 가 없다 는 것 이다.따라서 우리 presenter 가 좋 은 유닛 테스트 보급률 을 가지 고 있다 면 UIViewController 를 위해 유닛 테스트 를 작성 할 필요 가 없습니다.
    이제 User Presenter 를 어떻게 테스트 하 는 지 살 펴 보 자.우선 우 리 는 두 개의 시 뮬 레이 션 작업 을 정의 한다.하나의 시 뮬 레이 션 은 UserService 가 필요 한 사용자 목록 을 제공 하도록 하 는 것 이다.또 다른 시 뮬 레이 션 은 UserView 로 방법 이 올 바 르 게 호출 되 었 는 지 검증 하 는 것 이다.
    class UserServiceMock: UserService {
        private let users: [User]
        init(users: [User]) {
            self.users = users
        }
        override func getUsers(callBack: ([User]) -> Void) {
            callBack(users)
        }
     
    }
     
    class UserViewMock : NSObject, UserView{
        var setUsersCalled = false
        var setEmptyUsersCalled = false
     
        func setUsers(users: [UserViewData]) {
            setUsersCalled = true
        }
     
        func setEmptyUsers() {
            setEmptyUsersCalled = true
        }
    }
    

    현재 Google 은 서비스 가 비어 있 지 않 은 사용자 목록 을 제공 할 때 presenter 의 행동 이 올 바른 지 테스트 할 수 있 습 니 다.
    class UserPresenterTest: XCTestCase {
     
        let emptyUsersServiceMock = UserServiceMock(users:[User]())
     
        let towUsersServiceMock = UserServiceMock(users:[User(firstName: "firstname1", lastName: "lastname1", email: "[email protected]", age: 30),
                                                         User(firstName: "firstname2", lastName: "lastname2", email: "[email protected]", age: 24)])
     
        func testShouldSetUsers() {
            //given
            let userViewMock = UserViewMock()
            let userPresenterUnderTest = UserPresenter(userService: towUsersServiceMock)
            userPresenterUnderTest.attachView(userViewMock)
     
            //when
            userPresenterUnderTest.getUsers()
     
            //verify
            XCTAssertTrue(userViewMock.setUsersCalled)
        }
    }
    

    마찬가지 로 서비스 가 빈 사용자 목록 으로 돌아 가면 presenter 가 정상적으로 작 동 하 는 지 테스트 할 수 있 습 니 다.
    func testShouldSetEmptyIfNoUserAvailable() {
            //given
            let userViewMock = UserViewMock()
            let userPresenterUnderTest = UserPresenter(userService: emptyUsersServiceMock)
            userPresenterUnderTest.attachView(userViewMock)
     
            //when
            userPresenterUnderTest.getUsers()
     
            //verify
            XCTAssertTrue(userViewMock.setEmptyUsersCalled)
        }
    

    변천 과정.
    우 리 는 MVP 가 MVC 의 변천 이라는 것 을 이미 보 았 다.UI 논 리 를 Presenter 라 는 추가 구성 요소 에 넣 고 UIViewController passiv (Dump) 를 가능 하 게 해 야 합 니 다.
    MVP 의 특징 중 하 나 는 presenter 와 View 가 서로 통신 하 는 것 이다.이 보기 (이 예 에서 UIViewController) 는 presenter 의 인용 을 제공 합 니 다. 반대로 도 마찬가지 입 니 다.응답 식 프로 그래 밍 을 사용 하여 presenter 에서 사용 하 는 보기 의 참 고 를 삭제 할 수 있 지만.ReactiveCocoa 나 Rx Swift 등 응답 식 프레임 워 크 를 사용 하면 하나의 시스템 구 조 를 구축 할 수 있 는데 그 중에서 보기 만 presenter 를 알 고 반대로 도 마찬가지 입 니 다.이런 상황 에서 이 구 조 는 MVVM 이 라 고 불 릴 것 이다.
    ? Contributions
  • WeChat : WhatsXie
  • Email : [email protected]
  • Blog : https://reversescale.github.io
  • Code : https://github.com/ReverseScale/MVPSimpleDemo
  • 좋은 웹페이지 즐겨찾기