Swift: new wrapper based on C-API instead of ObjectiveC++ - #1061
Conversation
90c3e9b to
60646f2
Compare
77312a8 to
c6ec5ff
Compare
Not right away, if I can help it (see #726 (comment)). But if you want access to the libzint based backend, then you would (as of today) need to use the Swift based wrapper. |
|
I tested this wrapper over the last couple of weeks and it works very well. Since it hasn’t landed yet, I’d consider optimising for the most idiomatic Swift API shape now rather than carrying through rough edges from the native/C surface. From a Swift API design perspective, I think a few changes would make it feel much more natural and safer for Apple-platform use:
More broadly, I think the wrapper should lean Swift-first where it can by using typed APIs and value semantics by default, with low-level/raw escape hatches still available where the native surface is broader. I made a broader Swift-focused pass after this revision covering the items above plus some docs/tests/ergonomics cleanup. I’ve pushed a concrete implementation for reference here: swift-wrapper. Two commits so that a little easier to follow, logic in the first and then the second modularises into separate files. This doesn’t cover the Objective-C wrapping discussed in this comment, as I haven’t worked with Objective-C / CocoaPods myself. With that caveat, the current and proposed approach both sound reasonable to me. To sum up: I don’t think any of this is fundamentally wrong in its current shape, but I do think these proposed changes would make the wrapper significantly more “Swifty” before it lands! Thanks for kicking this all off @axxel, really fantastic and greatly appreciated. |
|
First of all: thank you very much for your detailed and in-depth feedback and actual proposal to look at. I wrote like 0 lines of swift in my life, so feedback from someone actually at home in this domain is very much appreciated. That said, there is a lot of stuff I like, a lot of stuff I don't like and a bunch of other stuff that I would need to be convinced of, yet. It'll take some iteration to go over those in a comprehensive manner. I'll just start somewhere...
I deliberately did not do that since I wanted to prevent 2 things: a) copying (meaning maintaining duplicates) or loosing default values and b) having to set all values, even when they are not changed. Loosing those features might be worth it if making them
You realized that keeping the Barcode handle is required for the zint machinery. I have not understood what your approach brings to the table then. I understand that you might want to determine equality of 2 barcodes. There needs to be a distinction made between reading and writing in this context. reading: The library has an internal concept of two barcodes being "euqual", that means two peaces of (linear) barcode or two detections from different resolutions are referring to the same symbol in the image. The idea is that every Barcode object returned from writing: The creation part is modeled as a Also, there is one expensive property (
Good catch, even though the
The retained object is the same (e.g.
Looks like I have missed that part, I'm running low on time right now, so I'm skipping that.
Not quite clear where you see the source of those drifting apart.
Totally agree.
Really appreciated. Before getting into nighty gritty details, here are some top-level thoughts:
100% my intention! :) Not quite sure how to efficiently move forward from here. The best I can currently think is: you could address my questions above, maybe change your code where there is no open question left and then it might make most sense for me to merge my code and then let you propose PRs (that are reasonably compartmentalized), which would allow to properly address individual parts with comments while making it sure that your contributions stay properly attributed in the end. Does that make sense to you? |
|
Thanks for the reply and the feedback! Some of this may well be me lacking project/C++ context, so I very much appreciate the clarifications.
Those two concerns are still covered in my implementation,
The main thing the struct approach brings is a Swift value-type surface with
Agreed, if
The concern is less about whether it happens to work in practice, and more about what lifetime the API actually guarantees.
No problem. On another look I don't think this one is worth changing. The current behavior is fine as-is.
The main thing I was thinking about is that the enums are manually mirrored between C++ and Swift with no codegen keeping them in sync. So if a case gets added on the C++ side and the Swift wrapper doesn't get updated at the same time, it would still compile fine and the crash would only show up at runtime when a user happens to hit the new value. It just feels more natural to me for the bridging to be defensive there, since a thrown error is a lot more actionable than a bare force-unwrap crash. And the code cost is slim, it's basically I tightened this into a single commit with just the points above implemented for reference: f0c3f7e. On your numbered points about the reference implementation (naming consistency, file structure, C API changes, etc.), those are all entirely fair, and I think separate focused PRs would be a better way to discuss the ones worth pursuing. Overall, I agree that the best option is to land this PR and then look at further changes from there. The current implementation already looks entirely usable from Swift/iOS to me, so none of my suggestions are blockers :) |
Not the two I had in my mind:
If being A little ChatGPT chat also made aware of the concept of an actor DecoderCore {
private let ptr: OpaquePointer
init() {
ptr = ZXing_CreateDecoder()
}
deinit {
ZXing_DestroyDecoder(ptr)
}
func decode(_ image: ImageData) -> DecodeResult {
ZXing_Decode(ptr, image.buffer, image.width, image.height)
}
}
public struct BarcodeReader {
private let core: DecoderCore
public init() {
core = DecoderCore()
}
public func decode(_ image: ImageData) async -> DecodeResult {
await core.decode(image)
}
}But that clearly looks like overkill to me, just to assemble and pass some parameters. But my question about what it is that you actually gain by making them sendable is still open. Can you describe a typical use case that would benefit from that feature? Maybe it would be obvious to me if I ever wrote an app in Swift... ;). From a high level perspective, all I want is to provide a single function
From what I just learned about
Thanks. Understood.
Very well.
That can only happen for returned enum types. Those are
All right. Will do then. Things that I like to see merged are:
|
|
I pushed a new commit that fixes a merge conflict, adds the |
No description provided.