是否有时候可以为委托使用“强引用”?

14 浏览
0 Comments

是否有时候可以为委托使用“强引用”?

我有一个从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  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 
@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 -  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

我相对较近地接触了目标C世界(仅在ARC时代生活,并且仍在适应块),并且承认我对内存管理的理解有些不足。

这个代码似乎运行得很好,但仅当我将代理作为 strong 时才有效。我明白我的代理应该是弱引用,以避免潜在的保留循环。查看工具,我发现随着持续调用,分配并不持续增长。但是,我认为“最佳实践”是要有弱引用的代理。

问题

Q1)是否有时可以使用强引用代理

Q2)我如何实现基础类的块包装器,保持其委托为weak委托(即在* delegateBlock接收协议方法之前防止其被释放)?

admin 更改状态以发布 2023年5月25日
0
0 Comments

这完全取决于你对象的结构。

当人们使用弱代理时,通常是因为代理通常是某种“父”对象,它会保留具有代理的事物(让我们称之为“委托者”)。为什么它必须是一个父对象?它不必是;然而,在大多数用例中,它通常是最方便的模式。由于代理是保留委托者的父对象,委托者不能保留代理,否则它将具有保留周期,因此它保持对代理的弱引用。

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

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

但正如我之前提到的,这并不总是警报视图所希望的;有时您希望将其保留并以编程方式解除其显示。在这种情况下,您需要一个弱代理,否则它会导致保留周期。那么警报视图应该具有强代理还是弱代理?好吧,调用者应该决定!在某些情况下,调用者需要强代理;在其他情况下,调用者需要弱代理。但这怎么可能呢?警报视图代理由警报视图类声明,并且必须被声明为强引用或弱引用。

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

0
0 Comments

Q1 - 是的。正如你所指出的,将委托属性设置为弱引用是为了避免循环引用的建议。所以,将委托属性设置为强引用本身并没有什么问题,但是如果你的类的客户端期望它是弱引用的,那么你可能会给他们带来惊喜。更好的方法是将委托设置为弱引用,然后在需要强引用时(指服务器端即具有委托属性的类),在内部保持一个强引用。正如@Scott指出的那样,苹果文档建议使用这种方法来处理NSURLConnection的情况。当然,这种方法并不能解决你的问题——你想让服务器为你保留委托……

Q2 - 从客户端的角度来看,问题在于如何在服务器只有一个weak引用时,保持委托的持续存在。有一个标准的解决方法称为关联对象(associated objects)。简而言之,Objective-C运行时允许将一组键-值对象与另一个对象关联起来,同时还可以指定与该关联应保持多长时间的关联策略。要使用此机制,您只需要选择自己的唯一键,它是类型即一个地址。下面的代码概述显示了如何以NSOpenPanel为例使用它:

#import  // 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自己。 (或者您可以使用类别将其添加到现有类中。)

希望对你有所帮助。

0