Search code examples
swiftrx-swiftrxdatasources

Clarity on init with `Self` requirement of SectionModelType in RxDataSources


Binding a sectioned table with RxDataSources using a TableViewSectionedDataSource, requires sections which conform to SectionModelType.

This SectionModelType protocol has the following initializer as one of it's requirements:

    init(original: Self, items: [Item])

In addition, the same protocol enforces var items: [Item] { get }. We can now initialize the items array backing variable (in above mentioned init) with either from original.items, or items passed as init parameter. This is very confusing. The SectionModelType code has no comments.

How section of README which explains creation of sections for this very case, talks about creating typealias (for associated value), and the items array, but not a word about following implementation of init with original: Self :

init(original: SectionOfCustomData, items: [Item]) {
    self = original
    self.items = items
}

While this can work in a struct, doing the same in a class shouts:

Cannot assign to value: 'self' is immutable

Can anyone here explain what is happening here and why do we NEED to init with Self

Lastly, is there another (cleaner) way to reactively (in RXSwift / RxCocoa only) bind a sectioned table view to an observable datasource. e.g. * my cells, and sections have their own data model, which need to be mutable (hence classes) * there are multiple screens with this requirement for different entities, so i would be interesetd in achieving this with protocols instead, and slap the corresponding data model with the protocol ans have a common implementation for RXBinding

Any pointers to either get clarity more on existing implementation, or achieving above points would be really helpful. P.S.: I am already working in a huge code base which uses Rx, so not using Rx, or moving to SwiftUI etc is not what I am looking for.


Solution

  • init(original:items:) is a copy initializer. It takes an already existing instance of the struct and makes a new one that is identical except that it changes what's in the items property.

    So in a protocol declaration, init(original: Self, items: [Item]) means: "You hand me an already existing instance of whatever this type is that is conforming to me, the protocol, and we'll make a new instance that copies it while changing its items."

    And that is exactly what the example does. It's easier to see if you put it all together, like this:

    protocol SectionModelType {
        associatedtype Item
        var items: [Item] { get }
        init(original: Self, items: [Item])
    }
    struct CustomData {
        var anInt: Int
        var aString: String
        var aCGPoint: CGPoint
    }
    struct SectionOfCustomData {
        var header: String
        var items: [Item]
    }
    extension SectionOfCustomData: SectionModelType {
        typealias Item = CustomData
        init(original: SectionOfCustomData, items: [Item]) {
            self = original
            self.items = items
        }
    }
    

    It's up to you, in your implementation of the extension, to do what they did: set self to original, which is some already existing SectionOfCustomData object (that is permitted in a struct initializer) and then change its items. And you must do things like that, and in that order, because if you don't, you'd be trying to initialize a SectionOfCustomData object without setting its header property — whereas, this way, we are guaranteed that there is already a header property value, because we are starting with an already existing SectionOfCustomData object, and you can't make one without setting its header property.

    As for your other worry, that that won't compile if this is a class, not a struct: yes, you're perfectly right, but don't do that. It isn't what they said to do. They said make this a struct, and they meant it.