Pull to refresh

Comments 12

Вы так написали, как будто возможность использования замыканий вместо делегирования появилась только в Swift.
В Objective-C замыкания (блоки) появились 5 лет назад, и с тех пор в iOS SDK делегирование постепенно заменяется на блоки. Не говоря уже про сторонние библиотеки, которые заменили делегирование на замыкания почти для всех основных классов стандартного SDK.
Objective-C — прекрасный язык, никто не спорит.
Но в пользовательском коде можно редко увидеть использование блоков вместо делегирования.
Я не знаю, что останавливает программистов.
А в Swift это выглядит просто и красиво.
Сравните простейший пример использования замыканий.

Objective C

NSMutableArray *funcs = [[NSMutableArray alloc] init];
for (int i = 0; i < 10; i++) {
  [funcs addObject:[^ { return i * i; } copy]];
}
 
int (^foo)(void) = funcs[3];
NSLog(@"%d", foo()); // logs "9"


Swift

let funcs = [] + map(0..<10) {i in { i * i }}
println(funcs[3]()) // prints 9

Мне кажется в Swift использование замыканий вместо делегирования опустит планку для разработчика.
Я с Вами не соглашусь. То, что в коде, с которым вы сталкивались не используются блоки в качестве делегатов, не значит, что в целом разработчики игнорируют такой способ. Замена делегатов на блоки в стандартном SDK и количество плюсиков у библиотеки, на которую я сослался в предыдущем комментарии, подтверждают мои слова.
То, что в Swift замыкания описываются более лаконично, чем в Objective-C, нельзя считать причиной того, что Objective-C разработчики предпочитают использовать делегирование вместо блоков. Потому что Swift в целом изначально проектировался как лаконичный язык, большое количество возможностей сократить код замыкания в зависимости от условий (наличие аргументов, наличие возвращаемого типа и т.д) хорошо демонстрирует это.
Вы же не скажете, что Objective-C разработчики стараются избегать объявлять свойства в классе, а Swift разработчики обожают это делать, потому что в Swift это выглядит просто и красиво:
Objective C:
@property (nonatomic, strong) NSMutableString *string;

- (instancetype)init {
    self = [super init];
    if (self) {
        string = [NSMutableString string];
    }
}

Swift:
var string = ""

Пример грубый, но я думаю ясно отражает суть мысли.

Повторюсь, мой изначальный комментарий был не о том, что Swift плохой, а Objective-C хороший или наоборот, а о том, что в Вашей статье неточность:
… а вот передача информации «назад» из текущего MVC в предшествующий осуществляется с помощью делегирования как в Objective-C, так и в Swift.
Нужно выполнить 6 шагов, чтобы внедрить делегирование во взаимодействие View и Controller. Однако в Swift мы можем заменить этот процесс более простым...

В Objective-C этот процесс можно сделать таким же простым как и в Swift.
Да, я соглашусь с вашим замечанием относительно того, что в моей статье закралась неточность относительно использования замыканий (блоков) вместо делегирования в Objective-C.
Спасибо вам за очень обстоятельное разъяснение и ссылку на библиотеку.
Я внесла необходимые изменения в статью. Действительно, мой опыт использования замыканий в Objective-C для взаимодействия между View и Сontroller и между различными MVC ограничен.
В своей статье я вовсе не хочу сравнивать использование замыканий в Objective-C и в Swift, я хочу сказать: " Смотрите, как просто этим пользоваться в Swift. Не нужны никакие дополнительные библиотеки, никакие вспомогательные протоколы и делегаты. Достаточно вложить здравый смысл в простой и понятный синтаксис Swift."
Пользуясь тем, что есть возможность поговорить с умным собеседником, не могли бы вы для полноты картины привести простой, как на Swift, код на Objective-C для простого примера, указанного в статье, когда есть GraphView и GraphViewController, а GraphView запрашивает данные о зависимости y = f(x).
Для Swift это выглядит так:
GraphView
typealias yFunctionX = ( x: Double) -> Double?
    var yForX: yFunctionX?
. . . . . . . . . . 
 func drawCurveInRect(bounds: CGRect, origin: CGPoint, pointsPerUnit: CGFloat){
. . . . . . . . .
        if let y = (self.yForX)?(x: Double ((point.x - origin.x) / scale)) {
. . . . .
         } 
}

GraphViewController
. . . . . . . . . .
  @IBOutlet weak var graphView: GraphView! { didSet {
graphView.yForX = { [unowned self](x:Double)  in
                self.brain.setVariable("M", value: Double (x))
                return self.brain.evaluate()
            }
}

Для Objective-C так ...?
На Objective-C это будет выглядеть вот так (дословный перевод):
// GraphView
typedef double (^yFunctionX)(double x);
.......
@property (nonatomic, copy) yFunctionX yForX;
.......
- (void)drawCurveInRect:(CGRect)bounds origin:(CGPoint)origin pointsPerUnit:(CGFloat)pointsPerUnit {
    if (self.yForX != nil) {
        double y = self.yForX((double)((point.x - origin.x) / scale);
        ......
    }
}

// GraphViewController
@property (nonatomic, weak) IBOutlet GraphView *graphView;
.......
- (void)setGraphView:(GraphView *)graphView {
    _graphView = graphView
    __weak typeof (self) weakSelf = self;
    _graphView.yForX = ^(double x) {
        [weakSelf.brain setVariable:@"M" value:x];
        return [weakSelf.brain evaluate];
    };
}
Спасибо большое.
Действительно все понятно и достаточно кратко в пределах возможностей Objective-C.
Я думаю, этот вариант стоит попробовать тем, кто программирует на Objective-C и не только.
Единственный минус использования замыканий/блоков вместо протоколов заключается в том, что компилятор/анализатор не подскажет Вам, если Вы забудете проинициализировать свойство замыкания/блока (в примере было yForX). В случае использования протокола мы получим warning о том, что обязательные методы протокола не реализованы в классе-делегате.
Для моей реализации в Swift это не является недостатком, так как я намеренно сделала переменную-замыкание yForX Optional, то есть графика может и не быть

    var yForX: yFunctionX?

и использую я ее как Optional при построении графика

     if let y = (self.yForX)?(x: Double ((point.x - origin.x) / scale)) {
.  .  .  .  

Название функции заключается в круглые скобки и ставится знак? вопроса для корректного построения цепочки Optionals. В случае, если замыкание -переменная yForX не определена в GraphViewController, то аварийного завершения приложения не будет — просто не построится график.
В Objective-C нет Optional значений. Там в случае не определения замыкания, приложение закончится аварийно.
так как я намеренно сделала переменную-замыкание yForX Optional

В этом то и проблема, yForX не должен быть опциональным, потому что без этого замыкания GraphView не имеет значения, так как она не выполнит своего главного назначения — отобразить график. Указывая здесь optional Вы прячете потенциальный баг в приложении.
Если бы я делал эту фичу с помощью протокола, я бы сделал этот метод @required, в таком случае ошибка не будет запрятана.
Почему не имеет значения? На графике есть оси, но могут быть и другие графические элементы, может быть несколько графиков, а построение конкретного графика yForX отдается на откуп пользователю: хочет строит, хочет — нет. Эту ошибку не спрячешь — она сразу себя покажет на графике.
Но в некоторых случаях, я с вами согласна, наличие замыкания обязательно. Например, если вы удалили функцию в строке в Popover, то вам нужно обязательно синхронизировать это удаление с Моделью в Controller. Здесь замыкание не может быть Optional.
yForX отдается на откуп пользователю: хочет строит, хочет — нет.

Согласен, это имеет смысл, о таком варианте я не подумал.
Дополню, это тоже самое, если бы метод — (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section в UITableViewDataSource протоколе сделать опциональным. И в случае, если этот метод не реализован, то таблица будет отображать пустоту. Это некорректное состояние для таблицы.
Sign up to leave a comment.

Articles