Objective-C中的Mixins或多继承?

9
假设我有一个MyUITextViewSubclass类和一个MyUITextFieldSubclass类,它们都继承自UITextView和UITextField,并且这两个子类包含许多相同的方法和属性以添加类似的行为到这些UI控件中。由于UITextView和UITextField继承自不同的类,是否有一种简单的方法来创建一个抽象类来组合所有重复的代码呢?换句话说,我能否创建一个抽象类,从而继承这两个子类,并仅覆盖那些两者之间不同的方法呢? 我现在知道的是:我知道Objective-C不支持多重继承(从两个或更多类继承),我知道我可以使用Categories添加共同的方法,但我认为这不能解决覆盖init方法或添加私有属性的问题。

你能更具体地说明常见行为是什么吗? - Brad Allred
5个回答

12

在Amin的回答基础上,您可以按照以下步骤进行操作:

第1步:创建一个TextSurrogateHosting协议,其中包含您需要从要添加到两个子类中的方法访问的UITextFieldUITextView子类的所有方法。例如,这可能是一个textsetText:方法,以便您的方法可以访问并设置文本字段或文本视图的文本。它可能看起来像这样:

SPWKTextSurrogateHosting.h

#import <Foundation/Foundation.h>

@protocol SPWKTextSurrogateHosting <NSObject>

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

@end

第二步: 创建一个 TextSurrogate 类,其中包含您想在 UITextFieldUITextView 子类之间共享的所有方法。将这些方法添加到协议中,以便我们可以在Xcode中使用代码补全并避免编译器警告/错误。

SPWKTextSurrogate.h

#import <Foundation/Foundation.h>
#import "SPWKTextSurrogateHosting.h"

@protocol SPWKTextSurrogating <NSObject>
@optional
- (void)appendQuestionMark;
- (void)appendWord:(NSString *)aWord;
- (NSInteger)characterCount;
- (void)capitalize;
@end

@interface SPWKTextSurrogate : NSObject <SPWKTextSurrogating>

/* We need to init with a "host", either a UITextField or UITextView subclass */
- (id)initWithHost:(id<SPWKTextSurrogateHosting>)aHost;

@end

SPWKTextSurrogate.m

#import "SPWKTextSurrogate.h"

@implementation SPWKTextSurrogate {
    id<SPWKTextSurrogateHosting> _host;
}

- (id)initWithHost:(id<SPWKTextSurrogateHosting>)aHost
{
    self = [super init];

    if (self) {
        _host = aHost;
    }

    return self;
}

- (void)appendQuestionMark
{
    _host.text = [_host.text stringByAppendingString:@"?"];
}

- (void)appendWord:(NSString *)aWord
{
    _host.text = [NSString stringWithFormat:@"%@ %@", _host.text, aWord];
}

- (NSInteger)characterCount
{
    return [_host.text length];
}

- (void)capitalize
{
    _host.text = [_host.text capitalizedString];
}

@end

第三步:创建你的UITextField子类。它将包含三个必要的样板方法,用于将未识别的方法调用转发给你的SPWKTextSurrogate

SPWKTextField.h

#import <UIKit/UIKit.h>
#import "SPWKTextSurrogateHosting.h"
#import "SPWKTextSurrogate.h"

@interface SPWKTextField : UITextField <SPWKTextSurrogateHosting, SPWKTextSurrogating>

@end

SPWKTextField.m

#import "SPWKTextField.h"

@implementation SPWKTextField {
    SPWKTextSurrogate *_surrogate;
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        _surrogate = [[SPWKTextSurrogate alloc] initWithHost:self];
    }
    return self;
}

#pragma mark Invocation Forwarding
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([_surrogate respondsToSelector:[anInvocation selector]]) {
        [anInvocation invokeWithTarget:_surrogate];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
    NSMethodSignature* signature = [super methodSignatureForSelector:selector];
    if (!signature) {
        signature = [_surrogate methodSignatureForSelector:selector];
    }
    return signature;
}

- (BOOL)respondsToSelector:(SEL)aSelector
{
    if ([super respondsToSelector:aSelector] ||
        [_surrogate respondsToSelector:aSelector])
    {
        return YES;
    }
    return NO;
}

@end

第四步:创建你的UITextView子类。

SPWKTextView.h

#import <UIKit/UIKit.h>
#import "SPWKTextSurrogateHosting.h"
#import "SPWKTextSurrogate.h"

@interface SPWKTextView : UITextView <SPWKTextSurrogateHosting, SPWKTextSurrogating>

@end

SPWKTextView.m

#import "SPWKTextView.h"
#import "SPWKTextSurrogate.h"

@implementation SPWKTextView {
    SPWKTextSurrogate *_surrogate;
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        _surrogate = [[SPWKTextSurrogate alloc] initWithHost:self];
    }
    return self;
}

#pragma mark Invocation Forwarding
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([_surrogate respondsToSelector:[anInvocation selector]]) {
        [anInvocation invokeWithTarget:_surrogate];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
    NSMethodSignature* signature = [super methodSignatureForSelector:selector];
    if (!signature) {
        signature = [_surrogate methodSignatureForSelector:selector];
    }
    return signature;
}

- (BOOL)respondsToSelector:(SEL)aSelector
{
    if ([super respondsToSelector:aSelector] ||
        [_surrogate respondsToSelector:aSelector])
    {
        return YES;
    }
    return NO;
}

@end

步骤5:使用它:

SPWKTextField *textField = [[SPWKTextField alloc] initWithFrame:CGRectZero];
SPWKTextView *textView = [[SPWKTextView alloc] initWithFrame:CGRectZero];

textField.text = @"The green fields";
textView.text = @"What a wonderful view";

[textField capitalize];
[textField appendWord:@"are"];
[textField appendWord:@"green"];
[textField appendQuestionMark];

NSLog(@"textField.text: %@", textField.text);
// Output: The Green Fields are green?

[textView capitalize];
[textView appendWord:@"this"];
[textView appendWord:@"is"];

NSLog(@"textView.text: %@", textView.text);
// Output: What A Wonderful View this is

这个模式应该能解决你的问题。希望如此 :)

这里还提供了更多的背景信息:https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtForwarding.html#//apple_ref/doc/uid/TP40008048-CH105


绝对是一个有趣的方法!这解决了我原来方法中的大部分问题。谢谢分享 :) - ZeNewb

2
你需要的是一个mixin。在Objective-C中不支持这个功能。分类并不是mixin,因为它们只为一个类添加API而不是多个(>1)类。使用分类无法解决你所说的许多原因,也无法帮助你。
通常解决这个问题的方法是创建一个包含额外代码的辅助类,并在两个类中使用它。
然后你会发现自己在输入:
[myUITextViewSubclass.helper doSomething]

替代

[myUITextViewSubclass doSomething]

如果这真的是个问题,你可以通过转发调用来解决。只需写一个注释即可。

0

Objective-C不支持Traits或Mixins,你只有内置的分类选项。

但幸运的是,Objective-C Runtime几乎拥有所有工具来实现自己的混合或特性想法,通过在运行时向类添加方法和属性。您可以在苹果文档网站Objective-C Runtime Docs上了解更多Objective-C Runtime为您提供的机会。

思路如下:

1)您可以创建一个Objective-C协议(Mixin),其中您将声明属性和方法。

2)然后创建一个类(Mixin实现),该类将实现此协议中的方法。

3)使您希望提供与mixins组合的可能性的某些类符合该协议(Mixin)。

4)当您的应用程序启动时,您使用Objective-C runtime将(Mixin实现)类中的所有实现以及(Mixin)中声明的属性添加到您的类中。

5)完成 :)

或者您可以使用一些现成的开源项目,例如“Alchemiq


0

不,这是不可能的。

你能做到的最接近的事情就是手动添加功能到UITextView,使其模仿UITextField。显而易见的缺点是你必须手动完成所有这些工作,使用自己的代码。


0

你可以使用预处理器宏,但这样容易出错。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接