我可以使用类别扩展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(每个可用方法一个),总共需要实现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
协议,这三种方法就可以正常工作。所以我使用以下代码扩展了我支持的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"];
有效! Hazzah!一切都很完美......直到我尝试这个:
[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行进行编译。没错,它不会在运行时导致异常,但也不会导致任何事情发生,这也是我也试图避免的另一种错误。
类似的问题没有给出令人满意的答案:
答案 0 :(得分:8)
听起来你正在追求的是mixin:定义一系列形成所需行为的方法,然后将该行为仅添加到需要它的类集中。
这是我在项目EnumeratorKit中取得巨大成功的策略,它将Ruby样式的块枚举方法添加到内置的Cocoa集合类中(特别是EKEnumerable.h
和{{3} }}:
定义描述所需行为的协议。对于您要提供的方法实现,将它们声明为@optional
。
@protocol WritableView <NSObject>
- (void)writeText:(NSString *)text;
@optional
- (void)setTextToDomainOfUrl:(NSString *)text;
- (void)setTextToIntegerValue:(NSInteger)value;
- (void)setCapitalizedText:(NSString *)text;
@end
创建一个符合该协议的类,并实现所有可选方法:
@interface WritableView : NSObject <WritableView>
@end
@implementation WritableView
- (void)writeText:(NSString *)text
{
NSAssert(@"expected -writeText: to be implemented by %@", [self class]);
}
- (void)setTextToDomainOfUrl:(NSString *)text
{
// implementation will call [self writeText:text]
}
- (void)setTextToIntegerValue:(NSInteger)value
{
// implementation will call [self writeText:text]
}
- (void)setCapitalizedText:(NSString *)text
{
// implementation will call [self writeText:text]
}
@end
在NSObject
上创建一个类别,可以在运行时将这些方法添加到任何其他类(请注意,此代码不支持类方法,只支持实例方法):
#import <objc/runtime.h>
@interface NSObject (IncludeWritableView)
+ (void)includeWritableView;
@end
@implementation
+ (void)includeWritableView
{
unsigned int methodCount;
Method *methods = class_copyMethodList([WritableView class], &methodCount);
for (int i = 0; i < methodCount; i++) {
SEL name = method_getName(methods[i]);
IMP imp = method_getImplementation(methods[i]);
const char *types = method_getTypeEncoding(methods[i]);
class_addMethod([self class], name, imp, types);
}
free(methods);
}
@end
现在在要包含此行为的类中(例如,UILabel
):
WritableView
协议writeText:
实例方法将此添加到您的实施顶部:
@interface UILabel (WritableView) <WritableView>
@end
@implementation UILabel (WritableView)
+ (void)load
{
[self includeWritableView];
}
// implementation specific to UILabel
- (void)writeText:(NSString *)text
{
self.text = text;
}
@end
希望这会有所帮助。我发现这是一种非常有效的方法来实现跨领域的关注,而无需复制和粘贴多个类别之间的代码。
答案 1 :(得分:1)
Swift 2.0引入了Protocol Extensions,这正是我想要的。如果我只是使用Swift,我可以使用以下代码实现所需的结果:
protocol WritableView {
func writeText(text: String)
}
extension WritableView {
func setTextToDomainOfUrl(text: String) {
let t = text.stringByReplacingOccurrencesOfString("http://", withString:"") // just an example, obviously this can be improved
writeText(t)
}
func setTextToIntegerValue(value: Int) {
writeText("\(value)")
}
func setCapitalizedText(text: String) {
writeText(text.capitalizedString)
}
}
extension UILabel: WritableView {
func writeText(text: String) {
self.text = text
}
}
extension UIButton: WritableView {
fun writeText(text: String) {
setTitle(text, forState:.Normal)
}
}
不幸的是,在我使用Swift和Objective-C的有限测试中,看起来你不能在Objective-C中使用Swift协议扩展(例如我选择在Swift中扩展协议 WritableView 的那一刻, Objective-C不再可见协议 WritableView 。