【问题标题】:Heterogeneous generic container in SwiftSwift 中的异构泛型容器
【发布时间】:2018-08-21 11:24:54
【问题描述】:

我无法将具有泛型类型的结构放入一个数组中。我知道 Swift 将 Array 的元类型转换为具体类型,这就是冲突。我试图找到一个不同的解决方案,但我想我需要你的帮助。

这里我定义了结构和协议:

protocol ItemProtocol {
    var id: String { get }
}

struct Section<T: ItemProtocol> {
    var items: [T]
    var renderer: Renderer<T>
}

struct Renderer<T> {
    var title: (T) -> String
}

这里有两个实现ItemProtocol的示例结构:

struct Book: ItemProtocol {
    var id: String
    var title: String
}

struct Car: ItemProtocol {
    var id: String
    var brand: String
}

这就是我设置这些部分的方式:

let book1 = Book(id: "1", title: "Foo")
let book2 = Book(id: "2", title: "Bar")
let books = [book1, book2]
let bookSection = Section<Book>(items: books, renderer: Renderer<Book> { (book) -> String in
    return "Book title: \(book.title)"
})
let car1 = Car(id: "1", brand: "Foo")
let car2 = Car(id: "2", brand: "Bar")
let cars = [car1, car2]
let carSection = Section<Car>(items: cars, renderer: Renderer<Car> { (car) -> String in
    return "Car brand: \(car.brand)"
})

现在我想把这些部分放在一起。这是我尝试过的。但是这 3 行中的每一行都给我一个错误:

let sections: [Section<ItemProtocol>] = [bookSection, carSection]
let sections2: [Section] = [bookSection, carSection]
let sections3: [Section<AnyObject: ItemProtocol>] = [bookSection, carSection]

sections.forEach({ section in
    section.items.forEach({ item in
        let renderedTitle = section.renderer.title(item)
        print("\(renderedTitle)")
    })
})

对于 sections 数组的声明,我收到此错误:

不支持将“ItemProtocol”用作符合协议“ItemProtocol”的具体类型

对于sections2数组的声明这个错误:

无法将“Section”类型的值转换为预期的元素类型“Section”

sections3 抛出这个:

预期 '>' 完成通用参数列表

【问题讨论】:

    标签: arrays swift generics


    【解决方案1】:

    问题在于不同的Section 类型(具有不同的通用参数)之间没有共同点(Any 除外)。一种可能的解决方案是将所有 Section 类型统一到一个协议中,并使用该协议来构建数组:

    protocol SectionProtocol {
        var genericItems: [ItemProtocol] { get }
        var renderedTitles: [String] { get }
    }
    
    extension Section: SectionProtocol {
        var genericItems: [ItemProtocol] { return items }
        var renderedTitles: [String] {
            return items.map { renderer.title($0) }
        }
    }
    
    let sections: [SectionProtocol] = [bookSection, carSection]
    
    sections.forEach { section in
        section.renderedTitles.forEach { renderedTitle in
            print("\(renderedTitle)")
        }
    }
    

    因此,您无需遍历元素,而是遍历呈现的标题,每个部分都应该能够构建这些标题。

    现在,这解决了您问题中的基本用例,但是根据您在应用程序中使用的部分,这可能还不够,您将不得不求助于键入橡皮擦,正如其他回答者提到的那样。

    【讨论】:

    • 您的示例中如何定义renderer
    • @lukwuerz 这与问题中的渲染器属性相同。
    【解决方案2】:

    要使用协议实现它,您需要使用类型擦除或转换为Any,它非常复杂或不安全。您可以采取另一条路线并使用代数枚举来实现它,例如:

    protocol ItemProtocol: CustomStringConvertible {
        var id: String { get }
    }
    
    enum ItemType {
        case book(title: String)
        case car(brand: String)
    }
    
    struct Item: ItemProtocol {
        let id: String
        let type: ItemType
    
        var description: String {
            switch self.type {
            case .car(let brand):
                return "Car brand: \(brand)"
            case .book(let title):
                return "Book title: \(title)"
            }
        }
    }
    
    let book1 = Item(id: "1", type: .book(title: "Title1"))
    let book2 = Item(id: "2", type: .book(title: "Title2"))
    
    let car1 = Item(id: "1", type: .car(brand: "Brand1"))
    let car2 = Item(id: "2", type: .car(brand: "Brand2"))
    
    
    struct Section {
        let items: [Item]
    }
    
    let section1 = Section(items: [book1, book2])
    let section2 = Section(items: [car1, car2])
    
    let sections = [section1, section2]
    

    【讨论】:

      【解决方案3】:

      结构 Section 是通用的,因此您不能将其用作类型。一种解决方案是使用类型擦除:

      创建任何ItemProtocol 包装器:

      protocol ItemProtocol {
          var id: String { get }
      }
      
      struct AnyItem : ItemProtocol {
      
          private let item: ItemProtocol
      
          init(_ item: ItemProtocol) {
              self.item = item
          }
      
          // MARK: ItemProtocol
          var id: String { return item.id }
      }
      

      还有一个类型擦除的Section,Any section:

      protocol SectionProtocol {
          associatedtype T
          var items: [T] { get }
          var renderer: Renderer<T> { get }
      }
      
      struct Section<Item: ItemProtocol>: SectionProtocol {
          typealias T = Item
          var items: [Item]
          var renderer: Renderer<Item>
      
          var asAny: AnySection {
              return AnySection(self)
          }
      }
      
      struct AnySection : SectionProtocol {
          typealias T = AnyItem
      
          private let _items: () -> [T]
          private let _renderer: () -> Renderer<T>
      
          var items: [T] { return _items() }
          var renderer: Renderer<T> { return _renderer() }
      
          init<Section : SectionProtocol>(_ section: Section) {
              self._items = { section.items as! [AnySection.T] }
              self._renderer = { section.renderer as! Renderer<AnySection.T>}
          }
      } 
      

      现在你可以收藏AnySections:

      let sections: [AnySection] = [bookSection.asAny, carSection.asAny]
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-08-19
        相关资源
        最近更新 更多