Ugo
Ugo

Reputation: 183

How can I prevent ViewControllers from being deallocated after I inject them with a dependency in application:DidFinishLaunchingWithOptions:

I am trying to inject a dependency (to a data store) into all the viewControllers in my tabbed app, from AppDelegate, rather than access the datastore by reaching back into the appDelegate. I am using a storyboard.

I do this in application:didFinishLaunchingWithOptions:, and the code executes without errors.

However, when any of the viewControllers is presented, the property into which I have injected the datastore contains nil. I was expecting it to have a reference to the datastore.

I thought maybe my datastore went out of scope after application:didFinishLaunchingWithOptions returns and caused the datastore to become nil. But to my knowledge ARC should prevent that.

I started to suspect that maybe the VCs might go out of existence after application:didFinishLaunchingWithOptions: finishes running. So I added a dealloc method to the view controllers to see if it gets called, and lo-and-behold, it did. That explains why the dependency I injected previously is no longer there.

Now I am stuck, as I don't know how else to inject the dependency into the view controllers. The only idea I have left is to add properties to my AppDelegate and use them to retain the view controllers, but that feels a bit dangerous cause I'm now interfering with iOS management of view controllers.

Here is the code in AppDelegate:

//AppDelegate.h
#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end

//AppDelegate.m
#import "AppDelegate.h"
#import "InjectedViewController.h"
#import "InjectedDataStore.h"
@interface AppDelegate ()
@property (strong, nonatomic) InjectedDataStore *myDataStore;
@end

@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.

UIStoryboard *storyBoard;
storyBoard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
UIViewController *initViewController = [storyBoard instantiateInitialViewController];
[self.window setRootViewController:initViewController];


if (!_myDataStore) {
    self.myDataStore = [[InjectedDataStore alloc]init];
    NSLog(@"alloc inited %@", self.myDataStore);
}

UITabBarController *tabBarController = (UITabBarController *)initViewController;
    for (InjectedViewController *ivc in tabBarController.viewControllers) {
        ivc.dataStore = self.myDataStore;
        NSLog(@"dataStore injected into ivc: %@", ivc.dataStore);
    }

NSLog(@"application:didFinishLaunching... done");

return YES;
}

@end

here is my view controller subclass with the property into which I want to inject:

//InjectedViewController.h

#import <UIKit/UIKit.h>
#import "InjectedDataStore.h"

NS_ASSUME_NONNULL_BEGIN

@interface InjectedViewController : UIViewController
@property (strong, nonatomic) InjectedDataStore *dataStore;
@end

NS_ASSUME_NONNULL_END

InjectedViewController.m is boilerplate and otherwise empty. InjectedDataStore.m and .h are a boilerplate Cocoa Touch class without any properties or methods.

and here is one of the viewcontrollers - it is embedded in a tab view. (The other view controller for the other tab is identical.

//FirstViewController.h
#import <UIKit/UIKit.h> 
#import "InjectedViewController.h"
@interface FirstViewController : InjectedViewController
@end

//FirstViewController.m
#import "FirstViewController.h"

@interface FirstViewController ()

@end

@implementation FirstViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(@"data store for FirstVC: %@", self.dataStore);
}

- (void) dealloc {
NSLog(@"First VC dealloc called");
}
@end

and finally, the console output:

alloc inited <InjectedDataStore: 0x600001d483a0> 
dataStore injected into ivc: <InjectedDataStore: 0x600001d483a0> 
dataStore injected into ivc: <InjectedDataStore: 0x600001d483a0>
application:didFinishLaunching... done

First VC dealloc called   ///this is what causes the injected element to disappear.
Second VC dealloc called  ///causes the injected element to disappear.

data store for FirstVC: (null) 
data store for SecondVC: (null)

(I would eventually implement a protocol, but for now since I am stuck at getting the injection to work I have left that out.)

It seems to me what I have done here is very similar to @juanignaciosi 's answer to this question:

Appreciate any feedback, I am a relative newbie to iOS.

Upvotes: 0

Views: 148

Answers (1)

Cy-4AH
Cy-4AH

Reputation: 4585

Your's window property is nil. Since iOS 13 system is using window property from scene delegate. Just remove scene delegate and UIApplicationSceneManifest from plist if you don't need it.

Upvotes: 1

Related Questions