为代表提供“强大”的参考资料是否真的好?

时间:2013-06-27 16:31:09

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

我有一个类从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的'wrapper'类。

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

我最近才进入了客观世界(仅生活在ARC时代,并且仍然与街区达成协议),不可否认,我对内存管理的理解是在较为薄弱的一面。

此代码似乎工作正常,但前提是我的委托为strong。我理解我的代表应该weak以避免潜在的保留周期。查看工具,我发现随着持续呼叫,分配不会继续增长。但是,我认为“最佳做法”是让weak名代表。

问题

Q1)拥有strong代表

是否“没问题”

Q2)如何实现基于块的包装器,将基础类的委托保留为weak委托(即阻止* delegateBlock在接收协议方法之前被解除分配)?

4 个答案:

答案 0 :(得分:15)

Q1 - 是的。当你指出自己让代理属性变弱时,建议可以帮助避免保留周期。所以本身没有任何错误,因为有一个强大的代表,但如果你班级的客户希望它变弱,你可能会给他们带来惊喜。更好的方法是保持委托较弱,并使服务器端(具有委托属性的类)在内部为其需要的时间段保留强引用。正如@Scott指出Apple文档为NSURLConnection执行此操作。当然,这种方法并不能解决您的问题 - 您希望服务器为您保留代理...

第二季度 - 从客户端看,问题是如果一个代表有弱引用的服务器需要它,如何使代表保持活动状态。这个问题的标准解决方案称为关联对象。简而言之,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。

答案 1 :(得分:12)

完全取决于对象的架构。

当人们使用弱委托时,这是因为委托通常是某种“父”对象,它保留了具有委托的东西(让我们称之为“委托人”)。为什么它必须是父对象?它不一定是;然而,在大多数使用案例中,它被证明是最方便的模式。由于委托是保留委托者的父对象,委托人不能保留委托,或者它将具有保留周期,因此它保留对委托的弱引用。

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

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

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

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

答案 2 :(得分:9)

你是正确的,因为代表通常被弱引用。但是,有些用例需要强引用,甚至是必要的。 Apple在NSURLConnection中使用此功能:

  

在下载过程中,连接会保留对委托的强引用。当连接完成加载,失败或被取消时,它会释放强引用。

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

你可以做类似的事情。在dataRetrieveddataFailed方法中,将您的代理设置为nil。如果您想重复使用对象,则可能不需要设置代理readonly,但是您必须再次分配代理。

答案 3 :(得分:0)

正如其他人所说,这与建筑有关。但是,我将通过几个示例向您介绍:

失败后重试

假设您已经建立了一个URLSession,并且正在等待通过viewController进行的网络调用,有时它是否失败并不重要,但有时它会失败。例如您的应用正在向其他用户发送消息,然后关闭该ViewController,并且网络请求以某种方式失败。您是否要重试?如果是这样,则该viewController必须保留在内存中,因此它可以再次重新提交请求。

写入磁盘

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

大型后台任务

NSURLSession的最初用例是增强后台网络任务执行,大文件下载以及类似性质的功能。您需要有一些内存来处理这些任务的完成,以指示执行已完成,并且操作系统可以使应用程序休眠。

将下载大文件的生命周期与某个视图相关联是一个坏主意……它需要与一些更稳定/持久的应用程序绑定在一起,例如会议本身...

通常,如果我要使用基于委托的系统而不是URLSession的基于块的较新API,则我有一个辅助对象,该对象封装了处理我可能需要的失败和成功案例所需的所有逻辑,我不会不必依靠繁重的VC来完成肮脏的工作


这是我与MattS

进行的一次对话,完全是为了写答案