19

我有一个从 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 在接收协议方法之前被释放)?

4

4 回答 4

15

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。

于 2013-06-27T18:56:46.453 回答
13

这完全取决于您的对象的体系结构。

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

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

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

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

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

于 2013-06-29T22:07:13.717 回答
10

你是正确的,代表通常被弱引用。但是,在某些用例中,强引用是首选,甚至是必要的。Apple 在NSURLConnection中使用它:

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

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

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

于 2013-06-27T16:42:38.130 回答
0

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

失败重试

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

写入磁盘

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

大型后台任务

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

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

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


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

于 2019-10-09T23:36:46.223 回答