为在Objective-C中实现特定协议的类创建类别? [英] Creating a category for classes that implement a specific protocol in Objective-C?

查看:91
本文介绍了为在Objective-C中实现特定协议的类创建类别?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我可以用类别扩展UIView,但只能在实现特定协议(WritableView)的子类上使用吗?

Can I extend UIView with a category, but have it only work on subclasses that implement a specific protocol (WritableView)?

即我可以做以下事情吗?

I.e. can I do something like the following?

@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的任何实例:


Detailed problem description

Imagine I want the following category function added to any instance of UILabel:

[label setTextToDomainOfUrl:@"http://google.com"];

只需将UILabel的text属性设置为google.com.

Which simply sets a UILabel's text property to google.com.

类似地,我希望能够在其他几个类上调用此函数:

Simlarly, I want to be able to call this function on several other classes:

[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个方法:

Let's say I'm really happy with my design so far, and I want to add 2 more methods to all 4 classes:

[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).随着我添加更多函数,我需要在我的每个子类上实现它们,这些子类很快就会变得很难维护.

So now we have 4 classes that have 3 methods each. If I wanted to actually make this work, I would need to write 12 functions (4*3). As I add more functions, I need to implement them on each of my subclasses, which can quickly become very hard to maintain.

相反,我只想实现这些方法一次,并且只需在称为writeText:的受支持组件上公开一个新的类别方法即可.通过这种方式,不必实现12个功能,我可以将总数减少为4(每个受支持的组件一个)+ 3(每个可用的方法一个),总共需要实现7种方法.

Instead, I want to implement these methods only once, and simply expose a new category method on the supported components called writeText:. This way instead of having to implement 12 functions, I can cut the number down to 4 (one for each supported component) + 3 (one for each method available) for a total of 7 methods that need to be implemented.

注意:这些是愚蠢的方法,仅用于说明目的.重要的是,有很多方法(在本例中为3),它们的代码不应重复.

Note: These are silly methods, used just for illustrative purposes. The important part is that there are many methods (in this case 3), which shouldn't have their code duplicated.

我尝试实现此目标的第一步是注意到这4个类的第一个共同祖先是UIView.因此,放置这三种方法的逻辑位置似乎在UIView上的类别中:

My first step at trying to implement this is noticing that the first common ancestor of these 4 classes is UIView. Therefore, the logical place to put the 3 methods seems to be in a category on 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个受支持的类:

These 3 methods will work as long as the current instance of UIView conforms to the WritableView protocol. So I extend my 4 supported classes with the following code:

@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

现在,当我打电话给我时:

And now when I call the following:

[label setTextToDomainOfUrl:@"http://apple.com"];
[tableViewCell setCapitalizedText:@"hello"];

有效!哈扎!一切正常……直到我尝试这样做:

It works! Hazzah! Everything works perfectly... until I try this:

[slider setTextToDomainOfUrl:@"http://apple.com"];

代码可以编译(因为UISlider继承自UIView),但是在运行时失败(因为UISlider不符合WritableView协议).

The code compiles (since UISlider inherits from UIView), but fails at run time (since UISlider doesn't conform to the WritableView protocol).

我真正想做的是使这3种方法仅适用于已实现writeText:方法的UIView(即那些实现了我设置的WritableView协议的UIView).理想情况下,我将在UIView上定义类别,如下所示:

What I would really like to do is make these 3 methods only available to those UIViews which have a writeText: method implemented (I.e. those UIViews which implement the WritableView protocol I set up). Ideally, I would define my category on UIView like the following:

@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协议),但是它将使我的所有其他示例成功.

The idea is that if this were valid syntax, it would make [slider setTextToDomainOfUrl:@"http://apple.com"] fail at compile time (since UISlider never implements the WritableView protocol), but it would make all my other examples succeed.

所以我的问题是:有没有办法用一个类别扩展一个类,但是将其限制为仅实现了特定协议的那些子类?

So my question is: is there any way to extend a class with a category, but limit it to only those subclasses that have implemented a specific protocol?

我意识到我可以将断言(检查其是否符合协议)更改为if语句,但是仍然可以使有问题的UISlider行进行编译.是的,它不会在运行时引起异常,但也不会导致任何事情发生,这也是我也想避免的另一种错误.

I realize I could change the assertion (which checks that it conforms to the protocol) to an if statement, but that would still allow the buggy UISlider line to compile. True, it won't cause an exception at runtime, but it won't cause anything to happen either, which is another kind of error I am also trying to avoid.

未得到令人满意答案的类似问题:

Similar questions that haven't been given satisfactory answers:

  • 类别为符合协议的类别:这个问题似乎在问同样的事情,但是没有给出具体的例子,因此似乎被误解为在Objective-C中定义协议的类别?:不同提供了示例,因此可接受的答案说要在需要该方法的每个类的类别中实现它(即,在我的示例中最终将有12个方法);这是一个很好的答案,但对于这个问题并不是一个很好的解决方案.
  • Category for a class that conforms to a protocol: This question seems to be asking the same thing, but didn't give specific examples, so it seems like it was misunderstood to mean something else.
  • Defining categories for protocols in Objective-C?: Different example provided, so the accepted answer says to implement it in the category of each class that needs the method (i.e. you would end up with 12 methods in my example); which is a totally fine answer for that one, but not really a good solution for this problem.
  • How do I define a category that adds methods to classes which implement a particular protocol?: The asker chose to go the route of checking for implementation of the protocol (and doing nothing if it doesn't conform), but what if you want it to spot errors at compile time?

推荐答案

听起来像是在混入:定义一系列形成所需行为的方法,然后将该行为仅添加到一组需要它的类.

It sounds like what you're after is a mixin: define a series of methods that form the behaviour that you want, and then add that behaviour to only the set of classes that need it.

这是我在项目 EnumeratorKit (该项目添加了Ruby-内置Cocoa集合类的样式块枚举方法(尤其是 EKEnumerable.h EKEnumerable.m :

Here is a strategy I've used to great success in my project EnumeratorKit, which adds Ruby-style block enumeration methods to built-in Cocoa collection classes (in particular EKEnumerable.h and EKEnumerable.m:

  1. 定义描述所需行为的协议.对于要提供的方法实现,请将其声明为@optional.

@protocol WritableView <NSObject>

- (void)writeText:(NSString *)text;

@optional
- (void)setTextToDomainOfUrl:(NSString *)text;
- (void)setTextToIntegerValue:(NSInteger)value;
- (void)setCapitalizedText:(NSString *)text;

@end

  • 创建一个符合该协议的类,并实现所有可选方法:

  • Create a class that conforms to that protocol, and implements all the optional methods:

    @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上创建一个类别,该类别可以在运行时将这些方法添加到任何其他类中(请注意,此代码不支持类方法,仅支持实例方法):

  • Create a category on NSObject that can add these methods to any other class at runtime (note that this code doesn't support class methods, only instance methods):

    #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):

    Now in the class where you want to include this behaviour (for example, UILabel):

    1. 采用WritableView协议
    2. 实施必需的writeText:实例方法
    3. 将其添加到实现的顶部:

    1. Adopt the WritableView protocol
    2. Implement the required writeText: instance method
    3. Add this to the top of your implementation:

    @interface UILabel (WritableView) <WritableView>
    
    @end
    
    @implementation UILabel (WritableView)
    
    + (void)load
    {
        [self includeWritableView];
    }
    
    // implementation specific to UILabel
    - (void)writeText:(NSString *)text
    {
        self.text = text;
    }
    
    @end
    

    希望这会有所帮助.我发现这是一种真正有效的方法,可以解决跨领域的问题,而无需复制&在多个类别之间粘贴代码.

    Hope this helps. I've found it a really effective way to implement cross-cutting concerns without having to copy & paste code between multiple categories.

    这篇关于为在Objective-C中实现特定协议的类创建类别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆