OC中基类强制要求子类实现相应方法
在最近的需求中,为了方便后续拓展,将相应的View和Model都抽离成了比较通用的基类,并在collectionView
写好了通用的读取和加载方法,后续如果有扩展业务只需继承相应的基类,并且实现预设好的方法即可。
for (HobenBaseModel *model in modelArray) {
BOOL hasInitCurrentView = YES;
if (!self.viewDict) {
self.viewDict = [NSMutableDictionary dictionary];
}
id view = [self.viewDict safeObjectForKey:model.type];
if (!view) {
view = [activityModel createView];
hasInitCurrentView = NO;
}
if ([view respondsToSelector:@selector(setModel:)]) {
[view performSelector:@selector(setModel:) withObject:model];
}
[self.viewDict safeSetObject:view forKey:model.type];
}
但是作为基类,如何引导后续开发者实现必须实现的方法呢?写注释可以,但是如果漏看注释就会导致一些问题,因此需要基类强制要求子类实现对应的方法。
比如,现在BaseModel有一个强制要求子Model实现的createView
方法,用于对应View的初始化,后续开发只需在子Model实现该方法即可。那么我们可以这样做:
@interface HobenBaseModel : NSObject
@property (nonatomic, strong) NSString *type;
- (__kindof HobenBaseView *)createView;
@end
@implementation HobenBaseModel
- (__kindof HobenBaseView *)createView {
NSAssert(false, @"继承该类需要声明createView方法");
return [[HobenBaseView alloc] init];
}
@end
这样来做,如果子Model在implementation
没有实现createView
方法的话,就会进入断言:Thread 1: Exception: "继承该类需要声明createView方法"
,提醒开发者实现该方法。
子类Model必须实现对应的createView方法:
@implementation HobenSonModel
- (HobenSonView *)createView {
HobenSonView *view = [[HobenSonView alloc] init];
return view;
}
@end
同样地,BaseView也需要提醒子类,如果需要设置数据源的话,就得命名为model:
@interface HobenBaseView : UIView <NSCopying>
// 需要命名为model且定义@synthesize model = _model;
@property (strong, nonatomic) __kindof HobenBaseModel *model;
@end
@implementation HobenBaseView
@dynamic model;
- (id)copyWithZone:(NSZone *)zone {
id view = [[[self class] alloc] init];
if ([view respondsToSelector:@selector(setModel:)]) {
if ([self respondsToSelector:@selector(model)]) {
id model = [self performSelector:@selector(model)];
[view performSelector:@selector(setModel:) withObject:model];
}
}
return view;
}
@end
这样一来,子View实现起来就得这样写:
@interface HobenSonView ()
@property (nonatomic, strong) HobenSonModel *model;
@end
@implementation HobenSonView
@synthesize model = _model;
- (void)setModel:(HobenSonModel *)model {
_model = model;
// ...
}
@end
如果子View不实现@synthesize model = _model;
和setter
的话跑起来就会报错:Thread 1: Exception: "-[HobenSonView model]: unrecognized selector sent to instance 0x7fa6911c1750"
这是因为我在父类里面声明了这个model是@dynamic的,即不会自动生成getter和setter,需要自行实现,而子类实现的@synthesize的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。
这样一来,不规范的写法就会被编译器报错了,后续加的子类即插即用,不需要修改父类的内容,而是需要遵循父类引导的准则来写,应该是比较符合设计原则的。
更新于8.19
后续有大佬同事跟进了这个需求,发现写在collectionView
里面的代码还是太过于复杂,而且子view和model的命名、相应方法和继承均受到限制,对于后续拓展不太好,故需要整理一下代码的放置位置。
他的做法如下:
将setModel:
方法放在Model内,即
@implementation HobenSonModel
- (HobenSonView *)createView {
HobenSonView *view = [[HobenSonView alloc] init];
[view setModel:self];
return view;
}
@end
collectionView
获取View的方法如下:
for (HobenBaseModel *model in modelArray) {
BOOL hasInitCurrentView = YES;
if (!self.viewDict) {
self.viewDict = [NSMutableDictionary dictionary];
}
id view = [self.viewDict safeObjectForKey:model.type];
if (!view) {
if ([model isKindOfClass:[HobenSonModel class]]) {
view = [model createView];
}
hasInitCurrentView = NO;
}
[self.viewDict safeSetObject:view forKey:model.type];
}
另外,由于旧版本不能显示新版本的视图,所以需要加个标识符,代表是否需要处理后端返回的字段,这个字段最好也是放在Model里面,用Protocol处理:
@protocol HobenSubViewProtocol <NSObject>
// 数据 model 对应的 activity_type 字段(banner 分类)取值
+ (NSString *)subView_activityType;
@end
@interface HobenSonModel : NSObject <HobenSubViewProtocol>
- (HobenSonModel *)createView;
@end
@implementation HobenSonModel
- (HobenSonView *)createView {
HobenSonView *view = [[HobenSonView alloc] init];
[view setModel:self];
return view;
}
#pragma mark - HobenSubViewProtocol
+ (NSString *)subView_activityType {
return @"sub_view1";
}
@end
解析数据也只解析符合的
NSMutableArray<id<HobenSubViewProtocol>> *tmpArr = [NSMutableArray array];
[data enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSString *activity_type = [obj objectForKey:@"activity_type"];
// 便于后续扩展,只处理 activity_type 成功匹配的数据
if ([activity_type isEqualToString:[HobenSonModel subView_activityType]]) {
id model = [HobenSonModel fromJSONDictionary:obj];
[tmpArr addObject:model];
}
}];
如果后续要加新业务,则可以自定义Model、View,实现Protocol和对应方法,并在解析数据、collectionView上面加相关判断就行了,其实工作量也不是很大,感觉也还行,最主要还是对应业务放在对应Model、View里面实现较好。