iOS:循环依赖于在每个其他类中调用方法

时间:2017-05-31 16:29:12

标签: ios objective-c circular-dependency

我正在尝试在我的AppDelegate和我的ViewController之间实现循环依赖,以便从我的AppDelegate调用我的ViewController方法,但它无效。

请参阅我的代码如下:

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");
}

我没有任何错误或警告,但方法doSomething从未被调用过。你们中的任何人都知道为什么或者我做错了什么?

我真的很感谢你的帮助。

5 个答案:

答案 0 :(得分:8)

这与循环依赖无关。

正如您所知,从未调用方法doSomething,因为您是在说

[self.mainView doSomething];

...从未向self.mainView赋予的时间。仅仅声明一个属性

@property (strong,nonatomic) ViewController *mainView;

...不会将变量mainView指向您的 actual ViewController实例;它是nil,并且发给nil的消息不会产生错误,也不会导致任何事情发生。

可以通过在代码中添加一行来使ViewController设置对自身的引用来解决此问题:

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

但是不要!一个简单的事实是,您的整个方法都是错误的。在您的应用程序委托中,无需保留对ViewController的引用。您的应用随时都有一个视图控制器层次结构。应用程序委托应该知道ViewController在该层次结构中的位置。

当出现链接消息时,我们在您的应用程序委托中:

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

那时候,应用委托人的工作是了解 ViewController在视图控制器层次结构中的位置,甚至是安排视图控制器层次结构,以便响应链接消息,正在显示ViewController的场景是否还存在。

如何执行此操作取决于视图控制器层次结构的结构。您从层次结构的顶部开始,对于应用程序代表来说,层次结构是[[self window] rootViewController],然后一直走到要与之对话的现有视图控制器。

您尚未告诉我们您的视图控制器层次结构,因此无法提供详细帮助。但是,例如,假设您的应用围绕导航控制器界面旋转。然后,对于应用程序委托,[[self window] rootViewController]是导航控制器,因此可以强制转换为该类:(UINavigationController*)[[self window] rootViewController]。然后,如果ViewController是导航控制器的根视图控制器,则以其viewControllers[0]到达它,然后根据需要再次投射:

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

现在您可以将doSomething消息发送到vc。但这仅是一个例子。具体的细节取决于ViewController层次结构中ViewController的实际位置。当然,您可能还希望弹出视图控制器,以使ViewController的场景实际显示为 ,因为您可能无法保证在链接消息进入时就显示了该场景。

处理这种情况的另一种完全不同的方法是使用NSNotificationCenter 发布一个已注册ViewController实例的通知。当您确切知道您的视图控制器在视图控制器层次结构中的什么位置时,这通常是一种解决方案。但是在这种情况下,您应该知道这一点。

答案 1 :(得分:0)

尝试以下代码:

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");
}

输出:

ViewController is doing something

答案 2 :(得分:0)

  

我没有任何错误或警告,但是从未调用过doSomething方法。你们谁都知道为什么或我做错了什么?

发生这种情况是因为您尚未初始化ViewController的实例。因此,您在nil有一个mainView。当您尝试向mainView发送消息“ doSomething”时,您会将消息发送给nil。在Objective-C中,当您向nil发送消息时没有任何反应。

在尝试调用该方法之前,应初始化一个实例。例如,在didFinishLaunchingWithOptions处使用以下代码:

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

如果您以编程方式创建视图,它将起作用。如果使用情节提要或Xib,则应使用其他方法。 现在,当调用openURL时,您应该在控制台上看到“正在执行操作”。

顺便说一句,在应用程序委托和视图控制器之间有一个保留周期。因此,即使您明确地nil使其mainView也将永远不会释放。为了避免保留周期,您应该在 ViewController.h

使用弱属性
@property (nonatomic, weak) AppDelegate *delegate;

答案 3 :(得分:0)

您可以通过以下代码来执行此操作,但是请原谅我使用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
}

和MainViewController将是:

class ViewController: UIViewController {

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

func doSomething(){
    print("do something with \(self)")
}

答案 4 :(得分:0)

我感谢所有其他答案,我以不同的方式来到这里。通常,我在项目中使用过。

NSNotificationCenter defaultCenter请检查下面的代码。

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;
}

这里,您不需要检查可见的视图控制器,也不需要分配。如果您的屏幕位于顶部,那么您的方法将被调用。

祝你好运! :-)