Skip to content

iOS Wrapper improvements #630

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Oct 18, 2023
Merged

iOS Wrapper improvements #630

merged 16 commits into from
Oct 18, 2023

Conversation

benjohnde
Copy link
Contributor

The old wrapper had a few disadvantages which are solved by the following PR:

  • Package.swift resides now in root, enabling people to include zxing-cpp on iOS by just adding this repository as package dependency in Xcode
  • unsafeFlags are not used anymore, thus there is no need for injecting the framework manually in a hacky way
  • Creation of an 'umbrella framework' is avoided
  • Demo project was improved on a minor level
  • The cmake file was cleaned-up by removing the iOS bits

- move `Package.swift` to root
- use dynamic build
- get rid of `unsafeFlags`
- remove need of umbrella framework
- adjust readme and gitignore files
- add `ZXingCppWrapper` as swift package dependency
- embed `ZXingCppWrapper` framework in project
- lower minimum deployment target to `iOS 13.0`
- start stream on background thread
- use 32RGBA instead of 'old' yuv variant (nowadays devices stream native 32RGBA)
- add more camera variants to enable macro mode on newer devices
@benjohnde
Copy link
Contributor Author

@axxel ready for review and release, potentially 😏

@benjohnde
Copy link
Contributor Author

@alexmanzer I recognized your PR (#624), feel free to give this one a review. I removed the duplication of having a separate project to build the XCFramework which is then used by SwiftPM. One can now use zxing-cpp as package dependency and use the ZXingCppWrapper more easily.

Copy link
Collaborator

@axxel axxel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for the work done and the interest in merging our efforts. Since I'm not the author of most of the involved code here, I'll also ask those if they have any comments on your PR.

@axxel
Copy link
Collaborator

axxel commented Oct 16, 2023

To those who had their hands on any of the iOS/macOS wrapper code before: would you care to comment on this PR?

There are two changes that I'd like to specifically point to:

  • build-release.sh script removal, which might also loose the separate iphonesimulator Framework?
  • the removal of the 'core' ZXingCpp framework (see core/CMakeLists.txt)

@parallaxe, @Alex-MSFT, @alexmanzer, @ChristianNorbertBraun, @ryantrem

@benjohnde
Copy link
Contributor Author

benjohnde commented Oct 16, 2023

To those who had their hands on any of the iOS/macOS wrapper code before: would you care to comment on this PR?

There are two changes that I'd like to specifically point to:

  • build-release.sh script removal, which might also loose the separate iphonesimulator Framework?
  • the removal of the 'core' ZXingCpp framework (see core/CMakeLists.txt)

@parallaxe, @Alex-MSFT, @alexmanzer, @ChristianNorbertBraun, @ryantrem

Just to clarify on these two points:

  • the respective simulator frameworks (x86_64, arm64) get automatically built by the compiler using the Swift PM
  • the additional core framework was not necessary and resulted in an umbrella framework

@benjohnde
Copy link
Contributor Author

benjohnde commented Oct 16, 2023

@axxel I will wait for feedback from the other guys and then also adjust the gitlab action. I've overlooked that one, sry!

@benjohnde
Copy link
Contributor Author

Additional remark: if there is need for a solely published XCFramework, I would rather use the new configuration with some tool like https://github.com/unsignedapps/swift-create-xcframework.

@benjohnde benjohnde requested a review from axxel October 16, 2023 10:08
@benjohnde
Copy link
Contributor Author

CI was adjusted in 8f7f0e8, was a low-hanging fruit.

@parallaxe
Copy link
Contributor

First off, thanks @benjohnde for this PR.
Secondly, it would be great to also get opinions from the community about this PR, as this is a breaking change how the framework is build and used on iOS.

I discussed the consequences of the changes on our side with @alexmanzer. Our overall conclusion is: This is a very welcome pull request. Some of the decisions we made at the beginning are no longer relevant for ourself (i.e. having a build script that creates an xcframework that can then be distributed with Cocoapods - we are switching over to Swift Package Manager, and this PR makes this easier).

But there is one change that would break our requirements: The change from a static library to a dynamic one. We build frameworks for customers that use ZXing internally. But this is an internal detail the customer should not care about. Our philosophy is to hand them over one framework, without any external dependencies. But with a dynamic library, they have to add ZXing as additional dependency. It may seem like a little detail, but for us, this is important.

Also, from what I know, there are no real disadvantages of static libraries vs. dynamic ones.
@benjohnde you wrote that static frameworks will increase your overall app size - this should not be the case for most of the apps out there. Using a dynamic framework will copy all of the framework into your app, no matter what portion of the framework you use (except this has changed meanwhile - I'm no longer an iOS developer and am not up to date from this years changes). Using a static framework, the compiler will strip out code that is not used by the app / framework that links against it - so the overall app size may in fact be smaller.
Though, there might be the case where a dynamic framework reduces the overall size. If the app contains extensions that also use ZXing, they may share all of the dynamic libraries, instead of linking ZXing statically into the app and every extension. But I'm not even sure about that, and looking at the list of possible extensions, it most probably will only be a corner case of the corner case.

@benjohnde Do I overlook something that regards to dynamic vs. static? Would it be ok for you to change it to static? I'm not sure if Swift Package Manager may offer the possibility for the consumer of the package to choose between static or dynamic - if there is a way, it should be the best of both worlds.

@benjohnde
Copy link
Contributor Author

benjohnde commented Oct 16, 2023

@alexmanzer @parallaxe That is true to a certain degree, as only Apple frameworks are being used, it does probably not make a huge difference but normally dynamic libraries are smaller, as you don't link (store) other libraries in that one file. I won't be very detailed here as there is enough literature out there, one quick link would probably be https://theswiftdev.com/deep-dive-into-swift-frameworks/ or https://belkadan.com/blog/2022/02/Dynamic-Linking-and-Static-Linking/ (besides the cheesy title, good read though).

I can certainly feel the need for a static library sometimes, we also have that requirement for one of our customer (not related to zxing-cpp though).

My idea would then be (I should have implemented it like that right from the start) to omit .dynamic and .static. Then it gets built either dynamically or statically, depending on the target environment which is probably what you guys need, cf. library(name:type:targets:). Is that something we should experiment with? :)

"as this is a breaking change how the framework is build and used on iOS."

Still don't know how you have used the library so far, for us the built framework was not compliant to the App Store guidelines. Have you distributed that via the App Store (or TestFlight) or used some hack to get it injected properly? The produced library was not usable for us. Maybe we did a mistake there. I always experienced the following two issues:

@benjohnde
Copy link
Contributor Author

btw I love the discussion and effort to create a nice usable dependency 😃

@benjohnde
Copy link
Contributor Author

benjohnde commented Oct 16, 2023

Using a dynamic framework will copy all of the framework into your app, no matter what portion of the framework you use (except this has changed meanwhile - I'm no longer an iOS developer and am not up to date from this years changes). Using a static framework, the compiler will strip out code that is not used by the app / framework that links against it - so the overall app size may in fact be smaller.

Just realised, maybe you have mixed up both terms. Or I did a mistake here. You are describing the comparison between dynamic frameworks (Apple will strip code depending on the target device / platform) and static frameworks (the library contains everything to run independently on frameworks, which are available on the target side).

@alexmanzer
Copy link
Contributor

Still don't know how you have used the library so far, for us the built framework was not compliant to the App Store guidelines. Have you ever distributed that via the App Store (or TestFlight) or used some hack to get it injected properly? The produced library was not usable for us in an app which was sent to Apple. Maybe we did a mistake there. I always got the following two issues:

Yes, we have some apps in the AppStore that use zxing-cpp, among others CodeScan and DigitalProductPassport .
(With Xcode15 there was a new problem with the app upload, see here: #624 but with the change it works again with Xcode15).

You are right, the thing with the unsafeFlags was also a thorn in our side, that only worked if you either select the branch or a commit directly. The versioning as it is actually intended with SPM (via tag) is unfortunately not possible with unsafe flags. Therefore in general I am very pleased with your changes. 🙏

But the second argument (with the "umbrella framework") is not valid at all, from my perspective.
The "old way" creates two .frameworks which then are assembled into one .xcframework. And this .xcframework is used in our iOS apps. There is no umbrella framework at all needed for apps, that uses zxing-cpp.

Maybe as a general comment to this PR:
At the moment we also have an extra repo zxing-cpp-spm where we push the built .xcframework along with the Package.swift (in root) and Sources/Wrapper/ so we can get it easier via Xcode SPM.
But in general having the Package.swift in the root of the Repo would solve this also. 💪🏻
(In the past it was not so easy to place a file in the root folder here, so we had to go this workaround. 🤷🏻‍♂️)

@benjohnde
Copy link
Contributor Author

benjohnde commented Oct 16, 2023

Ouh nice!

You are right, the thing with the unsafeFlags was also a thorn in our side, that only worked if you either select the branch or a commit directly. The versioning as it is actually intended with SPM (via tag) is unfortunately not possible with unsafe flags. Therefore in general I am very pleased with your changes. 🙏

Thanks, I wasn't aware of that! We still get the warning 'Umbrella framework' though.

But the second argument (with the "umbrella framework") is not valid at all, from my perspective.
The "old way" creates two .frameworks which then are assembled into one .xcframework. And this .xcframework is used in our iOS apps. There is no umbrella framework at all needed for apps, that uses zxing-cpp.

As the unsafeFlag issue existed, I created a new XCFramework with the Wrapper files which then umbrella'd the other one. So I resulted in ZXingCppWrapper.xcframework which contained ZXingCpp.xcframework. So maybe that was totally on me and not what you guys had in mind.

…in swift pm

- without the `type` attribute, it gets either linked statically or dynamically depending on the target
@axxel
Copy link
Collaborator

axxel commented Oct 17, 2023

Also, since @benjohnde has not commented on #623 yet, please give this a quick thought, just in case the mentioned podspec question has any influence on the decisions made here.

@alexmanzer
Copy link
Contributor

alexmanzer commented Oct 17, 2023

Also, since @benjohnde has not commented on #623 yet, please give this a quick thought, just in case the mentioned podspec question has any influence on the decisions made here.

Hm, overall it's a question of how we/you want to proceed with iOS integration.
From my perspective, Cocoapods was the most widespread dependency manager in the iOS world for over 10 years. We also still have it in use on some older projects as well.
However, it must be said that the entire iOS developer world is currently moving towards Swift Package Manager (SPM), as a native package manager simply makes many things easier/better. Cocoapods is a monster in terms of functionality and complexity under the hood.
However, CocoaPods is not yet dead and SPM still has to come as far with some features as CocoaPods has come in the last 10 years.

Therefore, there are clearly two hearts beating in my chest:
For new projects I would always recommend SPM, but there are still enough projects out there that use CocoaPods. For them it would be nice if they could also use zxing-cpp.
So it's also a political question of saying:

  • fu__ off "legacy guys", use SPM! or
  • we are here for everyone, also for the older projects.

However, in the short term (= next days/weeks) I can't take the time to create a *.podspec and test through all the configurations, even though that's probably not the biggest thing in the world.

In general, CocoaPods should also have all the possibilities to build a framework from source during installation.
(I have seen in recent years podspecs like this one where the lib/framework is also created on pod install).
So @benjohnde's changes should not make CocoaPod integration impossible at all.
Maybe a little more complicated.

Ultimately, however, this would also mean having to have another file (*.podspec) at root level.
And I know that this is a sensitive topic. 🫣

@benjohnde
Copy link
Contributor Author

benjohnde commented Oct 17, 2023

Apart from the still failing CI build (see error message about the SPM version) I don't have any objections anymore.
@benjohnde I'd probably prefer to "squash" your commits to get rid of a bit of cleanup/revert noise.

Sure, squash that one! I will fix the CI in a sec.

I do totally agree on what @alexmanzer just stated, there are currently three "camps", CocoaPods, Carthage (still people are using that though) and SPM.

For CocoaPods, what was described here is a possibility to go (using the Framework in a Pods reference). I would still rather go with the same approach like the Package.swift: describe what needs to be built as CocoaPods is a wonderful tool for that. I did so with the old Objective-C port, cf. https://github.com/zxingify/zxingify-objc/blob/master/ZXingObjC.podspec. I can tackle that later next week for #623.

At which point I then define whether the lib is then built statically or dynamically when this info is flown out of the Package.swift.

Depends on your usage, if your library (which uses zxing-cpp) is linked statically, then your deps also have to be linked statically (at least should be, otherwise you cannot guarantee your static library is working "statically", i.e. not relying on other libraries installed on your system). I am very open to have a discussion or a little pair-programming session on that, if there are issues arising because of my proposed changes!

Lastly, if you really need that explicit .static param, in my thinking that should probably reside in some fork of yours than 'forcing' everybody else to use this dependency as a static build.

@benjohnde
Copy link
Contributor Author

@axxel I just lowered the swift-tools-version to 5.7.1. I mainly raised that due to CXXLanguageStandard.gnucxx20, which was not supported in the initially set version.

@parallaxe
Copy link
Contributor

@benjohnde Thanks for the changes, sounds good to me! Like @alexmanzer wrote, we have to figure out how to configure the static/dynamic-part at build time, but hopefully there will be a solution.

Regarding your comment on static / dynamic libraries

Just realised, maybe you have mixed up both terms. Or I did a mistake here. You are describing the comparison between dynamic frameworks (Apple will strip code depending on the target device / platform) and static frameworks (the library contains everything to run independently on frameworks, which are available on the target side).

Phuh, it's a complex topic, and Apple has it's own share of complexity on its own. So it would be no wonder if we are talking past each other at some point. 😉 I don't know if it is that important at this point to get this clear, but I give it a shot by outlining my understanding of the topic:

  • At this point, we should differentiate between a static / dynamic library (.a / .dylib) and static / dynamic framework (.framework / .xcframework). Breaking it down, a library is a binary that contains the executable code and exported symbols; a framework contains the library and bundles it with additional resources and meta informations. Frameworks make it easier to use a library, as its containing meta informations enable IDEs like Xcode to enable autocompletion & co, and you don't need to add headers / modulemaps manually.
  • A library can contain the code for one architecture (the default) or multiple architectures (called universal or fat library).
  • A framework may contain a universal / fat library, or a single architecture library. A xcframwork bundles multiple single architecture frameworks, where each framework is put into a directory for its specific architecture.
  • When an iOS app is build for multiple architectures and uploaded to the AppStore (independently of the used libraries, frameworks, ...), the AppStore splits it into multiple apps and strips them down, for the specific platform and architecture (which can be seen in the AppStore, i.e. Screenshot 2023-10-17 at 12 17 45)
  • The difference is: When building the app and linking against a static library / framework, the compiler will eliminate dead code. This includes the code of the static library. When building the app and linking against a dynamic library / framework, it will end up as-is on the customers iPhone, without eliminating the parts of the code the app does not use.

@benjohnde
Copy link
Contributor Author

benjohnde commented Oct 17, 2023

@parallaxe sry, my knowledge is probably just wrong, I will re-read some articles (e.g. https://bpoplauschi.github.io/2021/10/25/Advanced-static-vs-dynamic-libraries-and-frameworks.html).

I had the assumption that dynamic should be favoured over static in apps. That was probably wrong.

@parallaxe
Copy link
Contributor

Dynamic frameworks are the default for apps and in general. My assumption is for this is, that this enables a faster iteration at development time (if all needed libraries are statically linked into the app, the linking step could take longer than the compilation step itself). Though, for ZXing with a binary size of <2MB per architecture, this should not be noticeable.
Besides that, I think static libraries are not as bad as its reputation.

@benjohnde
Copy link
Contributor Author

Sry I forgot to push, 19241b6 is up. @axxel ci should hopefully turn green 😏

@axxel
Copy link
Collaborator

axxel commented Oct 17, 2023

Sry I forgot to push, 19241b6 is up. @axxel ci should hopefully turn green 😏

Ähm. no... But there is progress ;).

@alexmanzer
Copy link
Contributor

I had the assumption that dynamic should be favoured over static in apps. That was probably wrong.

Isn't the first illustration from the article actually the answer to having the type in Package.swift .static?
--> "Small App Size", "Fastest", "Safe" 👀
image

Anyway: I have just played around with all the latest changes.

As long as the .dynamic stays out of the Package.swift, I'm happy.
(If you have .dynamic in, then you also have to remember that you have to explicitly change this setting in Xcode:
Screenshot 2023-10-17 at 15 53 43

--> otherwise the app will crash at runtime, see here:
image

But there is no problem at all with leaving the .type completely out from Package.swift. 😃
Xcode also seems to automatically take the .static variant, since I don't see anything of the zxing-cpp framework in a finished *.ipa either.

tldr: So from my side there is a clear Go! 🚀

@benjohnde
Copy link
Contributor Author

@alexmanzer I had the same issue with .dynamic, the library needs to be embed. I mixed up both variants, my bad :)
@axxel Check, I will try to fix that as well

@benjohnde
Copy link
Contributor Author

@axxel I hope 2f0d820 does some justice w.r.t. the ci job. Can I also manually run the test?

@axxel
Copy link
Collaborator

axxel commented Oct 17, 2023

@axxel I hope 2f0d820 does some justice w.r.t. the ci job. Can I also manually run the test?

Seems to have had no effect. I thought you can simply manually trigger a ci job on the Actions page, but I could not find it now. hm. Maybe you need to somehow activate GA on your fork and then any push from you triggers a ci run?

@alexmanzer
Copy link
Contributor

alexmanzer commented Oct 17, 2023

@benjohnde Just tried some things here to find the problem with the build:

If I execute xcodebuild build -scheme demo -sdk "iphonesimulator" on my machine with Xcode15, everythings runs fine.
In one of our older build machines, that have still Xcode 14.3.1 (like the GitHub runners) I get the same problem as in the CI job:

/Users/ci-macos-01/tmp/zxing-cpp/wrappers/ios/demo/demo.xcodeproj: error: Missing package product 'ZXingCppWrapper' (in target 'demo' from project 'demo')
/Users/ci-macos-01/tmp/zxing-cpp/wrappers/ios/demo/demo.xcodeproj: error: Missing package product 'ZXingCppWrapper' (in target 'demo' from project 'demo')
** BUILD FAILED **

If I try to open the demo project with Xcode 14.3.1 it also throws this error:
image

So it is really a problem of the already updated demo project.

@alexmanzer
Copy link
Contributor

alexmanzer commented Oct 17, 2023

@benjohnde @axxel
Ok, I got it working again with Xcode14, this will fix the GitHub action.
Unfortunately I can't commit anything here, but this git patch fixes the problem:

zxing-cpp-bc06b10-Fix demo project for Xcode 14 again.patch

Co-authored-by: Alexander Manzer <alexander.manzer@kurzdigital.com>
@benjohnde
Copy link
Contributor Author

@benjohnde @axxel Ok, I got it working again with Xcode14, this will fix the GitHub action.

Thanks a million for patch @alexmanzer! Works on my fork, so it should also turn green here @axxel!

@benjohnde
Copy link
Contributor Author

Ah, I did a mistake. I tried to use the macos-13 runner but forgot to update to Xcode 15. The provided patch solves both, Xcode 14 and Xcode 15 usage. Guess we are good to fuel that into the master ⛽ 🚀 !

@alexmanzer
Copy link
Contributor

alexmanzer commented Oct 17, 2023

The provided patch solves both, Xcode 14 and Xcode 15 usage. Guess we are good to fuel that into the master ⛽ 🚀 !

🚀🚀🚀

So, thanks to all your work @benjohnde; together with some other iOS improvements (e.g. also this open pull request) the iOS usage of zxing-cpp really becomes very powerful and I think there is no reason to use the old zxingify-objc anymore. 💪🏻

@benjohnde
Copy link
Contributor Author

@parallaxe I have to admit that my knowledge was wrong. As far as I understood it, dynamic frameworks are useful for development (if linking statically is time consuming) and environments, in which you want to change the underlying framework without compromising the target (i.e. in which the framework is being used) - like hot-swapping frameworks 😏 without re-deploying the whole application. It could also be useful, if the downside of linking statically would result in a dramatic increased app size. Apart from that, it seems that static is more useful in an app context.

@axxel axxel merged commit 5e8c82d into zxing-cpp:master Oct 18, 2023
@axxel
Copy link
Collaborator

axxel commented Oct 18, 2023

Thanks to all of you for this pretty intense collaboration! This is the first time two parties have been working together on something in this code base, where I was neither of those two parties. :)

@alexmanzer
Copy link
Contributor

@axxel Thanks for merging into the master! 🙏

Merely for the sake of interest: When do you usually set a new version tag to this repo?
Because with this PR (and a new version tag) it is then actually finally possible to use the versioning system of SPM as it is intended.

(It would be cool to include the change from the other pull request, that would be really helpful for us.)

@axxel
Copy link
Collaborator

axxel commented Oct 18, 2023

When do you usually set a new version tag to this repo?

Usually right before the next release. I understand that this might cause confusion when just grabbing HEAD and tag it with the latest release number even if the code changed. But even if I started introducing some -alpha like naming scheme applied directly after a release, it would still not be the best idea to always simply grab HEAD when you build your app, right? So I would imagine there must be a way to specify in your SPM config which tag you'd like to download?

@alexmanzer
Copy link
Contributor

@axxel When importing an SPM package into Xcode the package manage will care about the versions, but only if the repo has "semantic version tag", like 2.1.0.

If you add a package in Xcode, you get this dialog:
Screenshot 2023-10-18 at 15 26 30

And the options for "Dependency Rules" are:

Screenshot 2023-10-18 at 15 26 39

So, Xcode SPM can only care about updates, when there is a version tag.
Sure, there is possibility to select a branch or a commit, but it's always better to reference to a defined version IMO.

And the fact that it's only possible with this PR here that you can even use the SPM like this would of course strengthen a new version tag. But of course, this mainly affects the iOS community
But since maybe lots of zxingify-objc users will end up here soon, it might be worth it. 🙂

@axxel
Copy link
Collaborator

axxel commented Oct 18, 2023

So, it will always only pull individual commits that are tagged SemVer style. That totally makes sense.

I'm Ok with making a new release soonish.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy