考虑下面的Objective-C协议声明,它只需要类方法:
@protocol TDWMethoding<NSObject>
+ (void)foo;
+ (void)bar;
@end
假设我需要从方法返回符合此协议的Class
的实例,我该如何指定返回类型?
- (nullable /*return-type*/)instantiateMethoding {
Class instance = ... // some implementation
if ([instance conformsToProtocol:@protocol(TDWMethoding)]) {
return instance;
}
return nil;
}
关于如何表达/*return-type*/
,我考虑了许多工作选项,但每个选项都有自己的缺点:
Class
-这样它不会暴露一致性。这是哪种Class
?它是做什么的?它完全符合协议吗?Class<TDWMethoding>
-这看起来像是一个可行的解决方案,甚至被其他开发人员建议了几次(这里和这里),但我个人觉得它不一致和误导:当我们有一个形式Type<Protocol> *instance
的变量,它通常意味着协议类方法应该发送到实例的类([[instance class] foo]
)而不是实例本身([instance foo]
);id<TDWMethoding>
并返回类的实例-这是一致的,但它要求我实例化类,这是冗余的,并且阻止我隐藏符合NS_UNAVAILABLE
宏协议的实用程序类的构造函数。
是否有更好的语义来表达这样的返回类型?
Class<TDWMethoding>
是正确的。这并不矛盾。当某些东西的类型是Class
时,您可以向它发送类方法。当某些东西是一个实例,并且你想要发送给类时,你访问它的-class
。
也就是说,这看起来很奇怪,很可能意味着您过度使用了Class方法。你应该认真考虑sharedInstance
是否是一个更好的模型。
但是如果您想要识别类型,Class<TDWMethoding>
是正确的,尽管id
可能更常见,如如何强制转换类对象以符合协议中所讨论的。
在深入研究了Objective-C编程语言文档之后,我实际上找到了这种场景的确切答案:
协议不能用于类型化对象。只有实例可以静态类型化为协议,就像只有实例可以静态类型化为类一样。(然而,在运行时,类和实例都响应
conformsToProtocol:
消息。)
这意味着它不被支持我应该用不同的方式实现它。(例如,使用单例模式,正如Rob的回答所建议的那样)
解决方案是根本不使用这些协议。为什么?因为它不灵活
应该是:
@protocol TDWMethoding
- (void)foo;
- (void)bar;
@end
然后你将能够做任何你想做的,例如,你将能够为你的类创建包装器,这将实现你的协议。
@interface TDWMethodingModel<TDWMethoding>
@property (nonatomic, readonly) void (^fooCaller)(void);
@property (nonatomic, readonly) void (^barCaller)(void);
- (instancetype)initWithFooCaller:(void (^)(void))fooCaller barCaller:(void (^)(void))barCaller NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
@end
@implementation TDWMethodingModel
- (instancetype)initWithFooCaller:(void (^)(void))fooCaller barCaller:(void (^)(void))barCaller {
self = [super init];
if (nil == self) {
return nil;
}
_fooCaller = fooCaller;
_barCaller = barCaller;
return self;
}
- (void)foo {
self.fooCaller();
}
- (void)bar {
self.barCaller();
}
@end
:
- (id<TDWMethoding>)instantiateMethoding
{
static id<TDWMethoding> methoding;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
methoding = [[TDWMethodingModel alloc] initWithFooCaller:^{
[SomeClass foo];
} barCaller:^{
[SomeClass bar];
}];
});
return methoding;
}