【发布时间】:2013-07-19 06:38:30
【问题描述】:
问题简述
我可以用一个类别扩展 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"];
这只是将 UILabel 的 text 属性设置为 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"
假设到目前为止我对自己的设计非常满意,并且我想为所有 4 个类再添加 2 个方法:
[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: 的新类别方法。通过这种方式,我可以将数量减少到 4(每个支持的组件一个)+ 3(每个可用的方法一个),而不是实现 12 个函数,总共需要实现 7 个方法。
注意:这些是愚蠢的方法,仅用于说明目的。重要的部分是有许多方法(在本例中为 3),它们的代码不应重复。
我尝试实现这一点的第一步是注意到这 4 个类的第一个共同祖先是 UIView。因此,将这 3 个方法放在逻辑上的位置似乎是在 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
只要当前的 UIView 实例符合WritableView 协议,这 3 个方法就可以工作。因此,我使用以下代码扩展了我的 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 种方法仅适用于那些实现了 writeText: 方法的 UIViews(即那些实现了我设置的 WritableView 协议的 UIViews)。理想情况下,我会在 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 行。没错,它不会在运行时导致异常,但也不会导致任何事情发生,这是我也在努力避免的另一种错误。
没有得到满意答案的类似问题:
- Category for a class that conforms to a protocol:这个问题好像问的是同一个问题,但是没有给出具体的例子,所以好像被mean something else误解了。
- Defining categories for protocols in Objective-C?:提供了不同的示例,因此接受的答案说在需要该方法的每个类的类别中实现它(即在我的示例中您最终会得到 12 个方法);这对那个问题来说是一个很好的答案,但对于这个问题来说并不是一个很好的解决方案。
- How do I define a category that adds methods to classes which implement a particular protocol?:提问者选择了检查协议的实现(如果不符合则什么都不做),但是如果你想让它在编译时发现错误呢?
【问题讨论】:
标签: objective-c objective-c-category objective-c-protocol