Swift & runtimeHistorical technical note
Decoupling iOS Views from Concrete Model Types
Uses several types of ticket data with different fields but similar presentation needs to explore a dictionary-backed approach. A protocol exposes common display properties, reducing cells' and controllers' dependence on specific models and clarifying where data conversion belongs.
Originally credited to Sindri; edited and archived by Bison.
Source listed in the originalOn this page
去model化 is an approach to framework design. The model here does not mean the architectural model layer. To quote Casa's original blog post:
Using models means using data objects; removing models means not using data objects.
Editor: Bison
Source: Sindri’s Notes
A common 去model化 approach stores data in dictionaries, then uses a reformer to convert it into information the View layer can display. The flow is:

For more detailed theory, read Casa’s Removing models and data objects. Building on Casa’s practice, this article uses another 去model化 implementation approach.
Background
I read that article long ago but had not tried the approach. A recent project finally gave me the opportunity: three very similar cell s corresponding to different data model s:

The display data needed by all three cell s includes:
- Product name
- When to use it
- Expiry date
- Background image
In addition, 优惠信息 is specific to the first and second types. This requirement currently presents three main problems:
The server uses substantially different field names for the three data-object types. Most apps face this problem, but these data objects have one important trait: their corresponding
cellare highly similar and can become similar display data.
three kinds of
cellcan share one wrapper but correspond to different data objects. This concernscelland data-object integration. Ifcellchanges later, will the original data object still apply?
The controller must coordinate different
cellandmodel, creating excessive coupling. This is common. A factory can usually separate coordination logic, but this article uses去modelapproach.
These problems can make later maintenance difficult, with small requirement changes causing large code changes. My solution defines two corresponding protocols between cell and model, allowing the controller to ignore their concrete types.
Implement
In my previous article,Thoughts on MVC architecture previously mentioned M layer’s business logic in model, although this article aims to 去model化, removing property objects while retaining its own processing logic. Here is my 去model化 protocol diagram and declared properties:

@protocol LXDTicketModelProtocol <NSObject>
@optional
@property (nonatomic, readonly) NSAttributedString * perferential;
@required
@property (nonatomic, readonly) NSString * backgroundImageName;
@property (nonatomic, readonly) NSString * goodName;
@property (nonatomic, readonly) NSString * effectCondition;
@property (nonatomic, readonly) NSString * deadline;
@property (nonatomic, readonly) LXDCellType type;
- (instancetype)initWithDict: (NSDictionary *)dict;
@end
@protocol KMCTicketCellProtocol<NSObject>
- (void)configurateCellWithModel: (id<LXDTicketModelProtocol>)model;
@end
For model objects sharing display behavior as in this example, declare a protocol with several readonly properties. Within the protocol's getter methods, the objects perform the 数据->展示 business logic, while the model itself simply retains dictionary data:

Taking LXDCouponTicketModel as an example, the protocol implementation is:
// h文件
@interface LXDCouponTicketModel: NSObject<LXDTicketModelProtocol>
@end
// m实现
@implementation LXDCouponTicketModel
{
NSDictionary * _dict;
}
- (NSString *)backgroundImageName
{
return ([_dict[@"overdue"] boolValue] ? @"coupon_overdue" : @"coupon_common");
}
- (NSAttributedString *)perferential
{
NSAttributedString * result = objc_getAssociatedObject(self, KMCPerferentialKey);
if (result) { return result; }
NSMutableAttributedString * attributedString = [[NSMutableAttributedString alloc] initWithString: @"¥" attributes: @{ NSFontAttributeName: [UIFont systemFontOfSize: 16] }];
[attributedString appendAttributedString: [[NSAttributedString alloc] initWithString: [NSString stringWithFormat: @"%g", [_dict[@"ticketMoney"] doubleValue]] attributes: @{ NSFontAttributeName: [UIFont boldSystemFontOfSize: 32] }]];
[attributedString addAttributes: @{ NSForegroundColorAttributeName: KMCCommonColor } range: NSMakeRange(0, attributedString.length)];
result = attributedString.copy;
objc_setAssociatedObject(self, KMCPerferentialKey, result, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return result;
}
- (NSString *)goodName
{
return [_dict[@"goodName"] stringValue];
}
- (NSString *)effectCondition
{
return [NSString stringWithFormat: @"· 满%lu元可用", [_dict[@"minLimitMoney"] unsignedIntegerValue]];;
}
- (NSString *)deadline
{
return [NSString stringWithFormat: @"· 兑换截止日期:%@", _dict[@"deadline"]];
}
- (LXDCellType)type
{
return LXDCellTypeCoupon;
}
- (instancetype)initWithDict: (NSDictionary *)dict
{
if (self = [super init]) {
_dict = dict;
}
return self;
}
Making all three data objects implement the protocol unifies their display output. A single encapsulated cell then need not know the concrete model type. It only implements the cell-configuration protocol method to obtain display data:
// h文件
@interface LXDTicketCell: UITableViewCell<LXDTicketCellProtocol>
@end
// m实现
#define LXDCommonColor [UIColor colorWithRed: 253/255. green: 99/255. blue: 99/255. alpha: 1]
@implementation LXDTicketCell
- (void)configurateWithModel: (id<LXDTicketModelProtocol>)model
{
UIView * goodInfoView = _goodNameLabel.superview;
if ([model type] != KMCTicketTypeConvert) {
[goodInfoView mas_updateConstraints: ^(MASConstraintMaker *make) {
make.left.equalTo(_perferentialLabel.mas_right).offset(10); }];
} else {
[goodInfoView mas_updateConstraints: ^(MASConstraintMaker *make) {
make.left.equalTo(_backgroundImageView.mas_left).offset(18); }];
}
[_use setTitleColor: LXDCommonColor forState: UIControlStateNormal];
_backgroundImageView.image = [UIImage imageNamed: [model backgroundImageName]];
_perferentialLabel.attributedText = [model perferential];
_effectConditionLabel.text = [model effectCondition];
_goodNameLabel.text = [model goodName];
_deadlineLabel.text = [model deadline];
[_effectConditionLabel sizeToFit];
[_goodNameLabel sizeToFit];
[_deadlineLabel sizeToFit];
}
@end
This resolves the first two problems: the protocol unifies display behavior, so the model need not store multiple property objects. It retrieves dictionary data when appropriate and converts it for presentation. The cell is no longer constrained by the passed parameter type; it calls protocol methods to obtain the required data. The final issue—controller coordination—then becomes simpler:
// m实现
@interface LXDTicketViewController ()
@property (nonatomic, strong) NSMutableArray< id<LXDTicketModelProtocol> > * couponTickets;
@property (nonatomic, strong) NSMutableArray< id<LXDTicketModelProtocol> > * discountTickets;
@property (nonatomic, strong) NSMutableArray< id<LXDTicketModelProtocol> > * convertTickets;
@end
@implementation LXDTicketViewController
#pragma mark - UITableViewDataSource
- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: KMCTicketCommonCellIdentifier];
if ([cell conformsToProtocol: @protocol(LXDTicketCellProtocol)]) {
[(id<LXDTicketCellProtocol>)cell configurateCellWithModel: [self modelWithIndexPath: indexPath]];
}
return cell;
}
#pragma mark - Data Generator
- (id<LXDTicketModelProtocol>)modelWithIndexPath: (NSIndexPath *)indexPath
{
return self.currentModelSet[indexPath.row];
}
- (NSMutableArray< id<LXDTicketModelProtocol> > *)currentModelSet
{
switch (_ticketType) {
case KMCTicketTypeCoupon:
return _couponTickets;
case KMCTicketTypeDiscount:
return _discountTickets;
case KMCTicketTypeConvert:
return _convertTickets;
}
}
@end
When cell and model communicate through protocols, the controller's stored data sources can also ignore concrete types. Generic declarations allow multiple sources, leaving the controller only to select a source as the state machine changes. Although their types are unified, different data objects still need to be created from server json and placed into those sources. Keeping that business logic in the controller would not achieve loose coupling, so introduce a Helper intermediary:
// h文件
@interface LXDTicketDataHelper: NSObject
+ (void)anaylseJSON: (NSString *)JSON complete: (void(^)(NSMutableArray< id<LXDTicketModelProtocol> > *)models);
@end
// m实现
#import "LXDCouponTicketModel.h"
#import "LXDConvertTicketModel.h"
#import "LXDDiscountTicketModel.h"
@implementation LXDTicketDataHelper
+ (void)anaylseJSON: (NSString *)JSON complete: (void(^)(NSMutableArray< id<LXDTicketModelProtocol> > *)models)
{
NSParameterAssert(JSON);
NSParameterAssert(complete);
[LXDQueue executeInGlobalQueue: ^{
Class ModelCls = NULL;
NSDictionary * jsonDict = [NSDictionary dictionaryWithJSON: JSON];
NSMutableArray< id<LXDTicketModelProtocol> > * results = @[].mutableCopy;
// 使用switch简单工厂,如果case太多时,使用继承关系的工厂会更好
switch ((LXDModelType)[jsonDict[@"modelType"] integerValue]) {
case LXDModelTypeCoupon:
ModelCls = [KXDCouponTicketModel class];
break;
case LXDModelTypeConvert:
ModelCls = [LXDConvertTicketModel class];
break;
case LXDModelTypeDiscount:
ModelCls = [LXDDiscountTicketModel class];
break;
}
for (NSDictionary * dataDict in jsonDict[@"data"]) {
id item = [(id<LXDTicketModelProtocol>)[ModelCls alloc] initWithDict: dataDict];
[result addObject: item];
}
[LXDQueue executeInMainQueue: ^{
complete(result);
}];
}];
}
@end
// m实现
#import "KMCNetworkHelper.h"
@implementation LXDTicketViewController
- (void)requestTickets
{
// get request parameters include 'url' and 'parameters'
[LXDNetworkManager POST: PATH(url) parameters: parameters
complete: ^(NSString * JSON, NSError * error) {
// error check
[LXDTicketDataHelper analyseJSON: JSON complete: ^(NSMutableArray * models) {
[self.currentModelSet addObjectsFromArray: models];
}];
}];
}
@end
After 去model化, the project's overall business flow is approximately:

The main advantage is that controllers and views no longer depend on a concrete model type. If model fields change in server json, update the corresponding ModelProtocol implementation. Even if a later version adds 现金券 or other ticket types, the change only requires an appropriate factory at the Helper stage.
Closing notes
去model化 is an effective, quick way to loosen coupling, but it is no 万能药. In this demo, its main motivation is that several cell s have much in common while the model property fields differ completely. The Helper can also become bloated as more models are added, so use the approach carefully.
A good project architecture evolves with requirements; there is no universally best design. Trying different approaches broadens our understanding and develops our judgment about framework design.
Recommended reading: Implement an iOS launch-screen advertisement in minutes