Это довольно распространенный вопрос для людей, переходящих от языков со строгим типом (таких как C ++ или Java) к языкам со слабой или динамической типизацией, таким как Python, Ruby или Objective-C. В Objective-C большинство объектов наследуют от NSObject
(тип id
) (остальные наследуют от другого корневого класса, такого как NSProxy
и также могут иметь тип id
), и любое сообщение может быть отправлено любому объект. Конечно, отправка сообщения экземпляру, который он не распознает, может вызвать ошибку времени выполнения (а также вызвать компилятор предупреждение с соответствующими флагами -W). Пока экземпляр отвечает на отправленное вами сообщение, вам может быть все равно, к какому классу он принадлежит. Это часто называют «типизацией утки», потому что «если она крякает как утка [то есть отвечает на селектор], то это утка [то есть она может обработать сообщение; кому какое дело, какой это класс]».
Вы можете проверить, отвечает ли экземпляр на селектор во время выполнения с помощью метода -(BOOL)respondsToSelector:(SEL)selector
. Предполагая, что вы хотите вызывать метод для каждого экземпляра в массиве, но не уверены, что все экземпляры могут обработать сообщение (поэтому вы не можете просто использовать NSArray
's -[NSArray makeObjectsPerformSelector:]
, что-то вроде это будет работать:
for(id o in myArray) {
if([o respondsToSelector:@selector(myMethod)]) {
[o myMethod];
}
}
Если вы управляете исходным кодом для экземпляров, которые реализуют метод (ы), который вы хотите вызвать, то более распространенным подходом будет определение @protocol
, содержащего эти методы, и объявление о том, что рассматриваемые классы реализуют этот протокол в их декларации. В этом случае @protocol
аналогичен интерфейсу Java или абстрактному базовому классу C ++. Затем вы можете проверить соответствие всему протоколу, а не ответ на каждый метод. В предыдущем примере это не имело бы большого значения, но если бы вы вызывали несколько методов, это могло бы упростить вещи. Пример будет таким:
for(id o in myArray) {
if([o conformsToProtocol:@protocol(MyProtocol)]) {
[o myMethod];
}
}
при условии MyProtocol
объявляет myMethod
. Этот второй подход предпочтительнее, потому что он разъясняет цель кода больше, чем первый.
Часто один из этих подходов освобождает вас от заботы о том, принадлежат ли все объекты в массиве заданному типу. Если вам все равно все равно, стандартный динамический языковой подход заключается в модульном тесте, модульном тесте, модульном тесте. Поскольку регрессия в этом требовании приведет к (вероятно, неустранимой) ошибке времени выполнения (не времени компиляции), вам необходимо иметь тестовое покрытие, чтобы проверить поведение, чтобы вы не выпустили аварийный сбой. В этом случае выполните операцию, которая модифицирует массив, а затем убедитесь, что все экземпляры в массиве принадлежат данному классу. При надлежащем тестовом покрытии вам даже не понадобятся дополнительные накладные расходы на проверку подлинности экземпляра. У вас есть хорошее покрытие модульных тестов, не так ли?