수색…


비고

이 주제에 대한 자세한 내용 은 Swift 의 WWDC 2015 토론 프로토콜 지향 프로그래밍을 참조하십시오.

또한 동일한 서면에 대한 훌륭한 서면 가이드가 있습니다. 스위프트 2에서 프로토콜 지향 프로그래밍을 소개 합니다.

단위 테스트를위한 프로토콜 지향 프로그래밍 활용

프로토콜 지향 프로그래밍은 코드에 대한 더 나은 단위 테스트를 쉽게 작성하기위한 유용한 도구입니다.

ViewModel 클래스에 의존하는 UIViewController를 테스트하려고한다고 가정 해 봅시다.

생산 코드에 필요한 단계는 다음과 같습니다.

  1. 클래스 ViewModel의 공용 인터페이스를 UIViewController에서 필요한 모든 속성과 메서드로 노출하는 프로토콜을 정의합니다.
  2. 해당 프로토콜을 준수하는 실제 ViewModel 클래스를 구현하십시오.
  3. 의존성 삽입 기술을 사용하여보기 컨트롤러가 원하는 구현을 사용하고 프로토콜이 아닌 실제 인스턴스로 전달합니다.
protocol ViewModelType {
   var title : String {get}
   func confirm()
}

class ViewModel : ViewModelType {
   let title : String

   init(title: String) {
       self.title = title
   }
   func confirm() { ... }
}

class ViewController : UIViewController {
   // We declare the viewModel property as an object conforming to the protocol
   // so we can swap the implementations without any friction.
   var viewModel : ViewModelType! 
   @IBOutlet var titleLabel : UILabel!

   override func viewDidLoad() {
       super.viewDidLoad()
       titleLabel.text = viewModel.title
   }

   @IBAction func didTapOnButton(sender: UIButton) {
       viewModel.confirm()
   }
}

// With DI we setup the view controller and assign the view model.
// The view controller doesn't know the concrete class of the view model, 
// but just relies on the declared interface on the protocol.
let viewController = //... Instantiate view controller
viewController.viewModel = ViewModel(title: "MyTitle")

그런 다음 단원 테스트 :

  1. 동일한 프로토콜을 준수하는 모의 ViewModel 구현하기
  2. 실제 인스턴스 대신 종속성 주입을 사용하여 테스트중인 UIViewController로 전달하십시오.
  3. 테스트!
class FakeViewModel : ViewModelType {
   let title : String = "FakeTitle"

   var didConfirm = false
   func confirm() {
       didConfirm = true
   }
}

class ViewControllerTest : XCTestCase {
    var sut : ViewController!
    var viewModel : FakeViewModel!

    override func setUp() {
        super.setUp()

        viewModel = FakeViewModel()
        sut = // ... initialization for view controller
        sut.viewModel = viewModel

        XCTAssertNotNil(self.sut.view) // Needed to trigger view loading
    } 

    func testTitleLabel() {
        XCTAssertEqual(self.sut.titleLabel.text, "FakeTitle")
    }

    func testTapOnButton() {
        sut.didTapOnButton(UIButton())
        XCTAssertTrue(self.viewModel.didConfirm)
    }
}

프로토콜을 퍼스트 클래스 유형으로 사용

프로토콜 지향 프로그래밍은 핵심적인 신속한 디자인 패턴으로 사용될 수 있습니다.

다른 유형은 동일한 프로토콜을 준수 할 수 있으며, 값 유형은 여러 프로토콜을 준수 할 수 있으며 심지어 기본 메소드 구현을 제공 할 수도 있습니다.

처음에는 일반적으로 사용되는 속성 및 / 또는 특정 유형 또는 일반 유형의 메소드를 나타낼 수있는 프로토콜이 정의됩니다.

protocol ItemData {
    
    var title: String { get }
    var description: String { get }
    var thumbnailURL: NSURL { get }
    var created: NSDate { get }
    var updated: NSDate { get }
    
}

protocol DisplayItem {
    
    func hasBeenUpdated() -> Bool
    func getFormattedTitle() -> String
    func getFormattedDescription() -> String

}

protocol GetAPIItemDataOperation {
    
    static func get(url: NSURL, completed: ([ItemData]) -> Void)
}

필요한 경우, 적합 형이 구현을 오버라이드 (override) 할 수 있다고해도, get 메소드의 디폴트 구현을 작성할 수 있습니다.

extension GetAPIItemDataOperation {
    
    static func get(url: NSURL, completed: ([ItemData]) -> Void) {
        
        let date = NSDate(
        timeIntervalSinceNow: NSDate().timeIntervalSince1970
            + 5000)
        
        // get data from url
        let urlData: [String: AnyObject] = [
            "title": "Red Camaro",
            "desc": "A fast red car.",
            "thumb":"http://cars.images.com/red-camaro.png",
            "created": NSDate(), "updated": date]
        
        // in this example forced unwrapping is used
        // forced unwrapping should never be used in practice
        // instead conditional unwrapping should be used (guard or if/let)
        let item = Item(
            title: urlData["title"] as! String,
            description: urlData["desc"] as! String,
            thumbnailURL: NSURL(string: urlData["thumb"] as! String)!,
            created: urlData["created"] as! NSDate,
            updated: urlData["updated"] as! NSDate)
        
        completed([item])
        
    }
}

struct ItemOperation: GetAPIItemDataOperation { }

ItemData 프로토콜을 준수하는 값 유형 인이 값 유형은 다른 프로토콜에도 부합 할 수 있습니다.

struct Item: ItemData {
    
    let title: String
    let description: String
    let thumbnailURL: NSURL
    let created: NSDate
    let updated: NSDate
    
}

여기서 항목 구조체는 표시 항목을 준수하도록 확장됩니다.

extension Item: DisplayItem {
    
    func hasBeenUpdated() -> Bool {
        return updated.timeIntervalSince1970 >
            created.timeIntervalSince1970
    }
    
    func getFormattedTitle() -> String {
        return title.stringByTrimmingCharactersInSet(
            .whitespaceAndNewlineCharacterSet())
    }
    
    func getFormattedDescription() -> String {
        return description.stringByTrimmingCharactersInSet(
            .whitespaceAndNewlineCharacterSet())
    }
}

정적 get 메소드를 사용하기위한 호출 사이트의 예.

ItemOperation.get(NSURL()) { (itemData) in
    
    // perhaps inform a view of new data
    // or parse the data for user requested info, etc.
    dispatch_async(dispatch_get_main_queue(), { 
        
        // self.items = itemData
    })
    
}

다른 유스 케이스에는 다른 구현이 필요하다. 여기에서 주요 아이디어는 프로토콜이 디자인의 핵심 포인트 인 다양한 유형의 적합성을 보여주는 것입니다. 이 예에서 API 데이터는 조건부로 핵심 데이터 엔티티에 저장됩니다.

// the default core data created classes + extension
class LocalItem: NSManagedObject { }

extension LocalItem {
    
    @NSManaged var title: String
    @NSManaged var itemDescription: String
    @NSManaged var thumbnailURLStr: String
    @NSManaged var createdAt: NSDate
    @NSManaged var updatedAt: NSDate
}

여기서 Core Data 지원 클래스는 DisplayItem 프로토콜을 준수 할 수도 있습니다.

extension LocalItem: DisplayItem {
    
    func hasBeenUpdated() -> Bool {
        return updatedAt.timeIntervalSince1970 >
            createdAt.timeIntervalSince1970
    }
    
    func getFormattedTitle() -> String {
        return title.stringByTrimmingCharactersInSet(
            .whitespaceAndNewlineCharacterSet())
    }
    
    func getFormattedDescription() -> String {
        return itemDescription.stringByTrimmingCharactersInSet(
            .whitespaceAndNewlineCharacterSet())
    }
}

// In use, the core data results can be
// conditionally casts as a protocol
class MyController: UIViewController {

    override func viewDidLoad() {
        
        let fr: NSFetchRequest = NSFetchRequest(
        entityName: "Items")
    
        let context = NSManagedObjectContext(
        concurrencyType: .MainQueueConcurrencyType)
        
        do {
            
            let items: AnyObject = try context.executeFetchRequest(fr)
            if let displayItems = items as? [DisplayItem] {
                
                print(displayItems)
            }
        
        } catch let error as NSError {
            print(error.localizedDescription)
        }
        
    }
}


Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow