簡體   English   中英

為在Objective-C中實現特定協議的類創建類別?

[英]Creating a category for classes that implement a specific protocol in Objective-C?

簡短的問題描述

我可以使用類別擴展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:的新類別方法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協議)。

我真正想做的是使這三種方法僅適用於那些實現了writeText:方法的writeText: (即那些實現我設置的WritableView協議的writeText: )。 理想情況下,我會在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行進行編譯。 沒錯,它不會在運行時導致異常,但也不會導致任何事情發生,這也是我也試圖避免的另一種錯誤。

類似的問題沒有給出令人滿意的答案:

這聽起來像你所追求的是一個混合:定義一系列形成你想要的行為的方法,然后將該行為只添加到需要它的類集。

這是我在我的項目EnumeratorKit中取得巨大成功的策略,它將Ruby樣式的塊枚舉方法添加​​到內置的Cocoa集合類中(特別是EKEnumerable.hEKEnumerable.m

  1. 定義描述所需行為的協議。 對於要提供的方法實現,將它們聲明為@optional

     @protocol WritableView <NSObject> - (void)writeText:(NSString *)text; @optional - (void)setTextToDomainOfUrl:(NSString *)text; - (void)setTextToIntegerValue:(NSInteger)value; - (void)setCapitalizedText:(NSString *)text; @end 
  2. 創建一個符合該協議的類,並實現所有可選方法:

     @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 
  3. 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 ):

  1. 采用WritableView協議
  2. 實現所需的writeText: instance方法
  3. 將其添加到您的實現的頂部:

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

希望這可以幫助。 我發現這是一種非常有效的方法來實現跨領域的關注點,而無需在多個類別之間復制和粘貼代碼。

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時,協議WritableView對於Objective-不再可見C)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM