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

私は比較的最近客観的 c の世界に来ました (ARC の時代にのみ住んでいて、まだブロックと折り合いをつけています)。

このコードは正常に動作するように見えますが、デリゲートがstrong. weak私は、リテンション サイクルの可能性を回避するために代理人が必要であることを理解しています。金融商品を調べてみると、コールの継続によって割り当てが増え続けていないことがわかりました。ただし、「ベストプラクティス」はweakデリゲートを持つことだと思います。

質問

strongQ1)デリゲートを持つことは「OK」ですか?

Q2) 基になるクラスのデリゲートをデリゲートとして残すブロックベースのラッパーを実装するにはどうすればよいですかweak(つまり、プロトコル メソッドを受け取る前に *delegateBlock の割り当てが解除されるのを防ぎます)。

4

4 に答える 4

15

Q1 - はい。ご指摘のとおり、デリゲート プロパティが弱いことは、リテイン サイクルを回避するための推奨事項です。したがって、強力なデリゲートを持つこと自体は何も悪いことではありませんが、クラスのクライアントがデリゲートが弱いことを期待している場合、彼らを驚かせる可能性があります。より良いアプローチは、デリゲートを弱く保ち、サーバー側 (デリゲート プロパティを持つクラス) が必要な期間に強い参照を内部的に保持することです。@Scott が指摘しているように、これを行っている Apple ドキュメントはNSURLConnection. もちろん、そのアプローチは問題を解決しません-サーバーにデリゲートを保持させたい場合...

Q2 - クライアント側から見た問題は、デリゲートへの弱い参照を持つサーバーがデリゲートを必要とする限り、デリゲートを存続させる方法です。この問題には、関連オブジェクトと呼ばれる標準的な解決策があります。簡単に言うと、Objective-C ランタイムは基本的に、オブジェクトのキーコレクションを別のオブジェクトに関連付けることを許可し、その関連付けが持続する期間を示す関連付けポリシーを使用します。このメカニズムを使用するには、独自の一意のキーを選択する必要があります。これはタイプ、void *つまりaddressです。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

オブジェクトのアーキテクチャに完全に依存します。

人々が弱いデリゲートを使用するのは、デリゲートが通常、デリゲートを持つものを保持するある種の「親」オブジェクトであるためです (「デリゲート」と呼びましょう)。なぜ親オブジェクトでなければならないのですか? そうである必要はありません。ただし、ほとんどのユースケースでは、これが最も便利なパターンであることが判明しています。デリゲートはデリゲートを保持する親オブジェクトであるため、デリゲートはデリゲートを保持できないか、または保持サイクルを持つことになり、デリゲートへの弱い参照を保持します。

ただし、使用状況はそれだけではありません。たとえば、UIAlertViewiOSUIActionSheetで。それらが使用される通常の方法は次のとおりです。関数内で、メッセージを含むアラート ビューを作成してボタンを追加し、そのデリゲートを設定し、他のカスタマイズを実行し、それを呼び出し-showて、それを忘れます (どこにも保存されません)。 . これは一種の「ファイア アンド フォーゲット」メカニズムです。一度取得するshowと、それを保持する必要はなく、画面に表示されたままになります。場合によっては、警告ビューを保存して、プログラムで無視できるようにすることもできますが、それはまれです。ほとんどのユースケースでは、それを表示して忘れて、デリゲート呼び出しを処理するだけです。

したがって、この場合、適切なスタイルは強力なデリゲートになります。1) 親オブジェクトはアラート ビューを保持しないため、保持サイクルに問題はなく、2) デリゲートこれにより、アラート ビューで何らかのボタンが押されたときに、誰かがそれに応答するようになります。多くの場合、デリゲート (親オブジェクト) はある種のビュー コントローラーであるか、別の方法で保持されているため、#2 は問題になりません。しかし、これは必ずしもそうではありません。たとえば、View Controller の一部ではないメソッドを作成して、アラート ビューを表示するために誰でも呼び出すことができ、ユーザーが [はい] を押すと、何かをサーバーにアップロードします。コントローラーの一部ではないため、何によっても保持されない可能性があります。ただし、アラートの表示が完了するまで、十分長く存在する必要があります。したがって、理想的には、アラート ビューがそれを強く参照する必要があります。

しかし、前に述べたように、これは常にアラート ビューに必要なものとは限りません。場合によっては、それを保持してプログラムで却下したい場合があります。この場合、弱いデリゲートが必要です。そうしないと、保持サイクルが発生します。では、アラート ビューには強いデリゲートまたは弱いデリゲートが必要ですか? まあ、発信者が決める必要があります!状況によっては、発信者が強いことを望んでいます。他の場合、呼び出し元は弱いことを望んでいます。しかし、これはどのように可能ですか?アラート ビュー デリゲートは、アラート ビュー クラスによって宣言され、強いまたは弱いとして宣言する必要があります。

幸いなことに、呼び出し元が決定できる解決策があります -ブロックベースの callbackです。ブロックベースの API では、基本的にブロックがデリゲートになります。ただし、ブロックは親オブジェクトではありません。通常、ブロックは呼び出し元のクラスで作成されself、「親オブジェクト」でアクションを実行できるようにキャプチャされます。委任者 (この場合はアラート ビュー) は、常にブロックへの強い参照を持っています。ただし、呼び出しコードでのブロックの記述方法に応じて、ブロックは親オブジェクトへの強い参照または弱い参照を持つ場合があります (親オブジェクトへの弱い参照をキャプチャするselfには、ブロックで直接使用しないでください。代わりに、の弱いバージョンを作成するselfブロックの外側に置き、代わりにブロックにそれを使用させます)。このように、呼び出し元のコードは、デリゲーターが強い参照を持つか弱い参照を持つかを完全に制御します。

于 2013-06-29T22:07:13.717 に答える