Вместо написания собственных методов доступа, включения в прикладной код директивы ©synthesize или применения автоматического синтеза можно снабдить объявление свойства директивой ©dynamic (в разделе реализации).

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

Это редкое, но не такое уж и неслыханное явление. Оно возникает, главным образом, в следующих двух контекстах: при определении собственного свойства анимационного представления, а также при использовании управляемых свойств в базе данных Core Data. В обоих случаях вы берете на себя бразды правления средой Cocoa, чтобы каким-нибудь чудесным образом обработать вызовы методов доступа. Несмотря на то что вам точно неизвестно, каким образом такая обработка выполняется в среде Cocoa, вас это не должно особенно беспокоить.

Если эту чудесную обработку требуется выполнить самостоятельно, то интересно, прежде всего, знать, как в подобных случаях это делается в самой средой Cocoa? Ответ на этот вопрос кроется в возможностях языка Objective-C организовывать динамический обмен сообщениями. Хотя это расширенные возможности, тем не менее они настолько привлекательны, что так и напрашиваются на демонстрацию на конкретном примере.

Итак, предлагается написать класс, в котором объявляются свойства name (типа NSString) и number (типа NSNumber), но отсутствуют методы доступа к этим свойствам и не предполагается их синтез. Вместо этого свойства name и number объявляются в разделе реализации данного класса как динамические. Поскольку мы отказываемся от любой помощи, которую может предоставить автоматический синтез, то нам придется самостоятельно объявить переменные экземпляра, как показано ниже.

// раздел интерфейса, в котором объявляются name и number 0implementation MyClass (

NSString* _name;

NSNumber* _number;

@dynamic name, number;

// ...ввести здесь код чудесной обработки...

@end

Теперь перейдем непосредственно к изобретению способов чудесной обработки. В данном примере для этой цели используется малоизвестный метод resolve Ins tanceMethod: из класса NSObject.

Отправка сообщения объекту отличается от вызова того же самого метода для этого объекта. Если этот метод отсутствует у данного объекта, то предпринимаются дополнительные шаги, чтобы разрешить такой метод. Один из этих первых шагов заключается в следующем: когда к объекту в первый раз поступает заданное сообщение, у которого нет соответствующего метода в классе этого объекта, среда выполнения пытается найти реализацию метода resolvelnstanceMethod: в этом классе. Если она найдет такой метод, то вызовет его, передав ему селектор данного сообщения. Метод resolvelnstanceMethod: возвратит логическое значение типа BOOL. Если это логическое значение YES, то оно означает, что все в порядке и можно вызывать данный метод.

Как метод resolvelnstanceMethod: вообще мог возвратить такой ответ? Что он мог сделать такого, чтобы стал возможным вызов несуществующего метода? Он мог просто создать этот метод. Динамические возможности языка Objective-C позволяют это сделать. Не следует забывать, что метод resolvelnstanceMethod: вызывается лишь один раз для каждого разрешаемого метода. Следовательно, если он создал такой метод и возвратил логическое значение YES, то проблему существования этого метода можно в дальнейшем считать разрешенной раз и навсегда.

Для того чтобы создать метод в реальном времени, т.е. динамически, следует вызвать функцию class_addMethod (). Для этого придется организовать импорт файла, введя <ob j с / runt ime. h> или соответствующую директиву @ import языка Objective-C, если применяются модули. Данная функция принимает следующие параметры.

  • Класс, в который предполагается ввести метод.
  • Селектор для вводимого метода (по существу, это имя данного метода).
  • Указатель на метод экземпляра (IMP) для данного метода. Что такое IMP? Это функция, возвращающая данный метод. За каждым методом Objective-C стоит функция С. Эта функция принимает те же самые параметры, что и метод Objective-C, а кроме них — еще два параметра, которые указываются первыми. Это объект, действующий как self в данной функции, а также селектор для метода, который данная функция возвращает.
  • Символьная строка С, описывающая в специальном коде тип значения, возвращаемого функцией. Этот тип является также типом значения, возвращаемого методом. Под типом здесь подразумевается нечто большее, чем тип С. В частности, тип каждого объекта считается одним и тем же.

В рассматриваемом здесь примере необходимо охватить два динамических свойства (паше и number) и два метода доступа к ним (setName: и setNumber:). Итак, чтобы вызвать функцию class_addMethod() в методе resolvelnstanceMethod:, нужно также написать функции С, которые будут действовать как IMP для каждого из упомянутых методов доступа. Для этого можно было бы написать четыре функции С, но это было бы бесполезно! Если сделать это, то зачем вообще утруждать себя применением динамического метода доступа? Вместо этого предлагается написать только две функции С: одну — для обращения к любому get-методу, который, возможно, потребуется направить ей, а другую — для обращения к любому set-методу, который также может быть направлен ей.

Рассмотрим сначала get-метод, поскольку он намного проще. Что же вообще должен делать обобщенный get-метод? Он должен получать доступ к соответствующей переменной

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

id callValueForKey(id self, SEL _cmd) {

NSString* key = NSStringFromSelector(_cmd); key = [@M_,f stringByAppendingString:key]; return [self valueForKey:key];

}

Теперь, когда имеется get-метод, становится понятнее, как написать set-метод. В частности, чтобы получить имя переменной экземпляра, придется выполнить несколько более сложное манипулирование именем селектора, а именно: отбросить слово set в начале этого имени и двоеточие в его конце, сделать строчной первую букву и затем присоединить полученное имя к знаку подчеркивания, как и прежде. Ниже показано, как все это реализуется непосредственно в коде.

void callSetValueForKey(id self, SEL _cmd, id value) {

NSString* key = NSStringFromSelector(_cmd);

key = [key substringWithRange:NSMakeRange(3, [key length]-4)];

NSString* firstCharLower =

[[key substringWithRange:NSMakeRange(0,1)] lowercaseString]; key = [key stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:firstCharLower]; key = [@"_M stringByAppendingString:key];

[self setValuervalue forKey:key];

}

В заключение, можно приступить к написанию метода resolvelnstanceMethod:. В данном примере этот метод выполняет роль контролера, проверяющего, является ли метод, который должен быть вызван, методом доступа к одному из динамических свойств, как показано ниже. (Если вам не понятно, что собой представляет закодированная символьная строка, указанная в приведенном ниже коде в качестве четвертого аргумента функции class_addMethod () языка С, обращайтесь за справкой к документации, где поясняется ее назначение.)

+ (BOOL) resolvelnstanceMethod: (SEL)       sel {

// этот метод будет вызван

if (sel == Qselector(setName:) I I sel == ^selector(setNumber:)) {

class_addMethod([self class], sel, (IMP) callSetValueForKey, "v@:@"); return YES;

}

if (sel == @selector(name) || sel == ©selector(number)) {

class_addMethod([self class], sel, (IMP) callValueForKey, "00:"); return YES;

}

return [super resolvelnstanceMethod:sel];

Рассмотренная выше реализация динамических методов доступа довольно проста. В частности, ради простоты в ней применяется механизм доступа к значениям по ключам. Здесь также пришлось отказаться от копирования семантики в методе установки из класса NSString. Это довольно распространенный прием, который все же позволяет раскрыть внутренний механизм языка Objective-C.


 

 

 

Добавить комментарий