Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Обзор property и KVO в objective c

Anna | 2.07.2014 | нет комментариев
image
Здравствуйте. Позже недавнего перехода с objectivе c на java нашел неимение в java столь отличных пророческой как свойств и KVO(Key-Value Observing). В данной статье расказано о том для чего в тезисе усложнять доступ к внутренним переменным объекта, как это реализованно objective c и какую пользу с точки зрения наблюдения за состоянием объектов мы получаем, при применении свойств и KVO.

Часть 1 — property

Свойства в objective c это дерзко публичные переменные класса с модифицированными геттерами/сеттерами. Для того, Дабы отменнее осознать для чего это необходимо посмотрим на пример. Возможен у нас есть класс company хранящий в себе число работников данной компании (people):

class company {
    int people;    
}

Мимолетным взглядом примечаем возможный источник ошибок — число людей в компании объявлено как int включающий также и негативные значения. В данном случае задачу дозволено устранить заменой типа переменной на беззнаковую, но что делать если мы хотим еще огромнее сузить диапазон возможных значений. Скажем в соответствии с ТЗ число работников в компании не может быть огромнее тысячи.
Нормальное решение данной задачи в языках лишенных встроенной поддержки свойств это создание пары акцессор/мутатор (accessor/mutator):

class company {
    private int people;
    void setPeople(int people){
        if(people>0)
        this.people=people;
        else 
            this.people=0;
    }
    int getPeople(){
        return this.people;
    }
}

Мутатор разрешает изменять значение переменной с учетом наложенных ограничений, а присутствие акцессора диктуется необходимостью утаить внутреннюю переменную. Напротив всякий мог бы обратиться к переменной напрямую и записать туда некорректный итог.
Казалась бы задача решена, но стоит подметить один факт — применение пар акцессора/мутатора крепко затрудняет чтение кода. Сравните код увеличения числа рабочих на единицу:

company_A.setPeople(company_A.getPeople() 1);

и

company_A.people  ;

Видимо что во втором случае код больше читаем, да и принуждать программиста писать для всякой переменной пары акцессор/мутатор не отлично. Язык objective c разрешает избежать написания этого тоскливого кода с поддержкой применения свойств. Нормальное изложение класса с применением свойств выглядит так:

// company.h
@class company : NSObject {
@property int people; 
@end

А в реализации класса довольно дописать одну строчку, а остальное компилятор сделает сам.

// company.m
@implementation company
@synthesize people; 
@end

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

  • Права доступа к переменной — readwrite/ readonly при обращении извне.
  • Многопоточный доступ — atomic/nonatomic( синхронизировать чтение/запись между потоками либо нет)
  • Каким образом происходит присвоение — copy/weak/strong. Указание опции copy принуждает при присвоении снимать копию с начального значения, а не легко приравнивать указатели. Опции strong и weak руководят временем жизни переменной (strong — увеличивает retain count объекта, weak — нет и становиться равной nil при разрушении объекта)

Как видно число опций довольно огромно для удовлетворения классических надобностей при написании кода. В случае же наличия ограничений на присылаемые значения нужно вручную переопределить способ set. Также дозволено вручную переопределить способ get (что в ряде случае разрешает избавиться от лишних переменных). Только стоит помнить, что в objective c функция акцессора пишется без префикса get, а synthesize разрешает указать как обращаться к переменной внутри класса. Пример кода.

//company.h
@interface company : NSObject
@property (atomic, readwrite )int people;
@property (atomic, readonly )BOOL isBigCompany;
@end

//company.m

@implementation company
@synthesize people= _people
-int people{
    return _people;
}
- void setPeople(int people){
    if(people>0)
        _people=people;
    else
        _people=-10;
}
- BOOL isBigCompany{
    return _people>1000;
}
@end

//использование 
company *company_A=[[company alloc] init];
 company_A.people=2000;
 if(company_A.isBigCompany){
    company_A.people=-1;
 }
// итог company_A.people=-10 

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

Помимо облегчения синтаксиса применение свойств разрешает применять еще несколько вероятностей языка, как то — обращение к переменной через ее наименование.

 [company_A valueForKey:@"people"];

Либо даже записать туда новое значение. А рассматривая вероятность приобретения списка всех свойств прямо своевременно выполнения программы это открывает крупные вероятности. Примечание по моему личному суждению сходственной вероятностью отменнее не пользоваться потому как это скорее чумазых хак, чем нормальный метод обращения к переменным. Подробнее про objective c runtime дозволено прочитать скажем тут.
Перед тем как рассказывать об KVO стоит упомянуть и об уведомлениях в уведомлениях(notification)

Часть 2 — notification

Objective c разрешает стремительно и с минимальными усилиями реализовать патерн наблюдатель. В 2-х словах патерн наблюдатель это образец проектирования, когда существует два объекта — наблюдатель и отслеживаемый. Наблюдатель интересуются событиями протекающими с отслеживаемым, а вот 2-й отслеживаемый флегматично течет по течению иногда посылая уведомления о протекающих с ним событиях. Отслеживаемый даже не интересуется, а слышит ли его кто-нибудь, но продолжает рассылать уведомления. Сходственный подход разрешает реализовывать обратную связь к протекающим с отслеживаемым изменениям.
Objective c имеет дюже продвинутый механизм работы с уведомлениями, так Дабы зарегистрировать наблюдателя довольно одной строчки кода:

 [[NSNotificationCenter defaultCenter] addObserver:object selector:@selector(observeNotification:) name:nameNotification object:nil];

Где object — объект тот, что будет наблюдателем, трудная конструкция @selector(observeNotification:)указывает какой способ нужно вызвать у наблюдателя (в данном случае observeNotification:) и nameNotification — наименование события за которым мы будем отслеживать. В последнем параметре дозволено указать от какого объекта мы ждем получить уведомление, либо же легко получать все уведомления с заданным именем, что комфортно если предварительно неведомо кто именно пошлет уведомление.
В классе наблюдателя нужно реализовать указанный выше способ (observeNotification:), а в объекте тот, что посылает уведомления довольно написать:

 [[NSNotificationCenter defaultCenter] postNotificationName:nameNotification object:self];

И на этом нужный для примитивной реализации патерна наблюдатель код заканчивается. Как видно нам не пришлось писать какого либо кода, тот, что бы хранил связи между наблюдателем и отслеживаемым, но пока имелась надобность в метаморфоза кода отслеживаемого. А вот KVO разрешает избежать избыточного вмешательства в Сторонний код.

Часть 3 — KVO

KVO (key-value observing) — еще одна реализация патерна наблюдатель, даже больше увлекательная чем предыдущая. В этом случае наблюдатель следит не за событиями, а за определенным свойством (property) объекта. Когда значение этого свойства меняется, наблюдателю приходит уведомление и он соответствующим образом реагируют.
По сопоставлению со многими другими языками реализация KVO в objective c радуют достаточно простым синтаксисом. Так в коде наблюдателя довольно написать:

    [company_a addObserver:self forKeyPath:@"people" options:NSKeyValueObservingOptionNew context:nil];

и всякий раз когда в company_a будет изменяться значение переменной people наблюдатель будет уведомляться с поддержкой вызова способа observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context и нужно лишь реализовать код, тот, что будет реагировать на уведомление.
Вы вероятно хотели спросить, а что нужно дописать в коде класса объекта за которым ведется слежение ( в данном случае company) для реализации сходственной восхитительной вероятности — так вот если применять свойства с механически синтезироваными директивой @ synthesize способами get/set, то ничего дописывать не нужно. В отвратном случае код изменяющий внутреннюю переменную нужно окружить двумя вспомогательными функциями(willChangeValueForKey: и didChangeValueForKey:). Так же с поддержкой способа keyPathsForValuesAffectingValueForKey дозволено указать какие свойства друг на друга влияют, что дозволит доработаь ранее использованный пример дальнейшим образом:

//company.h
@interface company : NSObject
@property (atomic, readwrite )int people;
@property (atomic, readonly )BOOL isBigCompany;
@end

//company.m

@implementation company
@synthesize people= _people
  (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
   if ([key isEqualToString:@"people"])
        return [NSSet setWithObjects:@"isBigCompany", nil];
    else
        return [super keyPathsForValuesAffectingValueForKey:key];
}
- (void) setPeople(int people){
    [self willChangeValueForKey:@"people"];
    if(people>0)
        _people=people;
    else
        _people=0;
    [self didChangeValueForKey:@"people"];
}

- (BOOL) isBigCompany{
    return _peope>1000;
}
@end

Сейчас дозволено подписаться только на наблюдение за свойством isBigCompany и не следить за точным значением переменной people. Уведомления будут приходить только тогда, когда качество isBigCompany будет меняться.
А рассматривая, что в подавляющем большинстве случае программисты не забывают дописать нужные строки мы получаем дюже сильный инструмент для наблюдения за другими объектами. Перечислю превосходства этого подхода

  • Минимализм кода. ( довольно написать каждого лишь несколько строчек, Дабы всецело реализовать паттерн наблюдатель)
  • Вероятность наблюдения за всякими свойствами(property) всяких классов как написанными нами, так и чужими. Реально внешние переменные неизменно оформляются через свойства, что разрешает с легкостью следить всякими изменениями.
  • Повторяюсь вероятность наблюдениям за всякими свойствами всяких классов будь то state у NSOperation либо frame у UIView. KVO разрешает следить за изменением параметров всякого класса
  • И еще раз — не значимо кто писал класс, есть ли его начальный код либо нет. У вас будет вероятность следить за изменениями и это легко фантастическая вещь, которая на порядок упрощает написание кода реакций на события.

Реально выше три раза упомянуто одно и то же превосходство, но это только потому, что аналогов столь сильного механизма в других знаменитых языках нет. В случае если вы хотите отреагировать каким либо образом на метаморфоза одного из параметров, вам нужно либо влезать вовнутрь и переписывать код объекта (что в большинстве случаев легко не допустимо) либо же изобретать другие еще больше трудные обходные пути. KVO же разрешает решить эту задачу стремительно и с минимальным число труда, что делает KVO одной из основной фишек objective C.

Часть 4 — недочеты KVO

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

  • Первая и дюже значимая задача — это невидимое падение продуктивности при обильном применении KVO. Не стоит писать код, где ваши объекты общаются в основном через KVO. Рассматривайте KVO как вспомогательно средство для работы с чужим кодом, а не как стержневой инструмент.
  • 2-й загвоздкой является надобность дюже старательно писать код при применении KVO. Так как строковые идентификаторов не проверяются компилятором на валидность, то это может привести к ошибкам при переименовании переменных. Как бороться с этой загвоздкой стоит прочитать тут.

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

- (void) setObservable:(id)observable{
    [_observable addObserver:self forKeyPath:@"property" options:NSKeyValueObservingOptionNew context:nil];
    _observable=observable;
    [_observable removeObserver:self forKeyPath:@"propery"];
}

Таким образом соотношение между добавлением и удалением сурово равно единице и нет необходимости скрупулезно следить где именно добавляется/удаляется слежение. Вызов деструктора также огромнее не является загвоздкой, так как перед истреблением объекта в качество запишется nil, заодно закончив слежение за объектом.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB