Các thể loại protocol trong Swift.
Bài đăng này đã không được cập nhật trong 5 năm
- 
Vai trò chủ yếu của protocol/interfacelà cho phép cácabstractionchung được xác định trên các triển khai cụ thể . Một kỹ thuật được gọi làpolymorphismcho phép chúng taswap/morphimplement của mình mà không ảnh hưởng đến API được công khai.
- 
Mặc dù Swiftđã hỗ trợ đầy đủ chopolymorphismdựa trêninterfacenhưng cácprotocolvẫn đóng vai trò lớn trong thiết kế tổng thể củalanguagevàlibnhư một phần chức năng chính mà Swift triển khai trực tiếp phía trên các protocol khác.
- 
Thiết kế protocol-oriented designcho phép chúng ta sử dụng cácprotocoltheo nhiều cách khác nhau trongsource code. Chúng ta cùng lướt qua các cách đó và cả hai hãy xem cách Apple sử dụng cácprotocolcủa họ.
1/ Enabling unified actions:
- Bắt đầu bằng cách xem các protocolyêu cầu cáctypephù hợp để có thể thực hiện cácactioncụ thể. Ví dụ:protocol Equatableđược sử dụng để đánh dấu rằng mộttypecó thể thực hiện kiểm tra đẳng thức giữa hai trường hợp, trong khiprotocol Hashableđược chấp nhận bởi cáctypecó thể đượchashed:
protocol Equatable {
    static func ==(lhs: Self, rhs: Self) -> Bool
}
protocol Hashable: Equatable {
    func hash(into hasher: inout Hasher)
}
- 
Lợi ích lớn của hai khả năng đó được cụ thể hóa bằng cách sử dụng hệ thống (thay vì được mã hóa vào trình biên dịch) là nó cho phép chúng ta viết codechung ràng buộc với cácprotocoltừ đó cho phép chúng ta sử dụng đầy đủ của những khả năng trongcodeđó.
- 
Cách thức chúng ta có thể mở rộng Arraybằng mộtmethodgiúp chúng ta đếm tất cả các lần xuất hiện của mộtvaluevới điều kiện là kiểuElementmảng phù hợp vớiEquatable:
extension Array where Element: Equatable {
    func numberOfOccurences(of value: Element) -> Int {
        reduce(into: 0) { count, element in
            // We can check whether two values are equal here
            // since we have a guarantee that they both conform
            // to the Equatable protocol:
            if element == value {
                count += 1
            }
        }
    }
}
- 
Bất cứ khi nào xác định protocoldựa trên cácactionchúng ta nên làm cho cácprotocolđó chung chung nhất có thể (giống nhưEquitablevàHashable) vì chúng vẫn tập trung vào chính các hành động thay vì ràng buộc với têndomaincụ thể.
- 
VD: Nếu chúng ta muốn thống nhất một số loại tải các đối tượng hoặc giá trị khác nhau, chúng ta có thể định nghĩa một protocolcó thể tải với một loại liên quan - sẽ cho phép mỗi loại tuân thủ khai báo loại kết quả mà nó tải:
protocol Loadable {
    associatedtype Result
    func load() throws -> Result
}
Tuy nhiên không phải mọi protocol đều định nghĩa các action . Trong khi tên của protocol:
protocol Cachable: Codable {
    var cacheKey: String { get }
}
- 
So sánh protocol CodablevàCachableta xác định các hành động cho cảencodevàdecode.
- 
Không phải tất cả các protocolđều cần sử dụngsuffix. Việc ràng buộcsuffixvào bất kỳ danh từ cụ thể nào chỉ để xác địnhprotocolcó thể dẫn đến khá nhiều nhầm lẫn:
protocol Titleable {
    var title: String { get }
}
- Khó hiểu hơn nữa là khi sử dụng suffixcó thể tạo ra một tên có ý nghĩa hoàn toàn khácmong muốn. Ví dụ, chúng ta đã định nghĩa mộtprotocolvới mục đích hoạt động như mộtAPIcho cáccolor containernhưng tên lại gợi ý rằng nó có thể được tô màu cho cáctypemà chính chúng có thể được tô màu:
protocol Colorable {
    var foregroundColor: UIColor { get }
    var backgroundColor: UIColor { get }
}
- Chúng ta có thể cải thiện một số các protocolnày cả về cách đặt tên cũng như cách chúng có cấu trúc. Bằng cách bước ra khỏi mục một và xem một vài cách khác nhau để xác định cácprotocoltrongSwift.
2/ Defining requirements:
- Mục số hai dành cho các protocolđược sử dụng để xác định các yêu cầu chính thức cho mộttypeđối tượng hoặcAPInhất định. Trongstandard library, cácprotocolđược sử dụng để xác định ý nghĩa củaCollection,NumberichoặcSequence:
protocol Sequence {
    associatedtype Iterator: IteratorProtocol
    func makeIterator() -> Iterator
}
- Định nghĩa trên của Sequencecho chúng ta biết rằng vai trò chính của bất kỳ Swiftsequencenào (chẳng hạn nhưArray,DictionaryhoặcRange) hoạt động nhưfactoryđể tạo các vòng lặpđược chính thức hóa thông qua các điều sau đâyprotocol:
protocol IteratorProtocol {
    associatedtype Element
    mutating func next() -> Element?
}
- Với hai protocoltrên hãy quay trở lại cácprotocolcó thể lưu và có thể tạo màu mà chúng ta đã xác định trước đó, để xem liệu chúng có thể được cải thiện hay không bằng cách chuyển đổi chúng thành các định nghĩa yêu cầu thay thế.
protocol ColorProvider {
    var foregroundColor: UIColor { get }
    var backgroundColor: UIColor { get }
}
- Tương tự như vậy, chúng ta có thể đổi tên Cachablthành:
protocol CachingProtocol: Codable {
    var cacheKey: String { get }
}
- Hãy cùng chuyển mã tạo khóa của chúng ta thành các loại riêng biệt - sau đó chúng ta có thể chính thức hóa các yêu cầu để sử dụng protocol CacheKeyGenerator:
protocol CacheKeyGenerator {
    associatedtype Value: Codable
    func cacheKey(for value: Value) -> String
}
3/ Type conversions:
- Chúng ta hãy xem các protocolđược sử dụng để khai báo rằng một loại có thể chuyển đổi sang và từ cácvaluekhác. Chúng ta lại bắt đầu với một ví dụ từstandard librarylàCustomStringConvertibleđược sử dụng để cho phép bất kỳ loại nào được chuyển đổi thànhstringmô tả:
protocol CustomStringConvertible {
    var description: String { get }
}
- 
Kiểu viết đó đặc biệt hữu ích khi chúng ta có thể trích xuất một phần dâttừ nhiềutypehoàn toàn phù hợp với mục đích củaprotocolTitleable.
- 
Bằng cách đổi tên protocolđó thànhTitleConvertiblechúng không chỉ dễ hiểu hơn mà còn làm chocodecủa chúng ta phù hợp hơn vớistandard library:
protocol TitleConvertible {
    var title: String { get }
}
- Các protocolchuyển đổitypecũng có thể sử dụng cácmethodthay vì cácproperty. Điều này thường phù hợp hơn khi chúng ta muốn triển khai các yêu cầu tính toán hợp lý :
protocol ImageConvertible {
    // Since rendering an image can be a somewhat expensive
    // operation (depending on the type being rendered), we're
    // defining our protocol requirement as a method, rather
    // than as a property:
    func makeImage() -> UIImage
}
- Chúng ta cũng có thể sử dụng loại protocolnày để cho phép cáctypenhất định theo các cách khác nhau:
protocol ExpressibleByArrayLiteral {
    associatedtype ArrayLiteralElement
    init(arrayLiteral elements: ArrayLiteralElement...)
}
protocol ExpressibleByNilLiteral {
    init(nilLiteral: ())
}
- Ví dụ: Cách thức chúng ta có thể xác định protocol`` ExpressibleByUUIDcho các loại định danh có thể được tạo bằngUUID:
protocol ExpressibleByUUID {
    init(uuid: UUID)
}
4/ Abstract interfaces:
- 
Cuối cùng hãy để xem có lẽ cách sử dụng protocolphổ biến nhất trongcodecủa bên thứ ba để xác địnhabstractđể giao tiếp với nhiềutypecơ bản.
- 
Một ví dụ có thể được tìm thấy trong Apple Metal frameworkđó làAPIlập trình đồ họalow level. Vì GPU thay đổi rất nhiều giữa các thiết bị vàMetalnhằm mục đích cung cấpAPIphù hợp mọi loại phần cứng mà nó hỗ trợ, nên nó sử dụng mộtprotocolđể xác địnhAPI:
protocol MTLDevice: NSObjectProtocol {
    var name: String { get }
    var registryID: UInt64 { get }
    ...
}
- Khi sử dụng Metalchúng ta có thể gọi hàmMTLCreateSystemDefaultDevicevà hệ thống sẽ trả về việc thực hiệnprotocolphù hợp với thiết bị màcodecủa chúng ta hiện đang chạy:
func MTLCreateSystemDefaultDevice() -> MTLDevice?
- Ví dụ: chúng ta có thể xác định protocolNetworkEngineđể tách rời cách chúng ta thực hiện cácnetwork calltừ bất kỳ phương tiệnnetworknào:
protocol NetworkEngine {
    func perform(
        _ request: NetworkRequest,
        then handler: @escaping (Result<Data, Error>) -> Void
    )
}
- Chúng ta hiện có thể tự do định nghĩa số lượng  triển khai networkcơ bản mà chúng ta cần - ví dụ: một ứng dụng dựa trênURLSessionđể sản xuất và một phiên bản giả định để thử nghiệm:
extension URLSession: NetworkEngine {
    func perform(
        _ request: NetworkRequest,
        then handler: @escaping (Result<Data, Error>) -> Void
    ) {
        ...
    }
}
struct MockNetworkEngine: NetworkEngine {
    var result: Result<Data, Error>
    func perform(
        _ request: NetworkRequest,
        then handler: @escaping (Result<Data, Error>) -> Void
    ) {
        handler(result)
    }
}
All rights reserved
 
  
 