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

So *_* It 19 delegates memory-management objective-c objective-c-blocks automatic-ref-counting

我有一个类从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
Run Code Online (Sandbox Code Playgroud)

请注意,我正在使用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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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

这段代码似乎工作正常,但只有我有我的委托strong.我知道我的代表应该weak避免潜在的保留周期.查看工具,我发现随着持续呼叫,分配不会继续增长.但是,我认为"最佳实践"是有weak代表.

问题

Q1)拥有strong代表是否"没问题"

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

CRD*_*CRD 15

Q1 - 是的.正如您所指出的那样,自己的委托属性很弱是建议帮助避免保留周期.因此,拥有一个强大的代表本身并没有什么不妥,但如果你班级的客户希望它很弱,你可能会给他们带来惊喜.更好的方法是保持委托较弱,并使服务器端(具有委托属性的类)在内部为其需要的时间段保留强引用.正如@Scott所指出的那样,苹果公司正在为此做准备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];
Run Code Online (Sandbox Code Playgroud)

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

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

HTH.

  • @SoOverIt - 您可以*选择*以在稍后阶段删除关联,但如果不这样做,它将保留,直到与其关联的对象被回收.如果策略是"OBJC_ASSOCIATION_RETAIN",表示保留关联,则在回收对象时释放.有关更多详细信息以及支持的其他信息,请参阅文档. (2认同)

new*_*cct 13

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

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

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

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

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

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


Sco*_*ets 10

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

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

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

你可以做类似的事情.在你dataRetrieveddataFailed方法,将您的委托nil.readonly如果要重用对象,则可能不需要创建委托,但是必须再次分配委托.