Swift中的异构通用容器

Luk*_*ger 6 arrays generics swift

我在将通用类型的结构放入一个数组时遇到问题。我知道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
}
Run Code Online (Sandbox Code Playgroud)

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

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

struct Car: ItemProtocol {
    var id: String
    var brand: String
}
Run Code Online (Sandbox Code Playgroud)

这是我设置部分的方式:

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)"
})
Run Code Online (Sandbox Code Playgroud)

现在,我想将各个部分放在一起。这是我尝试过的。但是这三行都给我一个错误:

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)")
    })
})
Run Code Online (Sandbox Code Playgroud)

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

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

对于sections2数组的声明,此错误:

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

sections3抛出这个:

期望'>'来完成通用参数列表

Cri*_*tik 1

Any问题是不同Section类型(具有不同的泛型参数)之间没有共同点(除了)。一种可能的解决方案是将所有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)")
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,您不需要迭代元素,而是迭代渲染的标题,每个部分都应该能够构建这些标题。

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