【问题标题】:Feeding UIView with data为 UIView 提供数据
【发布时间】:2014-12-23 16:39:54
【问题描述】:

假设您有一个UIView,它显示来自某个模型对象的数据。当模型在后台发生变化时,它会通过某种订阅机制通知其监听器;一个很常见的模式......

我在 iOS 上一直在做的是订阅 ViewControllers viewWillAppear 中的模型通知;刷新相应的视图以响应更改通知;并在viewWillDisappear 中停止我的订阅。这样,当给定的视图控制器离开屏幕时,通过跟踪更改来确保我不会浪费资源,所以我对这个解决方案很满意。

但是,我当前的项目需要一些视图,它们跟踪模型对象,并且它们在多个视图控制器中到处使用。如果我使用以前的方法,那么订阅/取消订阅管道将不得不在许多视图控制器中复制。我想知道,这种逻辑是否可以放在视图本身中?尽管UIView's 生命周期事件(willMoveToSuperview: 和 willMoveToWindow:) 在这方面有一些模糊的语义,但这必须是可能的,因为这就是 Apple 对 iAd 显示视图所做的事情——即 ADBannerView 不需要任何管道来启动除了将广告放在您的视图层次结构中之外,它还显示广告,并且它从远程源中提取数据,因此它不能通过对 iAd 服务器进行不必要的订阅来浪费资源。 有没有人做过这件事? IE。将昂贵的变更跟踪机制与UIView 生命周期事件可靠地耦合?

【问题讨论】:

    标签: ios iphone ipad uiview


    【解决方案1】:

    虽然使用viewDidAppear,但我经常使用此过程,因为我不能确定其他视图控制器不会在调用当前的viewWillDisappear 之前调用viewWillAppear,这在您分配时可能会带来不便某个“共享实例”的代表。

    无论如何,我总是使用视图控制器来处理这种重新加载,然后调用特定的视图进行刷新。在某些特定情况下,我在 removeFromSuperView 方法中取消订阅,但您可以理解这不是最好的方法,因为视图可以作为子视图再次添加到某些视图中,并且订阅不会自动完成。但话又说回来,在视图本身由于订阅而自我保留的情况下,我确实使用了这个,最常见的情况是使用计时器或显示链接(这可以再次通过使用 2 个类来避免,但这是另一回事)。

    如果在视图控制器级别上使用此订阅/取消订阅,例如检查视图是否确实可见,我建议您将其保留在那里并手动订阅/取消订阅视图控制器拥有的视图。如果没有其他原因,您的代码将更易于管理。

    另一方面,如果这需要在某些特定视图类型的级别上(创建一个库甚至只是简单地重用),那么我会尝试在一些 initdealloc 方法中处理这个问题。同样,如果资源紧张,我会将逻辑移至视图控制器。

    无论如何,如果您找到一个可靠的解决方案,将这个逻辑严格地放在视图中,我会很高兴听到它。

    编辑评论以添加自保留解决方案:

    当涉及到一个类被订阅保留的问题时,例如计时器或通知中心,您所做的是创建 2 个类。一个代表您的接口并具有获取特定数据所需的所有方法,并且如果需要包含调用者可以订阅的委托(具有弱链接),我们将其称为 A 类。现在该类包含另一个包含实际的类订阅外部资源,例如通知中心,并且是自保留的,B类。所以A类不是自保留的,因为它没有直接订阅通知中心,计时器......这意味着A类将被正确释放,而B类将持续存在并导致潜在的内存泄漏。然后,B 类确实需要一个显式调用来取消订阅,以便将其释放,这应该在 A 类 dealloc 方法中完成。

    我想简单的解释可能有点复杂,所以看看这段代码:

    #import "ClassA.h"
    
    @class ClassA;
    @class ClassB;
    
    @protocol ClassBDelegate <NSObject>
    - (void)classBPing:(ClassB *)sender;
    @end
    
    @interface ClassB : NSObject
    @property NSTimer *timer;
    @property (weak) id<ClassBDelegate> delegate;
    - (void)beginNotificationHandling;
    - (void)endNotificationHandling;
    @end
    
    @implementation ClassB
    - (void)beginNotificationHandling {
        if(self.timer == nil) {
            self.timer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:@selector(onTimer) userInfo:nil repeats:YES];
        }
    }
    - (void)endNotificationHandling {
        [self.timer invalidate];
        self.timer = nil;
    }
    - (void)onTimer {
        [self.delegate classBPing:self];
    }
    
    @end
    
    @interface ClassA()<ClassBDelegate>
    @property ClassB *classBInstance;
    @end
    @implementation ClassA
    
    - (instancetype)init {
        if((self = [super init])) {
            self.classBInstance = [[ClassB alloc] init];
            self.classBInstance.delegate = self;
            [self.classBInstance beginNotificationHandling];
        }
        return self;
    }
    
    - (void)dealloc {
        // once this class is deallocated the classB instance must be invalidated so it is deallocated as well
        [self.classBInstance endNotificationHandling];
    }
    
    - (void)classBPing:(ClassB *)sender {
        NSLog(@"Ping");
    }
    
    @end
    

    请注意,这只是源文件,不需要在头文件中包含classB,因为您根本不应该在classA 之外使用它。现在使用此过程,您可以添加任何方法、委托或来自 classA 的任何内容来处理事件。

    【讨论】:

    • Init 和 dealloc 在订阅保留订阅者时会出现问题(例如 NSNotificationCenter 这样做)
    • 是的,正如我所提到的,当您遇到刚才提到的自我保留情况时,问题就出现了。让我编辑答案以添加已经提到的“另一件事”。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-09
    相关资源
    最近更新 更多