iOS: circular dependency to call method in each other class
Asked Answered
D

5

1

I'm trying to implement circular dependency between my AppDelegate and my ViewController to call methods from my AppDelegate to my ViewController but it's not working.

See my code as follow:

AppDelegate.h:

@class ViewController;

@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong,nonatomic) ViewController *mainView;

@end;

AppDelegate.m:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    return YES;
}

- (BOOL)application:(UIApplication *)app
            openURL:(NSURL *)url
            options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
    [self.mainView doSomething];
    return YES;
}

ViewController.h:

#import <UIKit/UIKit.h>

@class AppDelegate;

@interface ViewController : UIViewController
@property (strong,nonatomic) AppDelegate *delegate;

-(void)doSomething;

@end;

ViewController.m:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.delegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
}

- (void)doSomething
{
    NSLog(@"doing something");
}

I don't have any errors or warnings but the method doSomething has never been call. Any of you knows why or what I'm doing wrong?

I'll really appreciate your help.

Doddered answered 31/5, 2017 at 16:29 Comment(8)
"the method doSomething is never [called]" — Are you using storyboards to initialize mainView? If not the mainView instance variable is probably not initialized.Capuchin
I just check and mainView is nil. Where do I initialize mainView?, appDelegate or viewController?Doddered
you need to initialise the mainVIew before you use it! So you need to do it in app delegate!Lymphoid
Can you share the rest of your appDelegate.m? Do you have your app opening normally without using a URL scheme first?Capuchin
@JoePasq, I have updated my post add more of my appDelegateDoddered
I add this on my view controller: self.delegate.mainView = self; but it seems not the same instance of the viewcontroller when it calls doSomething.Doddered
@Doddered please create a new project and observe how the views are initialized there. The Xcode starting point for a single view application should be imitable.Capuchin
@Doddered It will be helpful if you can provide more information on for which exact scenario / problem you have! , so we can provide better solution on it.Towage
S
8

This has nothing to do with circular dependencies.

As you've been told, the method doSomething is never called because you are saying

[self.mainView doSomething];

...at a time when self.mainView has never been given a value. Merely declaring a property

@property (strong,nonatomic) ViewController *mainView;

...does not point the variable mainView at your actual ViewController instance; it is nil, and a message to nil generates no error and causes nothing at all to happen.

You could fix this by having the ViewController set a reference to itself by adding one line to your code:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.delegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
    self.delegate.mainView = self; // <--
}

But don't! The simple truth is that your entire approach here is wrong. There should be no need whatever to keep a reference to your ViewController inside your app delegate. Your app has, at every moment, a view controller hierarchy. The app delegate should know where the ViewController is within that hierarchy.

Here we are in your app delegate when a link message comes in:

- (BOOL)application:(UIApplication *)app
        openURL:(NSURL *)url
        options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {

At that moment, it is the app delegate's job to know where the ViewController is in the view controller hierarchy, and even to arrange the view controller hierarchy so that the ViewController's scene is showing if it wasn't already, in response to the link message.

How you do that depends on the structure of your view controller hierarchy. You start at the top of the hierarchy, which, for the app delegate, is [[self window] rootViewController], and work your way down to the existing view controller you want to talk to.

You have not told us your view controller hierarchy structure, so it's impossible to help in detail. But let's say, for example, that your app revolves around a navigation controller interface. Then, for the app delegate, [[self window] rootViewController] is the navigation controller, and so you can cast to that class: (UINavigationController*)[[self window] rootViewController]. Then, if the ViewController is the navigation controller's root view controller, you take its viewControllers[0] to reach it, and again you cast as needed:

UINavigationController* nav = (UINavigationController*)[[self window] rootViewController];
ViewController* vc = (ViewController*)nav.viewControllers[0];

Now you can send the doSomething message to vc. But that's just an illustration; the precise details will depend on where the ViewController really is, within the view controller hierarchy. And of course you might also want to pop view controllers so that the ViewController's scene is actually showing, since you likely cannot guarantee that it is showing at the time the link message comes in.

Another completely different way of handling this situation is to use the NSNotificationCenter to post a notification for which the ViewController instance has registered. That is often a solution when you do not know exactly where in the view controller hierarchy your view controller is. But in this situation, you should know that.

Steven answered 18/5, 2019 at 23:21 Comment(0)
D
0

Try the below code :

AppDelegate.m :

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

    [[ViewController sharedInstance] doSomething];
    return YES;
}

ViewController.h

+ (ViewController *)sharedInstance;
- (void)doSomething;

ViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

+ (ViewController *)sharedInstance {
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    return [storyboard instantiateViewControllerWithIdentifier:@"ViewController"];
}

- (void)doSomething {
    NSLog(@"ViewController is doing something");
}

Output :

ViewController is doing something
Dariodariole answered 16/5, 2019 at 11:27 Comment(5)
how do I know I going to access to the same instance is running the view controller?, also what are you doing is generating a singleton not a circular dependencyDoddered
Check the updated code if it works. with sharedInstance, you can access any variable or method (public) in AppDelegateDariodariole
@user2924482, Above solution is fine, but what you want exactly. Could you please share more info, So we can find it.Mi
"This method creates a new instance of the specified view controller each time you call it." (c) Apple documentation So, this is not a singleton. Each time you invoke sharedInstance you will get new instance of the view controller.Tombac
It's a really bad design to have a singleton of a view controller and it certainly isn't needed here.Exeat
T
0

I don't have any errors or warnings but the method doSomething has never been call. Any of you knows why or what I'm doing wrong?

It happens because you haven't initialised an instance of ViewController. So, you have a nil at mainView. When you try to send a message "doSomething" to mainView you send message to nil. At Objective-C when you send a message to nil nothing is happens.

You should initialise an instance before you try to invoke the method. For example, at didFinishLaunchingWithOptions with such code:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{
    self.mainView = [ViewController new];
    return YES;
}

It will works if you create views programatically. If you use a storyboards or xib you should use another methods. Now you should see "doing something" at console when openURL is invoked.

BTW, you have a retain cycle between app delegate and view controller. So, your mainView will never release even if you make it explicitly nil. To avoid a retain cycle you should use attribute weak at ViewController.h:

@property (nonatomic, weak) AppDelegate *delegate;
Tombac answered 17/5, 2019 at 15:39 Comment(1)
Making the delegate weak doesn't change anything. The app delegate is never going away and whether this property is weak or not in the view controller will not have any affect on whether the view controller is deallocated or not. The viewcontroller property in the app delegate should be weak.Exeat
B
0

You can do that by the below code but excuse me for doing it with Swift:

class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?
var mainVC: ViewController?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
    mainVC = ViewController()
    mainVC?.doSomething()
    setupMainViewController()
    return true
}

func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
    mainVC?.doSomething()
    return true
}

func setupMainViewController(){
    guard window != nil else{
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.rootViewController = mainVC
        window?.makeKeyAndVisible()
        return
    }
    window?.rootViewController = mainVC
}

and the MainViewController will be:

class ViewController: UIViewController {

override func viewDidLoad() {
    super.viewDidLoad()
    doSomething()
}

func doSomething(){
    print("do something with \(self)")
}
Bula answered 21/5, 2019 at 10:32 Comment(0)
P
0

I appreciate all the other answers, I come here with a different approach. Generally, I used in my projects.

NSNotificationCenter defaultCenter Please check the code below.

Viewcontroller.m

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];


    //self.delegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
}
- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];

    // HERE REGISTER NOTIFICATION WHEN SCREEN IS APPEAR

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(doSomething:)
                                                 name:@"TestNotification"
                                               object:nil];
}


// HERE ADDS NOTIFICAIOTN AS PARAMETERS

- (void)doSomething :(NSNotification *) notification
{
    NSLog(@"doing something");

    // IF YOU PASSED VALUE WITH THE NOTIFICAIONT THEN IT WILL SHOW HERE
    NSDictionary *userInfo = notification.userInfo;
    id myObject = [userInfo objectForKey:@"someKey"];
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];

    // HERE REMOVE NOTIFICATION OBSERVER
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

AppDelegate.m :

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    return YES;
}

- (BOOL)application:(UIApplication *)app
            openURL:(NSURL *)url
            options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
    //WITHOUT SENDING ANY DATA
    [[NSNotificationCenter defaultCenter] 
    postNotificationName:@"TestNotification" 
    object:self];

    // IF YOU WANT TO SEND ANY INFROMATION THEN PLEASE USE THIS METHODS
    NSDictionary *userInfo = 
   [NSDictionary dictionaryWithObject:myObject forKey:@"someKey"];
   [[NSNotificationCenter defaultCenter] postNotificationName: 
                   @"TestNotification" object:nil userInfo:userInfo];
    return YES;
}

Here you do not need to check the visible view controller and no allocation required. if your screen is in the top then your methods are called otherwise not.

Good luck! :-)

Pointdevice answered 22/5, 2019 at 6:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.