Swift Package and main app Target doesn't share the same Singleton - ios

I'm trying to do something using singletons. I have a singleton class inside my Swift Package that I have to configure on didFinishLaunching calling foo.shared.config, in order to use it INSIDE my package. The thing is, that when a code inside my package runs, the foo.shared is a different instance. It's like my App creates a instance for foo.shared and my Package creates another, like they don't share the same global objects.
Some code:
Inside the package:
public class foo {
public static let shared = foo()
internal var credentialProvider: CredentialProvider? = nil
public func config(_ provider: CredentialProvider) {
self.credentialProvider = provider
}
}
Using it inside my package like:
When debugging here, the foo.shared points to a different instance than the one used on the following snippet, hence, returning nil.
guard let token = foo.shared.credentialProvider?.accessToken else {
return
}
Initializing inside didFinishLauching:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let myCredentialProvider = MyCredentialProvider()
foo.shared.config(myCredentialProvider)
...
}
MyCredentialProvider is defined inside the target app, but it's superclass is inside the Package:
public class MyCredentialProvider: CredentialProvider {
public init() { }
public var accessToken: String? {
get {
return someManager.shared.user.accessToken
}
}
}
I really need some directions on this because I can't fine any reference stating that singletons inside libraries share different resources. The same app uses other packages as singletons that does not share the same problem and works as expected.
I really don't need to be using singletons... what I really need, is something that works on this package that makes it loosely coupled with the App itself. I tried to check on Dependency Injection using property wrappers but the storage would probably have the same problem as the App and the package would create totally different instances for it.
Thanks in advance!

Related

SwiftUI 2 accessing AppDelegate

I did a small prototype which uses Firebase Cloud Messaging and the new SwiftUI 2 app life cycle. I added a custom AppDelegate via
#UIApplicationDelegateAdaptor(AppDelegate.self) var delegate and disabled method swizzeling for FCM to work. Everything is working as expected.
Today a colleague asked me, if one can get that delegate object via UIApplication.shared.delegate. So I gave it a shot and noticed that there seems to be two different AppDelegate objects:
po delegate prints:
<MyProject.AppDelegate: 0x6000009183c0>
where as po UIApplication.shared.delegate prints:
▿ Optional<UIApplicationDelegate>
▿ some : <SwiftUI.AppDelegate: 0x600000b58ca0>
Now I'm wondering what is the correct way of accessing the AppDelegate? Should one get it via an #EnvironmentalObject and pass it along all views? Or use the old fashioned way via UIApplication?
Additionally I would like to understand why I end up with two AppDelegates.
Your MyProject.AppDelegate is not direct UIApplicationDelegate, it is transferred via adapter to internal private SwiftUI.AppDelegate, which is real UIApplicationDelegate and which propagates some delegate callback to your instance.
So the solution might be:
Use #EnvironmentalObject if you need access to your MyProject.AppDelegate only in SwiftUI view hierarchy (for this AppDelegate must be confirmed to ObservableObject).
Add and use MyProject.AppDelegate static property which is initialized with object created via adapter, like
class AppDelegate: NSObject, UIApplicationDelegate {
static private(set) var instance: AppDelegate! = nil
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
AppDelegate.instance = self // << here !!
return true
}
}
now everywhere in your code you can access your delegate via AppDelegate.instance.

How to update Core Data when data is entered from multiple view controllers

This is how my view controllers are setup: I have a Main view controller which contains a horizontal scrollview and acts as a container. This container has another three view controllers, lets called them A, B and C in which I add different data:
In A I add a start and end time and click Next
In B I add a message string and click Next
In C I add some more data and click Done
In C, when Done is pressed I want to save the data from all three view controllers to Core Data.
The way I am doing now is in C I just call a save() method from Main using delegates.
Inside Main I just call the IBOutlets from A, B and C when saving to Core Data.
I am not sure if this is the right solution.
Is there any other elegant solution for this ? I am looking for something that I could easy unit test.
In your case I would use a Singleton, although some may disapprove I think in your instance it may be helpful:
final class DataCoordinator: NSObject {
private override init() {
print("Data Coordinator Initialized")
}
//Shared Instance:
static let appData = DataCoordinator()
var someVar: String = "" //public variable
fileprivate var mysteryNumber: Int = 0
...
and access properties like this:
DataCoordinator.appData.someVar
make sure you can access the singleton in every VC by calling it from the app delegate...
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
...
print(DataCoordinator.appData)
...
return true
}
you can also have it handle CoreData as well, that is to say you can make the context a shared property within the data coordinator and then access it from any view controller as well as save it with the typical functions:
public func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
print("Data Saved to Context")
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
//use like this
DataCoordinator.appData.saveContext()
you may also find it handy to write functions or use didSet to clear variables. Likewise, you can also have the DataCoordinator reload data from CoreData. I am not a big fan of this approach myself but use it when it is appropriate, I strongly suggest you read up on Singletons before implementing one or you may run into some issues down the road.

Routing Viper architecture

How to create good routing in project on Viper architecture? I start create file for routing but i don't understand what i must do next.
I create a file wireframe first controller and wireframe protocol:
// ChooseLanguageWireframeProtocol.swift
import UIKit
#objc protocol ChooseLanguageWireframeProtocol {
func presentChooseLanguageViewControllerWindow()
func presentAuthScreenViewController()
}
in file wireframe i add:
// ChooseLanguageWireframe.swift
import UIKit
class ChooseLanguageWireframe: NSObject , ChooseLanguageWireframeProtocol{
var chooseLanguageScreenViewController: ChooseLanguageViewController?
var window: UIWindow?
func presentChooseLanguageViewControllerWindow() {
let chooseLanguageViewController = UIStoryboard.init(name: "ChooseLanguage", bundle: nil).instantiateViewController(withIdentifier: "ChooseLanguage") as? ChooseLanguageViewController
self.chooseLanguageScreenViewController = chooseLanguageViewController
self.window!.rootViewController = chooseLanguageScreenViewController
self.window!.makeKeyAndVisible()
}
func presentAuthScreenViewController() {
}
}
After i create RootWireframe
// RootWireframe.swift
import UIKit
class RootWireframe: NSObject {
let chooseLanguageScreenWireframe : ChooseLanguageWireframe?
override init() {
//What i must init??
}
func application(didFinishLaunchingWithOptions launchOptions: [AnyHashable: Any]?, window: UIWindow) -> Bool {
self.chooseLanguageScreenWireframe?.window = window
return true
}
}
In file AppDelegate i change only
var window: UIWindow?
let rootWireframe = RootWireframe()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.rootWireframe.application(didFinishLaunchingWithOptions: launchOptions as [NSObject : AnyObject]?, window: self.window!)
}
What i must add or change for correctly work?
One way to do it is to inherit from UINavigationController and do the routing logic in there. You may end up with multiple classes doing routing in different parts of your application depending on your task.
But like with any cromulent buzzword you firstly have to ask yourself
if the "solutuon" solves more issues than it creates.
It starts to make sense when you get two dozen screens and start to get lost in
the very same issue that got resolved ages ago by adding storyboards to
plain old xibs.
So you really have to step back and ask yourself if you really want to follow
this cromulent architecture creating lots and lots of classes in the process,
a maze that will be hardly more readable than a standard MVC project.
If you can not use storyboards and want to use VIPER do so,
if you can use storyboards do VIPE :-)
But in most projects the datamodel is so pathetically simple and the
presentation is so tightly coupled that you have zero need in
UIViewController defattening.
And I suspect that the cure applied by the most viper affictionados is
worse than the decease itself.
2 possibilities:
Change the init of your rootViewframe to accept your dependency and initialize it.
And btw your variable type should not be the real type but the protocol, so you can mock it easily in tests
let chooseLanguageScreenWireframe : ChooseLanguageWireframeProtocol?
override init(chooseLanguage: ChooseLanguageWireframeProtocol) {
self.chooseLanguageScreenWireframe = chooseLanguage
}
And then create your chooseLanguageWireframe implementation in the appdelegate and pass it in the constructor.
This makes the dependency clear and visible. You can also remove the optionality, since you always initialize it.
Or solution 2
Create the chooseLanguageWireframe in the appdelegate and inject it there outside constructor
lazy var rootWireframe = {
let r = RootWireframe()
r.chooseLanguageScreenWireframe = self.chooseLanguageScreenWireframe
return r
}
In any case, you need to instantiate your dependencies somewhere, they cannot get created automatically.
You usually do that with factories and with the help of a dependency injection framework (check SwiftInject or Typhoon)
Also, declare all your dependencies with protocols type, it is the purpose of the VIPER architecture to simplify testing and mocking with isolations between actors

Parse.com subclassing in Swift 2

My question is about a bridge header that does not seem to work in Swift 2. I copied this code strait from the Parse.com iOS guide into xCode to see if it would work.
#import <Parse/PFObject+Subclass.h>
class Armor : PFObject, PFSubclassing {
override class func initialize() {
struct Static {
static var onceToken : dispatch_once_t = 0;
}
dispatch_once(&Static.onceToken) {
self.registerSubclass()
}
}
static func parseClassName() -> String {
return "Armor"
}
}
This doesn't work. I get an error on the #import <Parse/PFObject+Subclass.h> line with the error Consecutive statements of a line must be separated by ';'. So, my question is how I would go about subclassing in Parse.com with Swift 2. I have looked around the internet and haven't found anything. I think that there may have been a change in how Swift imports bridge headers, but I am not at all sure because I have never used a bridge header before. So, I could be doing something idiotic. Anyway, any help is greatly appreciated. Thanks.
First of all make sure you have the latest Parse SDK. If you have the latest SDK you can create a PFObject Subclass like this;
class Armor: PFObject, PFSubclassing {
static func parseClassName() -> String {
return "Armor"
}
}
When you create your custom Subclass, you should register your subclass to Parse SDK in AppDelegate like this;
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//Registering SubClasses
Armor.registerSubclass()
// Initialize Parse.
Parse.enableLocalDatastore()
Parse.setApplicationId("Your API Key", clientKey: "Your Client Key")
return true
}
Post SDK release 1.14.0 you should not need to register subclasses.
See the changelog from https://github.com/ParsePlatform/Parse-SDK-iOS-OSX/releases/tag/1.14.0.
There is also discussion of this issues in #1023 and 1035 where removing the calls to registerSubclass() has resolved looping problems in PFUser.

App doesn't enter in the initial ViewController using Typhoon

I have created a project to test the Typhoon framework , I have created two classes ApplicationAssembly and CoreAssembly where I inject some properties and constructors and a default Configuration.plist to load data from it.
ApplicationAssembly
public class ApplicationAssembly: TyphoonAssembly {
public dynamic func config() -> AnyObject {
return TyphoonDefinition.configDefinitionWithName("Config.plist")
}
}
CoreAssembly
public class CoreAssembly: TyphoonAssembly {
public dynamic func apiHandler() -> AnyObject {
return TyphoonDefinition.withClass(ApiHandler.self) {
(definition) in
definition.useInitializer("initWithDebugging:debugProcess:mainURL:") {
(initializer) in
initializer.injectParameterWith(TyphoonConfig("debug_mode"))
initializer.injectParameterWith(TyphoonConfig("debug_path"))
initializer.injectParameterWith(TyphoonConfig("api_url"))
}
definition.scope = TyphoonScope.Singleton
}
}
public dynamic func viewController() -> AnyObject {
return TyphoonDefinition.withClass(ViewController.self) {
(definition) in
definition.injectProperty("apiHandler", with:self.apiHandler())
}
}
}
I set in my Info.plist the TyphoonInitialAssemblies first the ApplicationAssembly and then the CoreAssembly.
Everything works fine without exceptions or anything except that the app never enters in AppDelegate neither in the ViewController class. I don't know maybe I missed something in the doc or anything.
What I'm missing here?
Why in debug not enter in the ViewController class that is the initial view controller in Storyboard?
The problem was that the ApiHandler class does not extend NSObject, which is a requirement. This is because Typhoon is an introspective Dependency Injection container. As Swift has no native introspection it uses the Objective-C run-time.
The App should not however have crashed in such an obfuscated way. I have opened an issue to look at how to fail with a meaningful error, rather than infinitely recurse.
After solving the initial problem, I also noted that the init method for ApiHandler passing in a Swift Bool object. This needs to be an NSNumber.
init(debugging : NSNumber, debugProcess : String, mainURL : String) {
self.debugging = debugging.boolValue
self.debugProcess = debugProcess
self.mainURL = mainURL
}
Given that Typhoon uses the Objective-C runtime, there are a few quirks to using it with Swift - the same kinds of rules outlined for using Swift with KVO apply.

Resources