Closed rnystrom closed 7 years ago
Ah, interesting.
But, id<IGListDiffable>
should now be imported as Any<IGListDiffable>
, right? So Swift value types should be able to conform... ?
@jessesquires to my knowledge all ObjC protocols in Swift inherit from NSObjectProtocol
even if we didn't declare it that way.
Just tried w/ this:
struct StructUser: IGListDiffable {
let name: String
let id: Int
}
And the compiler errors.
I see. I thought we declared this as @protocol IGListDiffable <NSObject>
, but we don't. It's @protocol IGListDiffable
.
Seems like this should be consider a Swift bug to me. We should search https://bugs.swift.org or ask around.
Just looked through the inter-op docs and didn't see anything about this. Intended behavior or not, for now I'm not sure if we can support swift value types.
Maybe we could provide a "Box" for swift value types:
public final class DiffableBox<T: Equatable>: IGListDiffable {
let value: T
let identifier: Any
let equal: (T, T) -> Bool
init(value: T, identifier: Any, equal: @escaping (T, T) -> Bool) {
self.value = value
self.identifier = identifier
self.equal = equal
}
// IGListDiffable
func diffIdentifier() -> Any {
return identifier
}
func isEqual(obj: Any) -> Bool {
if let other = obj as? T {
return equal(value, other)
}
return false
}
}
// Usage
let str = "my string"
let diffBox = DiffableBox(value: str, identifier: str, equal: ==)
Just a rough sketch, but you init with your value type, an identifier, and a equal closure/func.
You can package Swift + ObjC sources in a framework. If we add this, then this class should only be exposed to Swift clients and everything should just work.
Clients could .map
their data to DiffableBox
s, or we might be able to provide a small Swift wrapper or something so that the framework could handle this.
Got an answer 😄
Bummer, makes sense tho. I wish there was something like NS_REFINED_FOR_SWIFT
for this.
Boxing is an awesome idea though. Probably good to provide something like that.
@jessesquires check out _ObjectiveCBridgeable
(blog post here). I wonder if we can use something there?
h/t to @nlutsenko https://twitter.com/nlutsenko/status/782652105530019840
Interesting.
_ObjectiveCBridgeable
, but I can't remember the details — it might have changed._Protocol
(underscore) protocols from the std lib is considered bad practice. These are "private".Ah yes, this was removed in Swift 3:
Swift 2.2 docs: http://swiftdoc.org/v2.2/protocol/_ObjectiveCBridgeable/ Not found for 3.0: http://swiftdoc.org/v3.0/
Ah, perhaps not removed, but now properly private
.
https://github.com/apple/swift-evolution/blob/master/proposals/0058-objectivecbridgeable.md
https://lists.swift.org/pipermail/swift-evolution-announce/2016-April/000095.html
Ok good to know. I was looking at it more over the weekend too and don't think it would've helped. I think boxing is going to be the way to go for now.
Ah, ReferenceConvertible
?
Messed around w/ it, not sure how we can add an IGListDiffable
extension though. There has to be a way for this to work...
Is there still no way we can have a swift struct conform to IGListDiffable
?
@matthewcheok nope 😕 when using Swift objects have to be a class
. I'm sure there's a good pattern by boxing with something like Box<StructType>
where Box
is a class
conforming to IGListDiffable
.
Going to close this since there's nothing actionable right now (and for the foreseeable future)
Inspired by @jessesquires I will put my solution in here for enabling swift value types to conform to Diffable. I ended up with a new Swift protocol Diffable
and used Equatable
as the means for exposing isEqual(toDiffableObject object)
from IGListKit.
I then wrap diff'ing around a struct called DiffUtility
/**
A diffable value type that can be used in conjunction with
`DiffUtility` to perform a diff between two result sets.
*/
public protocol Diffable: Equatable {
/**
Returns a key that uniquely identifies the object.
- returns: A key that can be used to uniquely identify the object.
- note: Two objects may share the same identifier, but are not equal.
- warning: This value should never be mutated.
*/
var diffIdentifier: String { get }
}
/**
Performs a diff operation between two sets of `ItemDiffable` results.
*/
public struct DiffUtility {
public struct DiffResult {
public typealias Move = (from: Int, to: Int)
public let inserts: [Int]
public let deletions: [Int]
public let updates: [Int]
public let moves: [Move]
public let oldIndexForID: (_ id: String) -> Int
public let newIndexForID: (_ id: String) -> Int
}
public static func diff<T: Diffable>(originalItems: [T], newItems: [T]) -> DiffResult {
let old = originalItems.map({ DiffableBox(value: $0, identifier: $0.diffIdentifier as NSObjectProtocol, equal: ==) })
let new = newItems.map({ DiffableBox(value: $0, identifier: $0.diffIdentifier as NSObjectProtocol, equal: ==) })
let result = IGListDiff(old, new, .equality)
let inserts = Array(result.inserts)
let deletions = Array(result.deletes)
let updates = Array(result.updates)
let moves: [DiffResult.Move] = result.moves.map({ (from: $0.from, to: $0.to) })
let oldIndexForID: (_ id: String) -> Int = { id in
return result.oldIndex(forIdentifier: NSString(string: id))
}
let newIndexForID: (_ id: String) -> Int = { id in
return result.newIndex(forIdentifier: NSString(string: id))
}
return DiffResult(inserts: inserts, deletions: deletions, updates: updates, moves: moves, oldIndexForID: oldIndexForID, newIndexForID: newIndexForID)
}
}
private final class DiffableBox<T: Diffable>: IGListDiffable {
let value: T
let identifier: NSObjectProtocol
let equal: (T, T) -> Bool
init(value: T, identifier: NSObjectProtocol, equal: @escaping(T, T) -> Bool) {
self.value = value
self.identifier = identifier
self.equal = equal
}
// IGListDiffable
func diffIdentifier() -> NSObjectProtocol {
return identifier
}
func isEqual(toDiffableObject object: IGListDiffable?) -> Bool {
if let other = object as? DiffableBox<T> {
return equal(value, other.value)
}
return false
}
}
@danielgalasko 😲 this is super cool! cc @jessesquires
@danielgalasko reading this took me some time, before i understood it. Could you put here small example of usage?
sure thing @CurlyHeir but its very similar to how you would use IGListKit normally. So lets start with a struct:
struct TestDiff: Diffable {
var name: String
let id: String
var diffIdentifier: String {
return id
}
static func ==(lhs: TestDiff, rhs: TestDiff) -> Bool {
return lhs.name == rhs.name
}
}
let bob = TestDiff(name: "Bob", id: "1")
let tigger = TestDiff(name: "tigger", id: "2")
let initial = [bob]
let new = [bob, tigger]
let diff = DiffUtility.diff(originalItems: initial, newItems: new)
//diff.inserts == 1
Not sure if thats what you were asking but thats how I use it
Hello @danielgalasko ! How do you use Diffable
objects in public func objects(for listAdapter: IGListAdapter) -> [IGListDiffable]
?
@Eke you will see I created DiffableBox
that takes a Diffable
object and transforms it into IGListDiffable
.
diffableItems.map({ DiffableBox(value: $0, identifier: $0.diffIdentifier as NSObjectProtocol, equal: ==) })
Kinda like that :)
Just created an extension to Sequence
to wrap DiffableBox
. Still got hope for a solution one day :)
extension String: Diffable {
public var diffIdentifier: String { return self }
}
extension Int: Diffable {
public var diffIdentifier: String { return String(self) }
}
extension Sequence where Iterator.Element: Diffable {
func diffable() -> [ListDiffable] {
let toListDiffable: [ListDiffable] = map{ DiffableBox(value: $0, identifier: $0.diffIdentifier as NSObjectProtocol, equal: ==) }
return toListDiffable
}
}
// usage
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
return ["Daniel", "Hollis", "Tavares"].diffable()
}
is this still the way to go ? any full example please ? @danielgalasko
Chiming in here. Would be fantastic to simply use Hashable
conformance on the Swift side in place of ListDiffable
, since it provides both identity and equality comparison. Much easier said than done, however, since ListDiffable
a core type of the diffing algorithm. Not much flexibility off the top of my head without either constructing a DiffableBox like @jessesquires above or maintaining a completely separate Swift adapter implementation. I'm going to do some more independent thinking/research and see what could be realized.
@rnystrom Instagram looking to adopt Swift anytime soon? 😂
@levi that would be awesome, any progress ? the DiffableBox
works but it adds unnecessary code to the project. I hope that we can use Hashable
soon 🥇
Just letting you all know that I have a Swift bridge layer idea in progress that will handle all of this under the hood, letting you use Swift structs with IGListKit!
Sent with GitHawk
Thanks for the update, Ryan!
@rnystrom First off, this is such a killer library you've built!
I'm just wondering if there's been any movement on the Swift bridge layer mentioned above for enabling the use of Swift structs? Thanks!
@vibrazy HI @danielgalasko I really love your solution, but met some problems.
Seems func diffable() -> [ListDiffable]
tries to map the value using DiffableBox
.
When I want to use them in a ListAdapterDataSource
, in func objects(for listAdapter: ListAdapter) -> [ListDiffable]
using your extension is really awesome.
But when I try to use in func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController
, the object
actually the type of DiffableBox<T>
, the typecast process will crash my code.
And DiffableBox<T>
is private so can't access the real value.
Should you think make DiffableBox<T>
as public or something to solve this problem?
Thanks for your help!
@Arcovv I think I know the problem you are having. When you're making the switch for your sectionControllerFor object:
you have to actually define the concrete type you're expecting the Diffable box to conform to.
example of concrete diffablebox vs a non concrete
switch object {
case is DiffableBox<YourStruct>: //your type needs to look like this
return CFLCardSectionController()
case is DiffableBox<Any>: //your type cannot be this
return AccessCardSectionController()// casting will likely fail with this implementation
}
edit: after rereading your comment, I suspect I have a newer version of the diffablebox, so maybe what I'm saying doesn't apply
Any progress officially supporting this? @rnystrom
@rnystrom Any progress supporting this?
2019 Already, is this still a WIP?
I would just look into using DifferenceKit
or the new iOS 13 api's for this instead
Is 2022. Nothing yet ?
The handy
NSObject+IGListDiffable
category doesn't really help with Swift objects likeString
andInt
. This seems a little tricky becauseString
is astruct
but is convertible to anNSString
. Doing that conversion gives us theNSObject<IGListDiffable>
conformance, but that's kind of lame. I wonder if there's a way we can get this to work a little better?For example, the Swift 3.0 compiler wont allow this:
But you can get this to work a few ways:
Also adding an extension to
String
isn't easy either, sinceString
is a struct andIGListDiffable
is an Objective-C protocol...