OC中基类强制要求子类实现相应方法

2020-06-08  本文已影响0人  肠粉白粥_Hoben

在最近的需求中,为了方便后续拓展,将相应的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里面实现较好。

上一篇 下一篇

猜你喜欢

热点阅读