IGListKit/Source
Nate Stedman a6526ce097 Expose ListGenericSectionController.object and ListSectionController.collectionContext as implicitly-unwrapped optionals
Summary:
These methods are currently correctly annotated as `nullable`, because they can return `nil`. However, this is not practical for writing actual Swift code that uses the APIs. Overrides of `cellForItem(at:)` //must// return a `UICollectionViewCell`. Without a collection context, there's no good way to get one, so callsites must either:

- Force-unwrap the `collectionContext`.
- `guard let`/`else fatalError` the `collectionContext`.
- Return a default `UICollectionViewCell`, which just hides the bug.

None of these are good: we don't want to encourage things force-unwraps and `fatalError` in idiomatic product code, because people will see them and get the idea that these patterns are okay to use elsewhere, and they are not. This also happens with `IGListGenericSectionController`'s `object` value: to use it when creating a cell, it must be explicitly unwrapped.

We could make these `nonnull`, since there's no enforcement of the annotations in Objective-C, that feels a bit too far, and it would break existing code that uses the optional API, because you can't force-unwrap (etc.) a non-optional value. Instead, we can use `null_resettable` explicitly. [While it's not covered by name in the Apple docs](https://developer.apple.com/documentation/swift/objective-c_and_c_code_customization/designating_nullability_in_objective-c_apis):

> Without a nullability annotation or with a null_resettable annotation—Imported as implicitly unwrapped optionals

...it bridges the property to Swift as an implicitly-unwrapped optional. I suppose it's implied as the equivalent of "without a nullability annotation".

This works even for `readonly` properties that can't be reset, and it solves both issues: it feels like less of a "100% `nonnull`" guarantee, and it's backwards-compatible with code using optionals.

To demonstrate that this is backwards-compatible with existing Swift code, this test code, which runs through various optional/non-optional APIs, compiles:

```
final class TestSectionController: ListSectionController {
    override func cellForItem(at index: Int) -> UICollectionViewCell {
        let cell = collectionContext.dequeueReusableCell(for: self, at: index)
        let optional = collectionContext?.dequeueReusableCell(for: self, at: index)
        if let _ = collectionContext {}
        return optional ?? cell
    }
}

final class TestGenericSectionController: ListGenericSectionController<NSString> {
    override func cellForItem(at index: Int) -> UICollectionViewCell {
        let label = UILabel() // imagine it's from a cell
        label.text = object as String
        label.text = (object as String) + "Suffix" // wouldn't work with optional
        label.text = object.map { $0 as String }
        if let object = self.object {
            label.text = object as String
        }
        return collectionContext.dequeueReusableCell(for: self, at: index)
    }
}
```

Reviewed By: patters, lorixx

Differential Revision: D23603716

fbshipit-source-id: e4750dcfe0072d482951dbf2e9efb1ee3de46884
2020-09-10 12:30:10 -07:00
..
IGListDiffKit skip performBatchUpdates if possible 2020-09-08 09:11:12 -07:00
IGListKit Expose ListGenericSectionController.object and ListSectionController.collectionContext as implicitly-unwrapped optionals 2020-09-10 12:30:10 -07:00
IGListSwiftKit remove non-existing parameters from docblock (#1412) 2019-12-20 10:59:06 -08:00
Info.plist Refine dequeueReusableCellOfClass methods for Swift (#1388) 2019-12-19 08:13:22 -08:00