查看将出现的困境

发布于 2024-12-29 19:48:36 字数 1209 浏览 2 评论 0原文

我使用 NSUserDefaults 来使对象在 UITabbarController 中使用的多个 UIViewController 之间保持同步。为此,我实现了以下代码。

- (void)viewWillAppear:(BOOL)animated
{
    NSLog(@"ViewControllerX Will Appear");
    [super viewWillAppear:animated];

    NSDictionary *dict = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"sharedDictionary"];
    [customObject setDictionary:dict];
}


- (void)viewWillDisappear:(BOOL)animated
{
    NSLog(@"ViewControllerX Will Disappear");
    NSDictionary *dict = [customObject dictionary];
    [[NSUserDefaults standardUserDefaults] setObject:dict forKey:@"sharedDictionary"];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

这里 customObject 是一个自定义类的实例,它具有 NSDictionary 类型的属性 dictionary。该对象可能会被可见的 UIViewController 改变。

我当前遇到的问题是,当用户切换选项卡时,例如从 ViewControllerX 切换到 ViewControllerY 时,这些方法不会按预期顺序调用。我希望在日志中看到这一点:

ViewControllerX Will Disappear
ViewControllerY Will Appear

但我看到的

ViewControllerY Will Appear
ViewControllerX Will Disappear

结果是旧字典被加载到 ViewControllerY 中,只有再次切换选项卡后才会出现新字典。有一个简单的方法可以解决这个问题吗?

I'm using NSUserDefaults to keep an object in sync across several UIViewControllers that are used in a UITabbarController. To do this, I'm implementing the following

- (void)viewWillAppear:(BOOL)animated
{
    NSLog(@"ViewControllerX Will Appear");
    [super viewWillAppear:animated];

    NSDictionary *dict = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"sharedDictionary"];
    [customObject setDictionary:dict];
}


- (void)viewWillDisappear:(BOOL)animated
{
    NSLog(@"ViewControllerX Will Disappear");
    NSDictionary *dict = [customObject dictionary];
    [[NSUserDefaults standardUserDefaults] setObject:dict forKey:@"sharedDictionary"];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

Here customObject is an instance of a custom class that has a property dictionary of type NSDictionary. This object may get changed by the visible UIViewController.

The problem I current have is that when the user switches tabs, say from ViewControllerX to ViewControllerY, these methods aren't getting called in the expected order. I expect to see this in the log:

ViewControllerX Will Disappear
ViewControllerY Will Appear

but instead I see

ViewControllerY Will Appear
ViewControllerX Will Disappear

The result is that the old dictionary is loaded in ViewControllerY, and only after switching tabs again does the new dictionary appear. Is there an easy way around this problem?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

捎一片雪花 2025-01-05 19:48:36

无法保证这些方法将以何种顺序被调用,因此您不能依赖它们的任何顺序。您得到的唯一保证是 -viewWillAppear:-viewWillDisappear: 将分别在视图出现或消失之前被调用。

处理此问题的另一种方法可能是将其更改为 will/did 类型场景。因此,您可以在 -viewWillDisappear: 中保存对象的当前状态,并在 -viewDidAppear: 中恢复状态(即加载字典)。这将保证即将消失的视图在出现的视图之前保存其字典。

另一种方法是更改​​自定义字典在视图控制器之间传递的方式,并在应用程序的 UITabBarViewController 上使用委托对象来处理将这些更改同步到用户默认值。您可以将其集成到您的应用程序中,但是最有意义,但我将在下面提供一个基本示例以及您需要向应用程序提供的更改(如您的问题中所述):

要使用该示例,您需要要进行这些更改(适应您的编码风格):

  • 将一个 NSDictionary 添加到名为 _sharedDictionary 的 ivar 到您的应用程序委托,
  • 在应用程序启动时从用户
  • 默认 值加载字典声明您的应用程序委托实现了 当您的应用加载时,将UITabBarControllerDelegate 协议
  • 分配给您的应用委托作为主 UITabBarController 的委托。
  • 更改视图控制器以响应可以调用 sharedDictionary 的新属性,
  • 您可以维护代码的其余部分,在 -viewWillAppear 中设置视图控制器的 < code>sharedDictionary 属性添加到您的自定义对象中,然后像之前一样继续

完成这些操作后,将以下方法实现添加到您的应用程序委托中:

-(BOOL)tabBarController:(UITabBarController*)tabBarController shouldSelectViewController:(UIViewController*)viewController
{
  // If you're using ARC, you can remove the retain/autorelease statements
  [_sharedDictionary autorelease];

  // In order to avoid a compiler warning here, you should have your view controllers
  // possibly inherit from a parent that defines the sharedDictionary property and cast
  // to that, or have your view controllers implement a protocol that defines the
  // property, and cast to that. As long as your view controllers actually implement
  // the sharedDictionary property, however, everything will work
  _sharedDictionary = [[[tabBarController selectedViewController] sharedDictionary] retain];

  // set the shared dictionary on the new view controller -- same casting rules apply
  // as stated above
  [viewController setSharedDictionary:_sharedDictionary];

  // save this to user defaults so that if the app stops, it maintains whatever state
  // you're keeping
  dispatch_async(dispatch_get_main_queue(), ^{
    [[NSUserDefaults standardUserDefaults] setObject:_sharedDictionary forKey:@"sharedDictionary"];
    [[NSUserDefaults standardUserDefaults] synchronize];
  });

  return YES;
}

There's no guarantee which order these methods are going to be called in, so you can't rely on any ordering with them. The only guarantee you get is that -viewWillAppear: and -viewWillDisappear: will be called before the view appears or disappears respectively.

Another way to deal with this might be changing this to a will/did type scenario. So, you save the current state of your object in -viewWillDisappear: and you restore the state (i.e., load your dictionary) in -viewDidAppear:. This will guarantee that the view that is going away saves its dictionary before the view that appears.

Another approach would be to change the way the custom dictionary is passed between your view controllers and use a delegate object on your application's UITabBarViewController to deal with syncing these changes to the user defaults. You can integrate this into your app however makes the most sense, but I'll provide a basic example below along with the changes you'd need to provide to your app (as described in your question):

To use the example, you need to make these changes (adapt to your coding style):

  • add an NSDictionary to ivar named _sharedDictionary to your application delegate
  • load the dictionary from the user defaults when the app launches
  • declare that your app delegate implements the UITabBarControllerDelegate protocol
  • when your app loads, assign your app delegate as the delegate of your main UITabBarController.
  • change your view controllers to respond to a new property which you can call sharedDictionary
  • you can maintain the rest of your code where in -viewWillAppear you set the value of the view controller's sharedDictionary property to your custom object and just continue as you did before

After you've done those things, add the following method implementation to your app delegate:

-(BOOL)tabBarController:(UITabBarController*)tabBarController shouldSelectViewController:(UIViewController*)viewController
{
  // If you're using ARC, you can remove the retain/autorelease statements
  [_sharedDictionary autorelease];

  // In order to avoid a compiler warning here, you should have your view controllers
  // possibly inherit from a parent that defines the sharedDictionary property and cast
  // to that, or have your view controllers implement a protocol that defines the
  // property, and cast to that. As long as your view controllers actually implement
  // the sharedDictionary property, however, everything will work
  _sharedDictionary = [[[tabBarController selectedViewController] sharedDictionary] retain];

  // set the shared dictionary on the new view controller -- same casting rules apply
  // as stated above
  [viewController setSharedDictionary:_sharedDictionary];

  // save this to user defaults so that if the app stops, it maintains whatever state
  // you're keeping
  dispatch_async(dispatch_get_main_queue(), ^{
    [[NSUserDefaults standardUserDefaults] setObject:_sharedDictionary forKey:@"sharedDictionary"];
    [[NSUserDefaults standardUserDefaults] synchronize];
  });

  return YES;
}
一场信仰旅途 2025-01-05 19:48:36

这很简单。让两个视图控制器共享同一个 CustomObject 实例,而不是让每个视图控制器拥有自己的 CustomObject 副本并尝试通过 NSUserDefaults 使它们保持同步。

此外,您还可以尝试使用观察者设计模式。您的视图控制器将扮演观察者的角色,而单个 CustomObject 实例将扮演主题的角色。


customObject 状态保存到用户默认值/从用户默认值加载的详细信息应封装在 customObject 内,并对视图控制器隐藏。您的视图控制器不需要知道如何 customObjectNSDictionnary 保存到用户默认值/从用户默认值加载。您的 customObject 类应如下所示:

@interface CustomClass : NSObject

@property (nonatomic, strong) NSString* name;
@property (nonatomic, assign) float value;

- (void)save;
- (void)load;
// Other methods

@end


@implementation CustomClass

@synthesize name = name_;
@synthesize value = value_;

- (void)save
{
    NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
                          name_, @"Name",
                          [NSNumber numberWithFloat:value_], @"Value",
                          nil];
    [[NSUserDefaults standardUserDefaults] setObject:dict
                                              forKey:@"CustomObject"];
}

- (void)load
{
    NSDictionary* dict = [[NSUserDefaults standardUserDefaults]
                          objectForKey:@"CustomObject"];
    name_ = [dict objectForKey:@"Name"];
    NSNumber* valueNum = [dict objectForKey:@"Value"];
    value_ = [valueNum floatValue];
}

@end

您的 customObject 应在应用程序启动时加载一次。您可以在 AppDelegate 的 didFinishLaunchingWithOptions 方法中执行此操作:

- (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [sharedCustomObject load];
}

在视图控制器的 viewWillAppear 中,您不需要重新加载 sharedCustomObject本身。 它将已经保持当前状态

在视图控制器的 viewWillDisappear 中,您只需确保 sharedCustomObject 的状态在用户默认值中为备份

- (void)viewWillDisappear:(BOOL)animated
{
    [sharedCustomObject save];
}

保存后,sharedCustomObject 仍然是最新的,不需要重新加载

我希望事情现在更清楚了。 :-)

It's simple. Instead of each view controller having their own copy of CustomObject and trying to keep them in sync via NSUserDefaults, have both view controllers share the same instance of CustomObject.

In addition, you might also try using the Observer design pattern. Your view controllers would play the role of Observer, and a single CustomObject instance would play the role of Subject.


The details on saving/loading customObject's state to/from user defaults should be encapsulated inside customObject and hidden from your view controllers. Your view controllers should not need to know how customObject save/loads a NSDictionnary to/from user defaults. Your customObject's class should look something like this:

@interface CustomClass : NSObject

@property (nonatomic, strong) NSString* name;
@property (nonatomic, assign) float value;

- (void)save;
- (void)load;
// Other methods

@end


@implementation CustomClass

@synthesize name = name_;
@synthesize value = value_;

- (void)save
{
    NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
                          name_, @"Name",
                          [NSNumber numberWithFloat:value_], @"Value",
                          nil];
    [[NSUserDefaults standardUserDefaults] setObject:dict
                                              forKey:@"CustomObject"];
}

- (void)load
{
    NSDictionary* dict = [[NSUserDefaults standardUserDefaults]
                          objectForKey:@"CustomObject"];
    name_ = [dict objectForKey:@"Name"];
    NSNumber* valueNum = [dict objectForKey:@"Value"];
    value_ = [valueNum floatValue];
}

@end

Your customObject should be loaded once when your application starts. One place you can do this is in your AppDelegate's didFinishLaunchingWithOptions method:

- (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [sharedCustomObject load];
}

In your view controllers' viewWillAppear, you don't need to make sharedCustomObject reload itself. It will already hold the current state.

In your view controllers' viewWillDisappear, you only need to make sure that sharedCustomObject's state is backed-up in the user defaults.

- (void)viewWillDisappear:(BOOL)animated
{
    [sharedCustomObject save];
}

After saving, sharedCustomObject is still up-to-date and does not need to be reloaded.

I hope things are clearer now. :-)

无敌元气妹 2025-01-05 19:48:36

我可能不会以这种方式使用 NSUserDefaults 。您应该能够将行为封装在给定的视图控制器中,而不是依赖于特定的执行路径来操作共享状态。要么使用 Jason 的 viewDidAppear 建议,要么将 ViewControllerX 的字典内部副本直接传递到 ViewControllerY 以避免解决该问题。

I wouldn't use NSUserDefaults in that way, probably. You should be able to encapsulate behaviour in a given view controller and not rely on a particular execution path to manipulate shared state. Either use Jason's suggestion of viewDidAppear or pass ViewControllerX's internal copy of the dictionary straight into ViewControllerY to avoid having to work around it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文