I’m going to port my iOS app to OS X (and perhaps tvOS after that). It uses the wonderful Realm for persistence. I’m currently in the process of breaking out the data model in my application into a dynamic framework, which I intend to use in both the iOS and OS X targets, to share that code, since it’s completely independent of UI.
I’m wondering what the best way to get Realm included here is. I will no longer need / want it as a dependency on the app itself, but I’d like the app to depend on the dynamic framework, then for that framework to depend on Realm. I don’t mind how this is done, i.e. I’m not particularly tied to Cocoapods.
The idea is that the apps themselves don’t see or care about Realm, or the persistence model used inside the framework. Like so:
iOS App -> Dynamic Framework -> RealmSwift
OS X App -> Dynamic Framework -> RealmSwift
tvOS App -> Dynamic Framework -> RealmSwift
-> = Depends on
I’d also like, if possible, for this to be smart, and include either the iOS or OS X builds of Realm, so that all I need to do is build the respective target in my Xcode project, and it’ll grab the right framework, right version of Realm, and all will be well.
How might I go about this? Is Cocoapods going to allow this? Is the dynamic framework the right idea in the first place? Should I make a podspec for the dynamic framework?
You can create middleware by defining s.dependency "Realm" in the podspec for your dynamic framework, if you think of it as just another pod in your app then you can have a nice abstraction layer that keeps you working above Realm specifically, I do this with analytics via ARAnalytics.
In terms of real-world linking, you'll have to link Realm to your app though, the runtime doesn't easily allow scoping a dependency specific to another library, as they exist inside a flat object graph.
Related
We are developing 8 SDKs, which are to be shared as XCFrameworks. The SDKs are interdependent on each other and also we have dependency on third party XCFrameworks which they have shared as Cocoapods.
Below is a simple demo of how the dependency graph looks.
A -> B, C
B -> D, E, F
C -> G, H
D, E, F -> H
H -> I, J
I -> K
here G and J are third party XCFrameworks. K is a third party FAT framework, written completely in Objective C. other letters denote the SDKs we are writing fully in Swift with Objective-C compatibility.
You can read the first line as A is dependent on B and C.
We started developing the SDKs by adding all these as Development Pods to a sample iOS Client. The only issue we faced was that even a small change in any of the pods required to clean build the entire project. We got around this setting "Legacy Build System" in Workspace settings.
When we approached release, we generated XCFrameworks and tried adding them to the Client.
We faced the following issue in almost all our frameworks. Because we had class names matching the SDK names, just like the old FAT framework.
‘FrameworkName’ is not a member type of ‘FrameworkName’ errors inside swiftinterface
Then we renamed all the frameworks and got struck with the following issue.
XCFramework requires to target iOS 13 for inter-framework dependencies with Objective-C compatibility
We are supporting from iOS 9. So moving to iOS 13 is not even an option. We were not able to find the exact cause of this issue for so long even after exploring the generated .swiftinterface file which was throwing these issues. We found that this issue could be because of using inheritance in the following way.
X is a Swift class, made Objective C compatible by inheriting NSObject. When we have another class Y from any of the other SDKs, inheriting X like below, this issue will be thrown.
For example, we have in Framework H.
#objc class X: NSObject { }
and then in framework D, which depends on H, we have
class Y: X { }
We removed it in one place and the issue resolved. We had this pattern all over the eight SDKs and we removed all those.
Then when we tried to build, we ran into the following issue.
'#objc' instance method in extension of subclass requires iOS 13.0.0.
We moved all #objc extension methods to the class. Those were mostly delegates and data source methods from common UIKit classes.
After facing these issues with XCFrameworks in the Client app, we decided to remove the development pod setup and manually drag and drop the xcodeproj files of all the SDKs into the Client app. Also we had to manually add the third party frameworks.
This way, some of the above issues can be identified during development instead of release time.
Now, the situation is that, when we make changes to any of the SDKs, it will be not be reflected unless we do a clean build, which takes a lot of time and is not desirable.
Even then, in some cases, the client app just throws Unable to import D or any other framework, in which we have made some changes. Then we to select that framework in the scheme, build it once and then select on to the Client App and build it again to see the change in our app.
This workflow is cumbersome and painful in the long run. Kindly suggest better alternative setups for developing multiple XCFrameworks.
Our next problem is when generating XCFrameworks, we need each SDK to use the XCFramework of the other dependent SDKs. For this, we have to work our way from the bottom most framework. Generate it, add to the one above it and then move on to the next.
This also feels like a burden in the long run. We seriously hope there are better ways to do it. Kindly suggest those.
We have not used Swift Package Manager (SPM) because most of our customers use Cocoapods for dependency management and the remaing few just drag the frameworks manually into their projects.
If SPM can act as a magic pill and solve all these issues at once, then we can consider using it.
Years ago, using ObjC and frameworks (and frameworks inside frameworks), it was common from time to time the “duplicate symbols” error. For example, if you created a framework including RestKit and then the app that wanted to use that framework was using RestKit too, you had that error. One way to fix it was to rename the included source code into your framework using a prefix. Or just avoid using 3rd party dependencies on your framework. Note we used to use static libraries, rather than dynamic libraries.
Is it possible to have the same problem with pure swift apps/frameworks and dynamic libraries today? As far as I understand, swift uses the concept of module for namespaces, something we didn’t have with ObjC.
I mean, is it possible to create a framework that uses Alamofire (let’s say version 5), and then create an app that uses Alamofire (let’s say version 5.0.1) AND your framework that uses Alamofire too?
I am using a similar setup and i did not face any such duplicate symbols in my project.
Let me share my experience.
I have a project setup like the one below.
Main Project -> uses PromiseKit via Cocoapods
My Framework -> uses PromiseKit via Cocoapods
Main Project uses My Framework
I did not face any such issue while running this setup. IMO, hope it is taken care by Swift compiler.
I can not use Realm within my framework as a framework because apple rejects nested frameworks.
However maybe there is no problem in using Realm as a pod depedence within my framework. Or is there a problem?
How does objective C be a single namespace I will not have collisions?
In my framework I want to capture GPS coordinates.
What is the benefit of using Realm for this versus file system?
Thanks
You're correct that the iOS App Store doesn't allow nested dynamic frameworks. The suggested solution is to place frameworks at the same directory level in your app bundle.
The benefits of using Realm are well documented on the official website: https://realm.io
Building Modern Frameworks addresses versioning and the importance of getting the API right the first time. Then, it says every app has its own copy of the framework. So then, can't I change my framework carelessly, i.e., without worrying about breaking other apps that are using older versions of my framework?
If we're talking about your own app on iOS, you can do whatever you like. The "framework" is merely a module like any other module; it is included in the app and is simply part of the app's code, so if you revise it, the next update gets the revision and the new code that uses it and there's no problem.
On OS X, however, there's an ability to install a framework into the library where the app will see it. Clearly in that case the code that uses the framework must be careful about versioning. Similarly, even on iOS, if you are using your framework as a way to convey a module to other developers, you must try not to break heedlessly their existing code that uses your framework.
I have my first iOS application under my belt (a relatively straightforward iPad app, to be released in a month or so), but now I'm moving on to something more ambitious, and could use some advice.
My next project will actually be two distinct but closely related iPad applications that will share quite a bit of core functionality and a common data format. So my instinct is to develop these two applications in conjunction with the development of a core framework shared by both, and I'd like this shared framework to use CoreData and also provide GUI elements (NIBs, view controllers) that can be used by the respective apps. My further instinct therefore is to create 3 Xcode projects: one iOS app project for app A, a second iOS app project for app B, and a third, CocoaTouch framework project for the shared framework.
Questions:
1) Is this the right way to structure my development, or is there a better way to do concurrent development of two closely related apps and a shared framework?
2) Can a framework use CoreData? (I ask this because when creating a CocoaTouch framework project in Xcode one isn't offered the option of having it use CoreData -- nor the option of having units tests -- as one is when creating an iOS app project).
3) Is there a way to "convert" an iOS app project into a framework project, or, perhaps preferably, rather to build an app project into a framework target? That would let me create the framework project with all the Xcode setup freebies (NIBs, core data, etc.), instead of adding all that stuff manually.
Thanks!
Carl
To answer question one, I wouldn't work this way. I would create one project with three targets, two app targets and a framework target. Maybe in the future I would move the framework to its own project when it had stabilized and if I was going to use it for even more iOS apps. Having all the targets in the same project reduce synchronization problems when working on code shared among the targets.
To answer question two, Xcode just doesn't include the template support to add Core Data and unit tests to a starter framework project. I see no reason why you can't use Core Data in a framework.
To answer question three, just add a framework target to your iOS app project, and move the appropriate portions of your iOS app to the framework.