【问题标题】:Is it ever Ok to have a 'strong' reference for a delegate?是否可以为代表提供“强”参考?
【发布时间】:2013-06-25 06:31:15
【问题描述】:

我有一个从 URL 检索 JSON 并通过协议/委托模式返回数据的类。

MRDelegateClass.h

#import <Foundation/Foundation.h>

@protocol MRDelegateClassProtocol
@optional
- (void)dataRetrieved:(NSDictionary *)json;
- (void)dataFailed:(NSError *)error;
@end

@interface MRDelegateClass : NSObject
@property (strong) id <MRDelegateClassProtocol> delegate;

- (void)getJSONData;
@end

请注意,我将 strong 用于我的委托属性。稍后再详细介绍...

我正在尝试编写一个以基于块的格式实现 getJSONData 的“包装器”类。

MRBlockWrapperClassForDelegate.h

#import <Foundation/Foundation.h>

typedef void(^SuccessBlock)(NSDictionary *json);
typedef void(^ErrorBlock)(NSError *error);

@interface MRBlockWrapperClassForDelegate : NSObject
+ (void)getJSONWithSuccess:(SuccessBlock)success orError:(ErrorBlock)error;
@end

MRBlockWrapperClassForDelegate.m

#import "MRBlockWrapperClassForDelegate.h"
#import "MRDelegateClass.h"

@interface DelegateBlock:NSObject <MRDelegateClassProtocol>
@property (nonatomic, copy) SuccessBlock successBlock;
@property (nonatomic, copy) ErrorBlock errorBlock;
@end

@implementation DelegateBlock
- (id)initWithSuccessBlock:(SuccessBlock)aSuccessBlock andErrorBlock:(ErrorBlock)aErrorBlock {
    self = [super init];
    if (self) {
        _successBlock = aSuccessBlock;
        _errorBlock = aErrorBlock;
    }
    return self;
}

#pragma mark - <MRDelegateClass> protocols
- (void)dataRetrieved:(NSDictionary *)json {
    self.successBlock(json);
}
- (void)dataFailed:(NSError *)error {
    self.errorBlock(error);
}
@end

// main class
@interface MRBlockWrapperClassForDelegate()
@end

@implementation MRBlockWrapperClassForDelegate

+ (void)getJSONWithSuccess:(SuccessBlock)success orError:(ErrorBlock)error {
    MRDelegateClass *delegateClassInstance = [MRDelegateClass new];
    DelegateBlock *delegateBlock = [[DelegateBlock alloc] initWithSuccessBlock:success andErrorBlock:error];
    delegateClassInstance.delegate = delegateBlock; // set the delegate as the new delegate block
    [delegateClassInstance getJSONData];
}

@end

我最近才接触到 Objective-c 世界(只生活在 ARC 时代,并且仍在接受块),诚然,我对内存管理的理解还比较肤浅。

这段代码似乎可以正常工作,但前提是我的代表是strong。我知道我的代表应该是weak 以避免潜在的保留周期。查看工具,我发现分配不会随着持续的调用而继续增长。但是,我认为“最佳实践”是拥有weak 代表。

问题

Q1) 有 strong 代表是否“可以”

Q2)我如何实现基于块的包装器,将底层类的委托保留为 weak 委托(即防止 *delegateBlock 在收到协议方法之前被释放)?

【问题讨论】:

  • 不要在方法前面加上get
  • @bbum 我知道属性的“设置”是不可以的,但我没有意识到方法的“获取”被认为是错误的形式。将不得不更多地研究命名约定。还在学习:)
  • get 保留用于通过引用返回内容的方法,并且很少使用。

标签: objective-c memory-management delegates automatic-ref-counting objective-c-blocks


【解决方案1】:

正如其他人所说,这是关于建筑的。但我会用几个例子来引导你:

失败重试

假设您已经创建了一个 URLSession,并且正在等待您通过 viewController 进行的网络调用,有时它是否失败并不重要,但有时它确实如此。例如您的应用程序正在向另一个用户发送消息,然后您关闭该视图控制器,并且该网络请求以某种方式失败。您希望它重试吗?如果是这样,则该 viewController 必须保留在内存中,以便它可以再次重新提交请求。

写入磁盘

另一种情况是,当请求成功时,您可能希望将某些内容写入磁盘,因此即使在视图控制器的 UI 更新后,您可能仍希望将本地数据库与服务器同步。

大型后台任务

NSURLSession 的原始用例是为后台网络任务执行、大文件下载和类似性质的事情提供动力。您需要内存中的某些内容来处理这些任务的最终确定,以指示执行已完成并且操作系统可以休眠应用程序。

将下载大文件的生命周期与某个视图相关联是个坏主意……它需要与更稳定/持久的某些视图相关联,例如会话本身……

通常,如果我要使用基于委托的系统而不是 URLSession 的新的基于块的 API,我有一个帮助对象,它封装了处理我可能需要的失败和成功案例所需的所有逻辑,我不不必依赖沉重的 VC 来做脏活


这个答案完全是由于我与MattS 的一次谈话而写的

【讨论】:

    【解决方案2】:

    这完全取决于您的对象的架构。

    当人们使用弱委托时,这是因为委托通常是某种“父”对象,它保留了拥有委托的东西(我们称之为“委托者”)。为什么它必须是父对象?不必如此;然而,在大多数用例中,它被证明是最方便的模式。由于delegate是一个保留delegator的父对象,所以delegator不能保留delegate,否则会有retain循环,所以持有对delegate的弱引用。

    但是,这不是唯一的使用情况。以 iOS 中的UIAlertViewUIActionSheet 为例。使用它们的通常方式是:在函数内部,创建一个带有消息的警报视图并向其添加按钮,设置其委托,执行任何其他自定义,在其上调用 -show,然后忘记它(它不是存储在任何地方)。这是一种“一劳永逸”的机制。一旦你show它,你不需要保留它或任何东西,它仍然会显示在屏幕上。在某些情况下,您可能想要存储警报视图,以便您可以通过编程方式将其关闭,但这种情况很少见;在绝大多数用例中,您只需显示并忘记它,只需处理任何委托调用。

    所以在这种情况下,正确的样式应该是强委托,因为 1) 父对象不保留警报视图,因此保留周期没有问题,以及 2) 委托需要 被保留,以便当在警报视图上按下某个按钮时,有人会在周围响应它。现在,很多时候,#2 不是问题,因为委托(父对象)是某种视图控制器或其他东西保留的东西。但情况并非总是如此。例如,我可以简单地拥有一个不属于任何视图控制器的方法,任何人都可以调用它来显示警报视图,如果用户按下是,则将某些内容上传到服务器。由于它不是任何控制器的一部分,因此它可能不会被任何东西保留。但它需要保持足够长的时间,直到警报视图完成。所以理想情况下,警报视图应该对它有很强的引用。

    但正如我之前提到的,这并不总是您想要的警报视图;有时您想保留它并以编程方式将其关闭。在这种情况下,您需要一个弱委托,否则会导致保留周期。那么警报视图应该有一个强委托还是弱委托?好吧,来电者应该决定!在某些情况下,调用者想要强;在其他人中,呼叫者想要弱。但这怎么可能?警报视图委托由警报视图类声明,并且必须声明为强或弱。

    幸运的是,有一个解决方案可以让调用者决定——基于块的回调。在基于块的 API 中,块本质上成为委托;但该块不是父对象。通常块在调用类中创建并捕获self,以便它可以对“父对象”执行操作。委托人(在这种情况下为警报视图)始终具有对块的强引用。但是,块可能对父对象有强引用或弱引用,这取决于块在调用代码中的编写方式(要捕获对父对象的弱引用,请不要在块中直接使用self,相反,在块外创建一个弱版本的self,并让块使用它)。这样,调用代码就完全控制了委托人对它的强引用还是弱引用。

    【讨论】:

      【解决方案3】:

      Q1 - 是的。正如您指出的那样,委托属性很弱是一个建议,以帮助避免保留周期。因此,本身拥有一个强大的委托并没有什么错,但是如果你班级的客户认为它很弱,你可能会让他们感到惊讶。更好的方法是保持委托弱,并为服务器端(具有委托属性的类)在内部为它需要的那些时期保持强引用。正如@Scott 指出的那样,Apple 文档为NSURLConnection 执行此操作。当然,这种方法并不能解决您的问题 - 您希望服务器为您保留委托...

      Q2 - 从客户端来看,问题是如何让代理保持活动状态,只要对它的弱引用的服务器需要它。这个问题有一个标准的解决方案,称为关联对象。简而言之,Objective-C 运行时本质上允许对象的键集合与另一个对象相关联,以及一个关联策略,该策略说明该关联应该持续多长时间。要使用此机制,您只需选择您自己的唯一密钥,其类型为 void * - 即 地址。以下代码大纲以NSOpenPanel 为例展示了如何使用它:

      #import <objc/runtime.h> // import associated object functions
      
      static char myUniqueKey; // the address of this variable is going to be unique
      
      NSOpenPanel *panel = [NSOpenPanel openPanel];
      
      MyOpenPanelDelegate *myDelegate = [MyOpenPanelDelegate new];
      // associate the delegate with the panel so it lives just as long as the panel itself
      objc_setAssociatedObject(panel, &myUniqueKey, myDelegate, OBJC_ASSOCIATION_RETAIN);
      // assign as the panel delegate
      [panel setDelegate:myDelegate];
      

      关联策略 OBJC_ASSOCIATION_RETAIN 将保留传入的对象 (myDelegate) 与它关联的对象 (panel) 的时间,然后释放它。

      采用这种解决方案可以避免让委托属性本身变得强大,并允许客户端控制是否保留委托。如果您也在实现服务器,您当然可以提供一种方法来执行此操作,也许是associatedDelegate:?,以避免客户端需要定义密钥并调用objc_setAssociatedObject 本身。 (或者您可以使用类别将其添加到现有类中。)

      HTH。

      【讨论】:

      • 谢谢。 关联对象 范式似乎正是我所寻找的。从你的推断和我现在看到的代码 sn-ps 看来,关联似乎是自动释放的?或者,一旦我不再需要它,我是否必须特别“释放”它?
      • @SoOverIt - 您可以选择在稍后阶段删除关联,但如果您不这样做,它会一直保留到与其关联的对象被回收。如果策略是OBJC_ASSOCIATION_RETAIN,这意味着在关联时保留,在对象被回收时释放。有关更多详细信息以及支持的其他内容,请参阅文档。
      【解决方案4】:

      您是正确的,代表通常被弱引用。但是,在某些用例中,强引用是首选,甚至是必要的。苹果在NSURLConnection 中使用了这个:

      在下载期间,连接保持对委托的强引用。当连接完成加载、失败或被取消时,它会释放该强引用。

      NSURLConnection 实例只能使用一次。完成后(无论是失败还是成功),它都会释放委托,并且由于委托是 readonly,因此不能(安全地)重用它。

      你可以做类似的事情。在您的dataRetrieveddataFailed 方法中,将您的委托设置为nil。如果你想重用你的对象,你可能不需要让你的委托readonly,但你必须重新分配你的委托。

      【讨论】:

      • 感谢 Scott 提供详细信息。使用 strong 委托时,将委托显式设置回 nil 是有意义的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-08-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-11
      相关资源
      最近更新 更多