[英]Declare conformance to objective-c protocol with a category and implement it with another category
[英]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.h
和EKEnumerable.m
:
定義描述所需行為的協議。 對於要提供的方法實現,將它們聲明為@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:
instance方法 將其添加到您的實現的頂部:
@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.