Reputation: 2885
I have a ErrorManager
class, that's used in multiple targets of my project:
MyApp
MyAppWidgetExtension
MyAppIntent
Not all the targets share the same SPM dependencies (i.e. MyAppIntent
doesn't require Alamofire
) so I've used canImport()
to conditionally import the correct dependencies.
Unfortunately when trying to compile MyAppIntent
I get the following error:
Undefined symbol: type metadata accessor for Alamofire.AFError
I figured that canImport(Alamofire)
returns true even if it hasn't been added to the target because technically it could.
Is there a way to check at compile time if an external library has been actually imported using SPM?
I've had the same code up and correctly running on another project that uses Cocoapods to manage the dependencies.
--
// ErrorManager.swift
import Foundation
#if canImport(FirebaseCrashlytics)
import FirebaseCrashlytics
#endif
#if canImport(Alamofire)
import Alamofire
#endif
#if canImport(FirebaseAuth)
import FirebaseAuth
#endif
class ErrorManager {
static func logError(error: Error?, additionalInfo: String? = nil) {
guard let error = error else {
return
}
if let error = error as NSError?, error.domain == NSURLErrorDomain, error.code == NSURLErrorCancelled {
return
}
if let error = error as NSError?, error.domain == NSURLErrorDomain, error.code == NSURLErrorNetworkConnectionLost {
return
}
if let error = error as NSError?, error.domain == NSURLErrorDomain, error.code == NSURLErrorNotConnectedToInternet {
return
}
#if canImport(FirebaseCrashlytics)
if let additionalInfo = additionalInfo {
Crashlytics.crashlytics().log(additionalInfo)
}
Crashlytics.crashlytics().record(error: error)
#endif
#if canImport(FirebaseAuth)
if let error = error as NSError?, error.domain == AuthErrorDomain, let under = error.userInfo[NSUnderlyingErrorKey] as? Error {
return logError(error: under)
}
#endif
#if canImport(Alamofire)
if let error = error as? AFError, let under = error.underlyingError {
return logError(error: under)
}
#endif
}
}
Upvotes: 0
Views: 1251
Reputation: 13838
canImport()
doesn't help you in your task because it works on compilation time only so if a library's interfaces (headers, modulemap etc.) are accessible it always returns true. But while linking it produces next error "Undefined symbol: type metadata accessor for ..." because the library binary is not included into your app.
If you want to pass different error classes from your targets to common code and want to avoid to link all unnecessary dependencies you should make a common protocol that has all needed methods or properties from your classes and then check its values in runtime.
For instance, your common code can be like:
protocol CommonError {
var underlyingError: Error? { get }
}
extension NSError : CommonError {
var underlyingError: Error? { nil }
}
func logError(error: CommonError) {
if let underlyingError = error.underlyingError {
print("AFError underlying:", underlyingError)
}
else if let error = error as? MyError {
print("MyError:", error)
}
else if let error = error as? NSError {
print("NSError:", error)
}
}
Target with your custom error:
enum MyError {
case errorDomain
}
extension MyError: CommonError {
var underlyingError: Error? { nil }
}
Target with Alamofire
:
extension AFError: CommonError {}
Now you can pass error from you targets:
let error = NSError(domain: "NSError", code: 1, userInfo: nil)
logError(error: error) // NSError: Error Domain=NSError Code=1 "(null)"
// Target with your custom error
let myError = MyError.errorDomain
logError(error: myError) // MyError: errorDomain
// Target with Alamofire
let afError = AFError.explicitlyCancelled
logError(error: afError) // AFError underlying: Error Domain=AFError Code=1 "(null)"
Upvotes: 2