Mixins или множественное наследование в Objective-C?

Скажем, например, что у меня есть MyUITextViewSubclass который наследует от UITextView а также MyUITextFieldSubclass который наследует от UITextField и оба этих подкласса содержат много одинаковых методов и свойств для добавления сходного поведения к этим элементам управления пользовательского интерфейса.

поскольку UITextView а также UITextField наследовать от разных классов, есть ли простой способ создать абстрактный класс, чтобы объединить весь этот повторяющийся код? Другими словами, возможно ли создать абстрактный класс, от которого я мог бы унаследовать оба этих подкласса, а затем просто переопределить методы, которые отличаются между ними?

Что я знаю до сих пор:

  • Я знаю, что Objective-C не поддерживает множественное наследование (наследование от двух или более классов)
  • Я знаю, что могу добавить общие методы, используя Categories, но я не думаю, что это решает переопределение методов init или добавление приватных свойств

5 ответов

Решение

Основываясь на ответе Амина, вот как вы могли бы это сделать:

Шаг 1. Создайте TextSurrogateHosting протокол, который будет содержать все методы вашего UITextField а также UITextView подклассы, к которым вам нужно получить доступ из методов, которые вы хотите добавить в оба подкласса. Это может быть, например, text а также setText: метод, так что ваши методы могут получить доступ и установить текст либо текстового поля или текстового представления. Это может выглядеть так:

SPWKTextSurrogateHosting.h

#import <Foundation/Foundation.h>

@protocol SPWKTextSurrogateHosting <NSObject>

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

@end

Шаг 2: Создайте TextSurrogate класс, который содержит все методы, которые вы хотите поделиться между UITextField и UITextView подклассы. Добавьте эти методы в протокол, чтобы мы могли использовать завершение кода в 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

Шаг 3: Создайте свой 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

Шаг 4: Создайте свой 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

То, что вы хотите, это миксин. Это не поддерживается в Objective-C. Категории не являются миксинами, потому что они добавляют API к одному классу, а не ко многим (>1) классам. Использование категорий, что, как вы сказали, невозможно по многим причинам, не поможет вам.

Обычный способ решить эту проблему - создать вспомогательный класс, содержащий дополнительный код, и использовать его в обоих классах.

Тогда вы обнаружите, что печатаете

[myUITextViewSubclass.helper doSomething]

вместо

[myUITextViewSubclass doSomething]

Если это действительно проблема, вы можете решить ее с помощью прямых вызовов. Просто напишите комментарий.

Вы можете использовать макрос препроцессора, но он подвержен ошибкам.

Нет, это невозможно.

Самое близкое, чего вы могли бы достичь - это вручную добавить функциональность в UITextView чтобы подражать UITextField, Очевидным недостатком является то, что вы должны делать все это вручную, с вашим собственным кодом.

Черты или миксины не поддерживаются Objective-C, у вас есть только встроенная опция категорий. Но, к счастью, в Objective-C Runtime есть почти все инструменты для реализации собственной идеи смешивания или черт с добавлением методов и свойств в ваш класс во время выполнения. Вы можете прочитать больше о возможностях, которые Objective-C Runtime предоставляет для вас на веб - сайте документации Apple, Objective-C Runtime Docs

Идея заключается в следующем:

1) Вы можете создать протокол Objective-C (Mixin), в котором вы будете объявлять свойства и методы.

2) Затем вы создаете класс (реализация Mixin), который будет реализовывать методы из этого протокола.

3) Вы делаете свой некоторый класс, в котором вы хотите предоставить возможность композиции с миксинами, чтобы соответствовать этому протоколу (Mixin).

4) Когда ваше приложение запускается, вы добавляете во время выполнения Objective C все реализации из класса (реализации Mixin) и свойства, объявленные в (Mixin), в ваш класс.

5) вуаля:)

Или вы можете использовать несколько готовых проектов с открытым исходным кодом, таких как " Alchemiq "

Другие вопросы по тегам