Краткое описание проблемы
Могу ли я расширить UIView категорией, но работать ли он только с подклассами, реализующими определенный протокол (WritableView
)?
Т.е. я могу сделать что-то вроде следующего?
@interface UIView<WritableView> (foo) // SYNTAX ERROR
- (void)setTextToDomainOfUrl:(NSString *)text;
- (void)setTextToIntegerValue:(NSInteger)value;
- (void)setCapitalizedText:(NSString *)text;
@end
@implementation UIView<WritableView> (foo)
// implementation of 3 above methods would go here
@end
Подробное описание проблемы
Представьте, что я хочу добавить следующую функцию категории к любому экземпляру UILabel
:
[label setTextToDomainOfUrl:@"http://google.com"];
Это просто устанавливает для свойства text
UILabel значение google.com
.
Точно так же я хочу иметь возможность вызывать эту функцию для нескольких других классов:
[button setTextToDomainOfUrl:@"http://apple.com"]; // same as: [button setTitle:@"apple.com" forState:UIControlStateNormal];
[textField setTextToDomainOfUrl:@"http://example.com"]; // same as: textField.text = @"example.com"
[tableViewCell setTextToDomainOfUrl:@"http://stackoverflow.com"]; // same as: tableViewCell.textLabel.text = @"stackoverflow.com"
Допустим, я действительно доволен своим дизайном, и я хочу добавить еще 2 метода ко всем 4 классам:
[label setTextToIntegerValue:5] // same as: label.text = [NSString stringWithFormat:@"%d", 5];
[textField setCapitalizedText:@"abc"] // same as: textField.text = [@"abc" capitalizedString]
Итак, теперь у нас есть 4 класса, каждый по 3 метода. Если бы я действительно хотел эту работу, мне нужно было бы написать 12 функций (4 * 3). По мере добавления дополнительных функций мне необходимо реализовать их в каждом из моих подклассов, что может быстро стать очень сложным в обслуживании.
Вместо этого я хочу реализовать эти методы только один раз и просто предоставить новый метод категории для поддерживаемых компонентов с именем writeText:
. Таким образом, вместо того, чтобы реализовывать 12 функций, я могу сократить их количество до 4 (по одному для каждого поддерживаемого компонента) + 3 (по одному для каждого доступного метода), всего 7 методов, которые необходимо реализовать.
Примечание: это глупые методы, используемые только в иллюстративных целях. Важная часть состоит в том, что существует множество методов (в данном случае 3), код которых не должен дублироваться.
Мой первый шаг в попытке реализовать это - заметить, что первым общим предком этих 4 классов является UIView
. Таким образом, логичным местом для размещения трех методов является категория на UIView
:
@interface UIView (foo)
- (void)setTextToDomainOfUrl:(NSString *)text;
- (void)setTextToIntegerValue:(NSInteger)value;
- (void)setCapitalizedText:(NSString *)text;
@end
@implementation UIView (foo)
- (void)setTextToDomainOfUrl:(NSString *)text {
text = [text stringByReplacingOccurrencesOfString:@"http://" withString:@""]; // just an example, obviously this can be improved
// ... implement more code to strip everything else out of the string
NSAssert([self conformsToProtocol:@protocol(WritableView)], @"Must conform to protocol");
[(id<WritableView>)self writeText:text];
}
- (void)setTextToIntegerValue:(NSInteger)value {
NSAssert([self conformsToProtocol:@protocol(WritableView)], @"Must conform to protocol");
[(id<WritableView>)self writeText:[NSString stringWithFormat:@"%d", value]];
}
- (void)setCapitalizedText:(NSString *)text {
NSAssert([self conformsToProtocol:@protocol(WritableView)], @"Must conform to protocol");
[(id<WritableView>)self writeText:[text capitalizedString]];
}
@end
Эти 3 метода будут работать, пока текущий экземпляр UIView соответствует протоколу WritableView
. Поэтому я расширяю свои 4 поддерживаемых класса следующим кодом:
@protocol WritableView <NSObject>
- (void)writeText:(NSString *)text;
@end
@interface UILabel (foo)<WritableView>
@end
@implementation UILabel (foo)
- (void)writeText:(NSString *)text {
self.text = text;
}
@end
@interface UIButton (foo)<WritableView>
@end
@implementation UIButton (foo)
- (void)writeText:(NSString *)text {
[self setTitle:text forState:UIControlStateNormal];
}
@end
// similar code for UITextField and UITableViewCell omitted
И вот когда я звоню следующее:
[label setTextToDomainOfUrl:@"http://apple.com"];
[tableViewCell setCapitalizedText:@"hello"];
Оно работает! Хаза! Все работает отлично ... пока я не попробую это:
[slider setTextToDomainOfUrl:@"http://apple.com"];
Код компилируется (поскольку UISlider
наследуется от UIView
), но не выполняется во время выполнения (поскольку UISlider
не соответствует протоколу WritableView
).
Что я действительно хотел бы сделать, так это сделать эти 3 метода доступными только для тех UIView, в которых реализован метод writeText:
(т.е. те UIViews, которые реализуют протокол WritableView
, который я установил). В идеале я бы определил свою категорию в UIView следующим образом:
@interface UIView<WritableView> (foo) // SYNTAX ERROR
- (void)setTextToDomainOfUrl:(NSString *)text;
- (void)setTextToIntegerValue:(NSInteger)value;
- (void)setCapitalizedText:(NSString *)text;
@end
Идея состоит в том, что если бы это был допустимый синтаксис, это привело бы [slider setTextToDomainOfUrl:@"http://apple.com"]
к сбою во время компиляции (поскольку UISlider
никогда не реализует WritableView
протокол), но все остальные мои примеры были бы успешными.
Итак, мой вопрос: есть ли способ расширить класс категорией, но ограничить его только теми подклассами, которые реализовали определенный протокол?
Я понимаю, что могу изменить утверждение (которое проверяет его соответствие протоколу) на оператор if
, но это все равно позволит скомпилировать ошибочную строку UISlider. Правда, это не вызовет исключения во время выполнения, но и ничего не произойдет, что является еще одним видом ошибок, которых я также пытаюсь избежать.
Подобные вопросы, на которые не было дано удовлетворительного ответа:
- Категория для класса, соответствующего протоколу: В этом вопросе, похоже, задается то же самое, но не приводится конкретных примеров, поэтому кажется, что это неправильно поняли для означает другое.
- Определение категорий для протоколов в Objective-C?: представлен другой пример, поэтому в принятом ответе говорится о том, чтобы реализовать его в категории каждого класса, которому нужен метод (т.е. в моем примере у вас будет 12 методов); что является прекрасным ответом на этот вопрос, но не совсем хорошим решением этой проблемы.
- Как определить категорию, которая добавляет методы к классам, реализующим конкретный протокол?: запрашивающий решил пойти по маршруту проверки реализации протокола (и ничего не делать, если он не соответствует), но что, если вы хотите, чтобы он обнаруживал ошибки во время компиляции?