I've got an app where I want to split its Model code into a framework (.xcframework and .framework for debugging) so that it can be used by more than one app.
The code has dependencies on 3rd party code, which are installed via pods.
During the conversion process I keep running into the same issue which manifests with all the 3rd party code - which is that the majority of its api can be used (something like 80-90%) but for the remainder there is a linker error at runtime showing undefined symbols. I have this problem with CocoaLumberjack,RealmSwift, PhoneNumberKit and more.
Its very quick and easy to reproduce the issue with a minimal framework and minimal app, below I'll describe how a minimal setup using CocoaLumberjack reproduces the issue:
From scratch, I use Xcode to create a framework project, run pod init, then modify the pod file to be:
platform :ios, '16.0'
workspace 'TheFramework'
project 'TheFramework'
target 'TheFramework' do
use_frameworks!
pod 'CocoaLumberjack/Swift', '3.8.5'
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0'
config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
end
end
end
Then I add source code:
import Foundation
import CocoaLumberjack
public class AClassInTheFramework {
public class func aMethod() {
let consoleLogger = DDOSLogger.sharedInstance
DDLog.add(consoleLogger, with: .debug)
DDLogDebug("Some logging")
}
}
Within the Xcode project, Build Libraries for Distribution is set to Yes, I also add that line to the pod file in case CocoaLumberjack isn't set similarly.
In the Framework's Xcode General section, Frameworks and Libraries contains Pods_TheFramework.framework set to Do Not Embed. In the Build Phases section, in the Link Binary with Libraries section, Pods_TheFramework.framework is set to required.
Next I create an Xcode app template, run pod install, and edit the app pod file to be:
platform :ios, '16.0'
workspace 'AppUsingFramework'
project 'AppUsingFramework'
target 'AppUsingFramework' do
use_frameworks!
pod 'CocoaLumberjack/Swift', '3.8.5'
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0'
end
end
end
I build the framework, and drag and drop it into the app. I add the following code to the app's delegate:
import TheFramework
...
AClassInTheFramework.aMethod()
The App's target has the following linkage settings:
When I build and run the app, there is the following error:
If I change the source code in the framework to this:
public class AClassInTheFramework {
public class func aMethod() {
let consoleLogger = DDOSLogger.sharedInstance
DDLog.add(consoleLogger, with: .debug)
// DDLogDebug("Some logging")
}
}
Then there is no error and the code runs successfully. This illustrates the problem I've encountered with all the nested frameworks - in this particular case calls to DDLog.add() don't result in an error but calls to DDlogDebug() do, and that has been mirrored with other nested frameworks (for example with Realm, opening a database, adding, finding,retrieving an item all works without a problem, however attempting to use Realm's Results<> API results in a similar symbol not found error).
Additionally note that the identical CocoaLumberjack code can run fine when used directly from within the app, i.e., if I add the following code to the app:
import CocoaLumberjack
func useCocoaLumberjackDirectlyFromWithinApp() {
let consoleLogger = DDOSLogger.sharedInstance
DDLog.add(consoleLogger, with: .debug)
DDLogDebug("Some logging")
}
useCocoaLumberjackDirectlyFromWithinApp()
Then it runs, i.e. DDLogDebug() can be successfully called from within the app, its only when its called via the framework that the error occurs.
Why might I be encountering these issues? I'd have thought either I'd be able to use 100% of the nested framework's public api, or 0% of it (is something is not configured correct), not ~80% which is what I am encountering.
Any ideas?
TIA