Глава 9: Обработка ошибок и исключений ← Вернуться к списку глав Глава 11: Лучшие практики и паттерны проектирования

Глава 10: Работа с протоколами и делегатами

Добро пожаловать в десятую главу нашего увлекательного путешествия по Objective-C! В этой главе мы познакомимся с протоколами и делегатами — важными концепциями, которые помогают создавать гибкие и расширяемые приложения. Вы узнаете, как использовать протоколы для определения контрактов между объектами и как применять паттерн делегирования для передачи ответственности. Как всегда, мы объясним все простым и понятным языком, чтобы вы могли легко во всем разобраться.


10.1. Что такое протоколы?

Протоколы в Objective-C

Зачем нужны протоколы?


10.2. Создание и реализация протоколов

Объявление протокола

Синтаксис:

@protocol ProtocolName <NSObject>

@required
// Обязательные методы
- (void)requiredMethod;

@optional
// Необязательные методы
- (void)optionalMethod;

@end

Пример объявления протокола

@protocol Vehicle <NSObject>

@required
- (void)startEngine;
- (void)stopEngine;

@optional
- (void)playRadio;

@end

Реализация протокола в классе

Чтобы класс соответствовал протоколу, нужно указать это при объявлении класса и реализовать требуемые методы.

Синтаксис:

@interface ClassName : SuperclassName <ProtocolName>

@end

Пример:

@interface Car : NSObject <Vehicle>

@property (nonatomic, strong) NSString *model;

@end

Реализация методов протокола

@implementation Car

- (void)startEngine {
    NSLog(@"Двигатель запущен");
}

- (void)stopEngine {
    NSLog(@"Двигатель остановлен");
}

// Метод playRadio является необязательным
- (void)playRadio {
    NSLog(@"Радио играет");
}

@end

Использование объектов, соответствующих протоколу

Вы можете объявить переменную, которая может быть любого класса, соответствующего протоколу.

Пример:

id<Vehicle> myVehicle = [[Car alloc] init];
[myVehicle startEngine];

10.3. Что такое делегирование?

Паттерн делегирования

Зачем использовать делегирование?

Примеры использования делегирования


10.4. Использование делегирования в Objective-C

Шаг 1: Объявление протокола делегата

Пример протокола делегата:

@protocol DownloadManagerDelegate <NSObject>

@required
- (void)downloadDidFinishWithData:(NSData *)data;

@optional
- (void)downloadDidFailWithError:(NSError *)error;

@end

Шаг 2: Создание класса, использующего делегат

Интерфейс класса:

@interface DownloadManager : NSObject

@property (nonatomic, weak) id<DownloadManagerDelegate> delegate;

- (void)startDownloadWithURL:(NSURL *)url;

@end

Реализация класса:

@implementation DownloadManager

- (void)startDownloadWithURL:(NSURL *)url {
    // Симуляция загрузки данных
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // Здесь вы выполняете реальную загрузку данных
        
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        dispatch_async(dispatch_get_main_queue(), ^{
            if (data) {
                // Сообщаем делегату о завершении загрузки
                if ([self.delegate respondsToSelector:@selector(downloadDidFinishWithData:)]) {
                    [self.delegate downloadDidFinishWithData:data];
                }
            } else {
                NSError *error = [NSError errorWithDomain:@"DownloadError" code:1001 userInfo:nil];
                if ([self.delegate respondsToSelector:@selector(downloadDidFailWithError:)]) {
                    [self.delegate downloadDidFailWithError:error];
                }
            }
        });
    });
}

@end

Шаг 3: Реализация делегата

Класс, выступающий в роли делегата:

@interface ViewController : UIViewController <DownloadManagerDelegate>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    DownloadManager *downloadManager = [[DownloadManager alloc] init];
    downloadManager.delegate = self; // Устанавливаем себя как делегата
    
    NSURL *url = [NSURL URLWithString:@"https://example.com/data.json"];
    [downloadManager startDownloadWithURL:url];
}

// Реализация метода делегата
- (void)downloadDidFinishWithData:(NSData *)data {
    NSLog(@"Данные успешно загружены");
    // Обработка полученных данных
}

// Реализация необязательного метода делегата
- (void)downloadDidFailWithError:(NSError *)error {
    NSLog(@"Ошибка загрузки: %@", error.localizedDescription);
}

@end

10.5. Практические примеры

Пример 1: Использование делегата для обработки событий

Протокол делегата:

@protocol ButtonDelegate <NSObject>

- (void)buttonWasPressed;

@end

Класс кнопки:

@interface CustomButton : NSObject

@property (nonatomic, weak) id<ButtonDelegate> delegate;

- (void)pressButton;

@end

@implementation CustomButton

- (void)pressButton {
    NSLog(@"Кнопка нажата");
    if ([self.delegate respondsToSelector:@selector(buttonWasPressed)]) {
        [self.delegate buttonWasPressed];
    }
}

@end

Класс делегата:

@interface ViewController : UIViewController <ButtonDelegate>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    CustomButton *button = [[CustomButton alloc] init];
    button.delegate = self;
    
    [button pressButton];
}

- (void)buttonWasPressed {
    NSLog(@"Делегат получил сообщение о нажатии кнопки");
}

@end

Вывод:

Кнопка нажата Делегат получил сообщение о нажатии кнопки

Пример 2: Использование нескольких делегатов

Объявление класса, соответствующего нескольким протоколам:

@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

@end

10.6. Практические задания

Задание 1: Создание протокола и его реализация

  1. Создайте протокол Playable с методами:

    • - (void)play; — обязательный метод.
    • - (void)pause; — необязательный метод.
  2. Создайте класс MusicPlayer, который соответствует протоколу Playable и реализует обязательный метод play.

  3. В классе MusicPlayer реализуйте метод play, который выводит сообщение "Воспроизведение музыки".

  4. Вызовите метод play у экземпляра MusicPlayer.

Решение:

@protocol Playable <NSObject>

@required
- (void)play;

@optional
- (void)pause;

@end

@interface MusicPlayer : NSObject <Playable>

@end

@implementation MusicPlayer

- (void)play {
    NSLog(@"Воспроизведение музыки");
}

// Реализация метода pause не обязательна

@end

// Использование:
MusicPlayer *player = [[MusicPlayer alloc] init];
[player play]; // Вывод: "Воспроизведение музыки"

Задание 2: Реализация делегата

  1. Создайте протокол DownloadDelegate с методом - (void)downloadDidComplete;.

  2. Создайте класс Downloader с свойством delegate типа id<DownloadDelegate>.

  3. В классе Downloader реализуйте метод - (void)startDownload, который после выполнения вызывает метод делегата downloadDidComplete.

  4. Создайте класс ViewController, который соответствует протоколу DownloadDelegate и реализует метод downloadDidComplete, выводящий сообщение "Загрузка завершена".

  5. Используйте классы Downloader и ViewController, чтобы продемонстрировать работу делегата.

Решение:

@protocol DownloadDelegate <NSObject>

- (void)downloadDidComplete;

@end

@interface Downloader : NSObject

@property (nonatomic, weak) id<DownloadDelegate> delegate;

- (void)startDownload;

@end

@implementation Downloader

- (void)startDownload {
    // Симуляция загрузки
    NSLog(@"Начало загрузки...");
    // После завершения сообщаем делегату
    if ([self.delegate respondsToSelector:@selector(downloadDidComplete)]) {
        [self.delegate downloadDidComplete];
    }
}

@end

@interface ViewController : UIViewController <DownloadDelegate>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Downloader *downloader = [[Downloader alloc] init];
    downloader.delegate = self;
    
    [downloader startDownload];
}

- (void)downloadDidComplete {
    NSLog(@"Загрузка завершена");
}

@end

// Вывод:
Начало загрузки...
Загрузка завершена

Задание 3: Проверка соответствия протоколу

  1. Создайте протокол Serializable с методом - (NSString *)serialize;.

  2. Создайте класс Person, который соответствует протоколу Serializable и реализует метод serialize, возвращающий строковое представление объекта.

  3. Напишите функцию, которая принимает объект типа id и проверяет, соответствует ли он протоколу Serializable. Если соответствует, вызывайте метод serialize и выводите результат.

Решение:

@protocol Serializable <NSObject>

- (NSString *)serialize;

@end

@interface Person : NSObject <Serializable>

@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;

@end

@implementation Person

- (NSString *)serialize {
    return [NSString stringWithFormat:@"{\"name\": \"%@\", \"age\": %ld}", self.name, (long)self.age];
}

@end

// Функция проверки
void serializeObject(id object) {
    if ([object conformsToProtocol:@protocol(Serializable)]) {
        NSString *serialized = [object serialize];
        NSLog(@"Сериализованный объект: %@", serialized);
    } else {
        NSLog(@"Объект не поддерживает сериализацию");
    }
}

// Использование
Person *person = [[Person alloc] init];
person.name = @"Иван";
person.age = 30;

serializeObject(person); // Вывод: Сериализованный объект: {"name": "Иван", "age": 30}

10.7. Заключение

Отличная работа! Вы познакомились с протоколами и делегатами в Objective-C — мощными инструментами, которые помогают создавать гибкие и масштабируемые приложения. Теперь вы знаете, как объявлять и реализовывать протоколы, как использовать делегирование для передачи ответственности между объектами, и как применять эти концепции на практике.


Что дальше?

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

Не забывайте практиковаться! Чем больше вы будете применять протоколы и делегаты в своем коде, тем лучше вы будете понимать их мощь и полезность.

Удачи и до встречи в следующей главе!

Глава 9: Обработка ошибок и исключений ← Вернуться к списку глав Глава 11: Лучшие практики и паттерны проектирования

Просмотров: 41