Зачем использовать делегат и протокол вместо простого экземпляра в Swift?

Я пытался передавать переменные между представлениями в Swift и сталкивался с довольно абстрактным понятием протоколов и делегатов.

Затем я попытался сохранить ссылку на первый вид во втором представлении и сразу вызвать функции. Кажется, что это работает:

ЭКРАН 1

class Screen1: UIViewController {

    var myName = "Screen1"

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    //
    // checking if the segue to screen 2 is called and then passing a reference
    //
    override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
        if segue.identifier == "screen2Segue"{
            let vc = segue.destinationViewController as Screen2
            vc.storedReference = self
        }
    }

    func getName() -> String {
        return myName
    }
}

ЭКРАН 2

class Screen2: UIViewController {

    var storedReference:Screen1!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    func testReference() {
        // calling a function on the stored reference to screen 1
        var str = storedReference.getName()
        println("Leaving screen 2, going to " + str)
    }
}

Мой вопрос: что случилось с этим кодом? Зачем использовать делегаты и протоколы, если вы можете просто передать ссылку напрямую?

Возможно, связано: когда представление не инициализируется и заменяется совершенно новым экземпляром представления? Я вызываю "getName()" на старый экземпляр?

Ответ 1

Протоколы полезны для ветки реализации от интерфейса, что помогает увеличить повторное использование кода, понятность и тестируемость.

Например, возможно, вы хотите сохранить элементы в списке какого-либо типа. Некоторые возможные реализации списка включают в себя реализации на основе массивов и реализации node (связанные списки). Если бы вы объявили протокол с именем List и имели классы ArrayList и LinkedList, которые реализовали этот протокол, все, что требовало использования списка (переменная, переданная как параметр методу, свойство и т.д.), Могла используйте List как тип переменной и сможете функционировать, не заботясь о том, был ли список ArrayList или LinkedList. Вы могли бы изменить, какой тип был использован, или как они были реализованы, и было бы неважно, что бы они их использовали, потому что был показан только открытый интерфейс, объявленный в протоколе.

Протоколы также могут быть полезны для эмуляции чего-то вроде множественного наследования, поскольку класс может наследовать от суперкласса, а также реализовать один или несколько интерфейсов. (например, летучая мышь является млекопитающим и крылатой, поэтому ее можно представить как класс Bat, наследуемый от класса Mammal, который реализует протокол Winged).

В шаблоне делегата используются протоколы для делегирования некоторых обязанностей другому объекту, что особенно полезно для разделения кода и повторного использования. Например, протокол UITableViewDelegate в iOS позволяет UITableView реагировать на такие вещи, как выбор ячейки, делегируя другой объект для обработки события. Вероятно, это использовалось миллионами объектов в тысячах приложений, без разработчиков Apple, которые внедрили UITableView и UITableViewDelegate, когда-либо знавшие об объектах, реализующих протокол.

Прямо передавая ссылку между вашими контроллерами просмотров, вы вынуждаете второй полностью зависеть от первого. Если вы когда-либо хотели изменить поток вашего приложения, чтобы второй контроллер просмотра мог получить доступ из другого места, вам придется переписать этот контроллер представления для использования нового источника. Если вы используете протокол вместо этого, никаких изменений во втором контроллере просмотра не потребуется.

Ответ 2

Это основной принцип проектирования, чтобы не выставлять больше дизайна, чем вам нужно. Передавая ссылку вокруг вас, вы подвергаете весь объект. Это означает, что другие могут вызывать любую из своих функций и получать доступ к любым своим свойствам. И измените их. Это плохо. Помимо того, что другие пользователи могут использовать объект так, как он мог бы не намереваться, вы также столкнетесь с проблемами, если попытаетесь изменить объект в будущем и узнаете, что он разбивает кого-то другого, который использовал то, что вы не намеревались. Итак, всегда хорошая идея не раскрывать ничего, что вам не нужно. Это цель делегатов и протоколов. Это дает объекту полный контроль над тем, что выставлено. Гораздо безопаснее. Улучшенный дизайн.

Ответ 3

Я думаю, вы не полностью поняли, что такое протоколы.

Я всегда говорю, что протоколы похожи на контракты.
Объект делегата, который реализует определенные протоколы promises, что он может делать то, что не может сделать делегат.

В реальном мире у меня проблема с моими трубами дома.
Я (делегат) позвонил водопроводчику (делегату), чтобы его исправить. Водопроводчик promises (по контракту), чтобы иметь возможность выполнить его. Обещание - это протокол. Мне все равно, как он это делает, пока он это делает.

Но эти контракты не только полезны для делегирования.
Я просто пишу приложение для заказа пищи. Поскольку у него есть меню, ему нужен элемент для отображения в нем.
Я мог бы пойти с базовым наследованием и написать класс MenuItem, который все наборы должны наследовать. Или я пишу протокол, чтобы выразить: "Независимо от того, кем вы являетесь, до тех пор, пока вы выполняете этот контракт, у нас есть сделка". это позволяет мне создавать много разных классов или аннотировать существующие классы в категориях, хотя у меня нет инструмента множественного наследования.

На самом деле я делаю оба: я пишу протокол MenuItem и класс MenuItem, который соответствует протоколу. Теперь я могу использовать простое наследование или использовать классы, которые не наследуются от класса MenuItem.

Код в Objective-C (извините: я все еще перехожу к Swift)

@protocol MenuItem <NSObject>

-(NSString *)name;
-(double) price;
-(UIColor *)itemColor;

@end


@interface MenuItem : NSObject <MenuItem>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) double price;
@property (nonatomic, strong) UIColor *itemColor;

@end

#import "MenuItem.h"

@implementation MenuItem

-(id)initWithCoder:(NSCoder *)decoder
{
    self = [super init];
    if (self) {
        self.name = [decoder decodeObjectForKey:@"name"];
        self.price = [decoder decodeDoubleForKey:@"price"];
        self.itemColor = [decoder decodeObjectForKey:@"itemColor"];
    }
    return self;
}

-(void)encodeWithCoder:(NSCoder *)encoder
{
    [encoder encodeDouble:self.price forKey:@"price"];
    [encoder encodeObject:self.name forKey:@"name"];
    [encoder encodeObject:self.itemColor forKey:@"itemColor"];
}


@end

Apple использует ту же архитектуру для NSObject: есть протокол и класс NSObject. Это позволяет классам, которые не являются целыми, наследующими от класса NSObject, чтобы действовать как NSObject. Один известный пример: NSProxy.


в вашем случае Screen1 promises, чтобы понимать сообщения, отправляемые контроллером подробного представления Screen2. Они позволяют развязать: любой объект, который понимает протокол Screen1, может использоваться. Также он помогает поддерживать разумное дерево объектов, поскольку нам не нужно иметь циклический импорт. Но в целом вы должны иметь в виду, что делегат (Screen2) должен хранить слабую ссылку на него делегат, иначе у нас есть круг сохранения.


Конечно, важным примером является UITableView:
Объект просмотра таблицы знает все о рендеринге его ячеек, обработке прокрутки и т.д. Но инженер, который написал его, не мог теперь, как вы хотите, чтобы ваш вид таблицы выглядел. Именно поэтому он представил делегата, чтобы дать вам возможность создать правильную ячейку. Поскольку он не мог также знать, как выглядят ваши данные, он также представил источник данных, который работает точно так же, как делегат: вам будет предложено предоставить всю информацию о ваших данных, которые необходимы.

Ответ 4

В основном это вопрос мнения, поэтому этот вопрос, вероятно, должен быть закрыт, но я думаю, что сообщество разработчиков в целом соглашается на это, поэтому я все равно буду отвечать на него.

Важное понятие в Архитектура программного обеспечения (дизайн структуры кода) называется Разделение проблем. Основной принцип заключается в том, что вы должны разбить то, что ваш код должен делать на небольшие компоненты, которые имеют только одну определенную цель. Каждый из этих компонентов должен быть в состоянии стоять в основном самостоятельно, без особых проблем с другими компонентами, кроме тех, с которыми ему нужно напрямую взаимодействовать.

Это очень помогает при повторном использовании кода. Если вы создаете небольшой компонент, который не зависит от большинства других, если не всех других компонентов, вы можете легко подключить их к другим частям вашего кода или других приложений. Например, возьмите UITableView. Используя шаблон делегата, каждый разработчик может легко создать представление таблицы и заполнить его любыми данными, которые они хотят. Поскольку этот источник данных является отдельным объектом (с отдельной заботой о приходе данных), вы можете подключить этот же источник данных к нескольким представлениям таблиц. Подумайте о списке контактов на iOS. Вы захотите получить доступ к тем же данным по-разному. Вместо того, чтобы всегда переписывать табличное представление, которое загружает конкретные данные и отображает их определенным образом, вы можете повторно использовать источник данных с другим табличным представлением столько раз, сколько хотите.

Это также помогает с понятностью вашего кода. Разработчикам сложно держать слишком много в голове о состоянии вашего приложения. Если каждый компонент вашего кода разбит на небольшие, четко определенные обязанности, разработчик может понять каждый компонент отдельно. Они также могут смотреть на компонент и делать точные предположения о том, что он делает, не глядя на конкретную реализацию. Это не такая уж большая проблема с небольшими приложениями, но по мере роста кодовых оснований это становится очень важным.

Передавая ссылку на ваш первый контроллер просмотра, вы делаете свой второй контроллер просмотра полностью зависимым от первого. Вы не можете повторно использовать второй контроллер представления в другом экземпляре, и его работа становится менее понятной.

Есть много других преимуществ для разделения проблем, но я считаю, что это два важных и важных.