Могут ли блоки использоваться в качестве замены для делегатов?
Я хочу написать собственный метод делегата для получения события в одном из моих контроллеров представления от другого контроллера представления. Должен ли я использовать блоки здесь вместо делегатов. Какой из них предпочтительнее?
@protocol MyClassDelegate
-(void)doSomethingInDelegate;
@end
@interface MyClass : NSObject
@property id<MyClassDelegate> delegate;
-(void)doSomething
@end
@implementation MyClass
-(void)doSomething
{
[self.delegate doSomethingInDelegate];
}
@end
@interface MyOtherClass<MyClassDelegate> : NSObject
...
@end
@implementation MyOtherClass
-(void)doSomethingInDelegate
{
NSLog(@"Doing something in delegate");
}
@end
1 ответ
В большинстве случаев, если у вас очень небольшое количество методов делегата (в идеале всего 1), тогда блоки могут быть хорошей заменой. Если у вас есть несколько методов делегата, блоки могут стать неудобными.
UITableView
имеет десятки методов делегата между UITableViewDelegate
а также UITableViewDataSource
, Настройка этого с блоками будет громоздкой и затруднит повторное использование кода. Если конкретный способ "быть делегатом" может быть многократно использован (как в UITableViewController
), тогда делегаты будут гораздо более мощным паттерном.
С другой стороны, если ваш делегат получит только один метод thisActionFinished:`, то делегат, вероятно, излишний, и лучше просто пропустить блок. Есть много случаев, когда это правда, и мы привыкли иметь необходимость создания большого количества протоколов делегатов с одним методом, и это было немного болезненно, блоки сделали этот общий шаблон намного проще.
Но это не универсальная замена делегирования, и у блоков много других целей, которые не имеют ничего общего с обратными вызовами. Поэтому важно изучить обе техники.
Глядя на ваш конкретный пример, есть несколько ошибок. Давайте сделаем это как в делегатской, так и в блочной формах.
делегат
// Since the protocol needs to know about the class, you need to warn the
// compiler that this class exists.
@class MyClass;
// Declare the delegate protocol. Delegate method names should follow this
// pattern with "did", "should", or "will" in their names. Delegate methods
// should always pass the delegating object as the first parameter. A given
// delegate may be delegating for several instances.
@protocol MyClassDelegate
-(void)myClass:(MyClass *)class didSomething:(id)something;
@end
// Declare the class that has a delegate. Notice that `delegate` should be `weak`
// here. In your example, it's `strong`, and that will almost always lead to a
// retain loop. With rare exceptions, delegates are not retained.
@interface MyClass : NSObject
@property (nonatomic, readwrite, weak) id<MyClassDelegate> delegate;
-(void)doSomething;
@end
// Do the thing
@implementation MyClass
-(void)doSomething {
[self.delegate myClass:self didSomething:@"SOMETHING"];
}
@end
// The delegate object almost always has a strong pointer to the thing it delegates
// for. That's why you want the `delegate` property to be weak.
// Note that your syntax was wrong. "MyOtherClass <MyClassDelegate>". That's
// the new generic syntax, not the protocol syntax. Protocols go at the end.
@interface MyOtherClass : NSObject <MyClassDelegate>
@property (nonatomic, readwrite, strong) MyClass *c;
@end
// And the obvious impl
@implementation MyOtherClass
- (instancetype)init {
self = [super init];
if (self) {
self.c = [MyClass new];
self.c.delegate = self;
}
return self;
}
-(void)myClass:(MyClass *)class didSomething:(id)something {
NSLog(@"Doing something in delegate");
}
@end
блок
Давайте сделаем то же самое, если бы это был API на основе блоков.
// If your callback takes no arguments and returns nothing, then you can
// use dispatch_block_t here. But often you need parameters or return
// something, and for that you should usually make a typealias. Welcome to the
// spiral world of block syntax.
typedef void(^MyClassCallback)(id something);
// Instead of a delegate, we have a callback. We might have several. We might
// have a block that returns the row height. But if you're doing a lot of
// that, just use a delegate. Note that blocks should always be `copy`.
@interface MyClass : NSObject
@property (nonatomic, readwrite, copy) MyClassCallback callback;
-(void)doSomething;
@end
// And here's how you use the block. It's just like a function.
@implementation MyClass
-(void)doSomething {
if (self.callback != nil) {
self.callback(@"SOMETHING");
}
}
@end
// And the delegate.
@interface MyOtherClass : NSObject
@property (nonatomic, readwrite, strong) MyClass *c;
@end
@implementation MyOtherClass
- (instancetype)init {
self = [super init];
if (self) {
self.c = [MyClass new];
// And here's the syntax for creating the block.
self.c.callback = ^(id something) {
NSLog(@"Doing something in delegate");
};
}
return self;
}
@end
Обратите внимание, что нам не нужен дополнительный метод в делегате только для хранения одной строки кода, и нам не нужно определять протокол. Это главная причина для перехода к блокам для легкого делегирования. Это держит связанный код близко друг к другу. Но когда код усложняется, "вместе" сходит с ума, и блоки уже не являются хорошим решением. Вернуться к делегатам, которые делают это очень хорошо.