Swift将协议用作头等类型

示例

面向协议的程序设计可以用作Swift的核心设计模式。

不同类型可以遵循相同的协议,值类型甚至可以遵循多种协议,甚至可以提供默认的方法实现。

最初定义的协议可以表示特定类型或通用类型的常用属性和/或方法。

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)
}

可以创建get方法的默认实现,但是如果需要的符合类型可能会覆盖该实现。

extension GetAPIItemDataOperation {
    
    static func get(url: NSURL, completed: ([ItemData]) -> Void) {
        
        let date = NSDate(
        timeIntervalSinceNow: NSDate().timeIntervalSince1970
            + 5000)
        
        // 从网址获取数据
        let urlData: [String: AnyObject] = [
            "title": "Red Camaro",
            "desc": "一辆快速的红色汽车。",
            "thumb":"http://cars.images.com/red-camaro.png",
            "created": NSDate(), "updated": date]
        
        // 在此示例中,使用了强制展开
        // 不得在实践中使用强制展开
        // 而是应使用条件展开(警卫或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
    
    // 也许告知新数据视图
    // 或解析数据以获取用户请求的信息等。
    dispatch_async(dispatch_get_main_queue(), { 
        
        //self.items = itemData
    })
    
}

不同的用例将需要不同的实现。这里的主要思想是展示各种类型的一致性,其中协议是设计重点。在此示例中,API数据可能有条件地保存到核心数据实体。

// 默认核心数据创建的类+扩展名
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
}

在这里,核心数据支持的类也可以符合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())
    }
}

// 在使用中,核心数据结果可以是
// 有条件地强制转换为协议
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)
        }
        
    }
}