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

时间:2022-09-07 11:12:38

I have a class that retrieves JSON from a URL and returns the data via the protocol/delegate pattern.

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

MRDelegateClass.h

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

Note that I'm using strong for my delegate property. More about that later...

请注意,我使用strong作为我的委托属性。稍后会详细介绍......

I am trying to write a 'wrapper' class that implements getJSONData in a block-based format.

我正在尝试编写一个'wrapper'类,它以基于块的格式实现getJSONData。

MRBlockWrapperClassForDelegate.h

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

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

I've come to the objective-c world relatively recently (only lived in ARC times, and still coming to terms with blocks) and admittedly my understanding of memory management is on the slimmer side of things.

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

This code seems to work fine, but only if I have my delegate as strong. I understand that my delegate should be weak to avoid potential retain-cycles. Looking in instruments, I find that allocations do not continue to grow with continued calls. However, I believe 'best practice' is to have weak delegates.

这段代码似乎工作正常,但前提是我的代表很强大。我知道我的代表应该很弱,以避免潜在的保留周期。查看工具,我发现随着持续通话,分配不会继续增长。但是,我认为“最佳实践”是让弱代表。

Questions

问题

Q1) is it ever 'ok' to have strong delegates

Q1)拥有强大的代表是否“没问题”

Q2) how could I implement the block-based wrapper leaving the delegate of the underlying class as weak delegate (ie. prevent the *delegateBlock from being deallocated before it receives the protocol methods)?

Q2)我怎样才能实现基于块的包装器,将底层类的委托作为弱委托(即阻止* delegateBlock在接收协议方法之前被解除分配)?

3 个解决方案

#1


14  

Q1 - Yes. As you point out yourself having delegate properties being weak is a recommendation to help avoid retain cycles. So there is nothing wrong per se with having a strong delegate, but if the clients of your class expect it to be weak you may cause them surprises. The better approach is to keep the delegate weak and for the server side (the class with the delegate property) to keep a strong reference internally for those periods it needs one. As @Scott points out Apple documents doing this for NSURLConnection. Of course that approach doesn't solve your issue - where you want the server to retain the delegate for you...

Q1 - 是的。正如您所指出的那样,自己的委托属性很弱是建议帮助避免保留周期。因此,拥有一个强大的代表本身并没有什么不妥,但如果你班级的客户希望它很弱,你可能会给他们带来惊喜。更好的方法是保持委托弱,并为服务器端(具有委托属性的类)在内部保留一个强引用,以便它需要一个。正如@Scott指出Apple文档为NSURLConnection做这件事。当然,这种方法无法解决您的问题 - 您希望服务器为您保留代理...

Q2 - Looked at from the client side the problem is how to keep a delegate alive as long as a server with a weak reference to it requires it. There is a standard solution to this problem called associated objects. In brief the Objective-C runtime essentially allows a key-collection of objects to be associated with another object, along with an association policy which states how long that association should last. To use this mechanism you just need to pick your own unique key, which is of type void * - i.e. an address. The following code outline shows how to use this using NSOpenPanel as an example:

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];

The association policy OBJC_ASSOCIATION_RETAIN will retain the passed in object (myDelegate) for as long as the object it is associated with (panel) and then release it.

关联策略OBJC_ASSOCIATION_RETAIN将保留传入的对象(myDelegate),只要它与对象(面板)关联,然后释放它。

Adopting this solution avoids making the delegate property itself strong and allows the client to control whether the delegate is retained. If you are also implementing the server you can of course provide a method to do this, maybe associatedDelegate:?, to avoid the client needing to define the key and call objc_setAssociatedObject itself. (Or you can add it to an existing class using a category.)

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

HTH.

HTH。

#2


9  

It entirely depends on the architecture of your objects.

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

When people use weak delegates, it's because the delegate is usually some kind of "parent" object, which retains the thing that has the delegate (let's call the "delegator"). Why does it have to be a parent object? It doesn't have to be; however, in most use cases it turns out to be the most convenient pattern. Since the delegate is a parent object that retains the delegator, the delegator can't retain the delegate or it will have a retain cycle, so it holds a weak reference to the delegate.

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

However, that is not the only use situation. Take, for example, UIAlertView and UIActionSheet in iOS. The usual way that they are used is: inside a function, create an alert view with a message and add buttons to it, set its delegate, perform any other customization, call -show on it, and then forget it (it is not stored anywhere). It's a kind of "fire and forget" kind of mechanism. Once you show it, you don't need to retain it or anything and it will still be displayed on screen. It's possible in some cases you might want to store the alert view around so you can programmatically dismiss it, but that is rare; in the vast majority of use cases, you simply show and forget it, and just handle any delegate calls.

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

So in this case, the proper style would be a strong delegate, because 1) the parent object does not retain the alert view, so there is no issue with a retain cycle, and 2) the delegate needs to be kept around, so that when some button is pressed on the alert view, someone will be around to respond to it. Now, a lot of times, #2 isn't a problem because the delegate (parent object) is some kind of view controller or something that is otherwise retained by something else. But this is not always the case. For example, I can simply have a method that is not part of any view controller, which anyone can call to show an alert view, and if the user presses Yes, uploads something to the server. Since it's not part of any controller, it likely is not retained by anything. But it needs to stay around long enough until the alert view is done. So ideally the alert view should have a strong reference to it.

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

But as I've mentioned before, this is not always what you want for an alert view; sometimes you want to keep it around and dismiss it programmatically. In this case, you want a weak delegate or it will cause a retain cycle. So should an alert view have a strong or weak delegate? Well, the caller should decide! In some situations the caller wants strong; in others the caller wants weak. But how is this possible? The alert view delegate is declared by the alert view class, and must be declared as either strong or weak.

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

Fortunately, there is a solution that does let the caller decide -- a blocks-based callback. In a blocks-based API, the block essentially becomes the delegate; but the block is not the parent object. Usually the block is created in the calling class and captures self so that it can perform actions on the "parent object". The delegator (alert view in this case) always has a strong reference to the block. However, the block may have a strong or weak reference to the parent object, depending on how the block is written in the calling code (to capture a weak reference to the parent object, don't use self directly in the block, and instead, create a weak version of self outside the block, and let the block use that instead). In this way, the calling code fully controls whether the delegator has a strong or weak reference to it.

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

#3


7  

You are correct in that delegates are usually weakly referenced. However, there are use cases where a strong reference is preferred, or even necessary. Apple uses this in NSURLConnection:

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

During a download the connection maintains a strong reference to the delegate. It releases that strong reference when the connection finishes loading, fails, or is canceled.

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

An NSURLConnection instance can only be used once. After it finishes (either with failure or success), it releases the delegate, and since the delegate is readonly, it can't be (safely) reused.

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

You can do something similar. In your dataRetrieved and dataFailed methods, set your delegate to nil. You probably don't need to make your delegate readonly if you want to reuse your object, but you will have to assign your delegate again.

你可以做类似的事情。在dataRetrieved和dataFailed方法中,将委托设置为nil。如果要重用对象,则可能不需要使您的委托只读,但您必须再次分配您的委托。

#1


14  

Q1 - Yes. As you point out yourself having delegate properties being weak is a recommendation to help avoid retain cycles. So there is nothing wrong per se with having a strong delegate, but if the clients of your class expect it to be weak you may cause them surprises. The better approach is to keep the delegate weak and for the server side (the class with the delegate property) to keep a strong reference internally for those periods it needs one. As @Scott points out Apple documents doing this for NSURLConnection. Of course that approach doesn't solve your issue - where you want the server to retain the delegate for you...

Q1 - 是的。正如您所指出的那样,自己的委托属性很弱是建议帮助避免保留周期。因此,拥有一个强大的代表本身并没有什么不妥,但如果你班级的客户希望它很弱,你可能会给他们带来惊喜。更好的方法是保持委托弱,并为服务器端(具有委托属性的类)在内部保留一个强引用,以便它需要一个。正如@Scott指出Apple文档为NSURLConnection做这件事。当然,这种方法无法解决您的问题 - 您希望服务器为您保留代理...

Q2 - Looked at from the client side the problem is how to keep a delegate alive as long as a server with a weak reference to it requires it. There is a standard solution to this problem called associated objects. In brief the Objective-C runtime essentially allows a key-collection of objects to be associated with another object, along with an association policy which states how long that association should last. To use this mechanism you just need to pick your own unique key, which is of type void * - i.e. an address. The following code outline shows how to use this using NSOpenPanel as an example:

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];

The association policy OBJC_ASSOCIATION_RETAIN will retain the passed in object (myDelegate) for as long as the object it is associated with (panel) and then release it.

关联策略OBJC_ASSOCIATION_RETAIN将保留传入的对象(myDelegate),只要它与对象(面板)关联,然后释放它。

Adopting this solution avoids making the delegate property itself strong and allows the client to control whether the delegate is retained. If you are also implementing the server you can of course provide a method to do this, maybe associatedDelegate:?, to avoid the client needing to define the key and call objc_setAssociatedObject itself. (Or you can add it to an existing class using a category.)

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

HTH.

HTH。

#2


9  

It entirely depends on the architecture of your objects.

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

When people use weak delegates, it's because the delegate is usually some kind of "parent" object, which retains the thing that has the delegate (let's call the "delegator"). Why does it have to be a parent object? It doesn't have to be; however, in most use cases it turns out to be the most convenient pattern. Since the delegate is a parent object that retains the delegator, the delegator can't retain the delegate or it will have a retain cycle, so it holds a weak reference to the delegate.

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

However, that is not the only use situation. Take, for example, UIAlertView and UIActionSheet in iOS. The usual way that they are used is: inside a function, create an alert view with a message and add buttons to it, set its delegate, perform any other customization, call -show on it, and then forget it (it is not stored anywhere). It's a kind of "fire and forget" kind of mechanism. Once you show it, you don't need to retain it or anything and it will still be displayed on screen. It's possible in some cases you might want to store the alert view around so you can programmatically dismiss it, but that is rare; in the vast majority of use cases, you simply show and forget it, and just handle any delegate calls.

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

So in this case, the proper style would be a strong delegate, because 1) the parent object does not retain the alert view, so there is no issue with a retain cycle, and 2) the delegate needs to be kept around, so that when some button is pressed on the alert view, someone will be around to respond to it. Now, a lot of times, #2 isn't a problem because the delegate (parent object) is some kind of view controller or something that is otherwise retained by something else. But this is not always the case. For example, I can simply have a method that is not part of any view controller, which anyone can call to show an alert view, and if the user presses Yes, uploads something to the server. Since it's not part of any controller, it likely is not retained by anything. But it needs to stay around long enough until the alert view is done. So ideally the alert view should have a strong reference to it.

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

But as I've mentioned before, this is not always what you want for an alert view; sometimes you want to keep it around and dismiss it programmatically. In this case, you want a weak delegate or it will cause a retain cycle. So should an alert view have a strong or weak delegate? Well, the caller should decide! In some situations the caller wants strong; in others the caller wants weak. But how is this possible? The alert view delegate is declared by the alert view class, and must be declared as either strong or weak.

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

Fortunately, there is a solution that does let the caller decide -- a blocks-based callback. In a blocks-based API, the block essentially becomes the delegate; but the block is not the parent object. Usually the block is created in the calling class and captures self so that it can perform actions on the "parent object". The delegator (alert view in this case) always has a strong reference to the block. However, the block may have a strong or weak reference to the parent object, depending on how the block is written in the calling code (to capture a weak reference to the parent object, don't use self directly in the block, and instead, create a weak version of self outside the block, and let the block use that instead). In this way, the calling code fully controls whether the delegator has a strong or weak reference to it.

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

#3


7  

You are correct in that delegates are usually weakly referenced. However, there are use cases where a strong reference is preferred, or even necessary. Apple uses this in NSURLConnection:

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

During a download the connection maintains a strong reference to the delegate. It releases that strong reference when the connection finishes loading, fails, or is canceled.

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

An NSURLConnection instance can only be used once. After it finishes (either with failure or success), it releases the delegate, and since the delegate is readonly, it can't be (safely) reused.

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

You can do something similar. In your dataRetrieved and dataFailed methods, set your delegate to nil. You probably don't need to make your delegate readonly if you want to reuse your object, but you will have to assign your delegate again.

你可以做类似的事情。在dataRetrieved和dataFailed方法中,将委托设置为nil。如果要重用对象,则可能不需要使您的委托只读,但您必须再次分配您的委托。