# Decoupling iOS Views from Concrete Model Types

Language: en
Canonical: [Decoupling iOS Views from Concrete Model Types](<https://allluckly.cn/en/journal/2016-07-28-decoupling-ios-views-from-concrete-model-types/>)
中文：[中文 HTML](<https://allluckly.cn/%E6%8A%95%E7%A8%BF/tougao56>)
English: [English HTML](<https://allluckly.cn/en/journal/2016-07-28-decoupling-ios-views-from-concrete-model-types/>)
Published: 2016-07-28T00:00:00+08:00
Modified: 2026-10-23T10:52:08+08:00
Author: Sindri

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 original](<http://www.jianshu.com/p/2b2290fa0beb>)

English translation of the [Chinese original](<https://allluckly.cn/%E6%8A%95%E7%A8%BF/tougao56>), translated on 2026-10-23. The article body uses automated translation; app descriptions, historical reading notes and video subtitles receive separate editorial checks. Original publication dates, attribution, code and version notes are retained.

**Before you read** This technical note dates from 2015–2017. The examples have not been rerun as part of this archive update. Read API, dependency and tool details in the context of the original publication date.

`去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](<https://allluckly.cn/en/>)  
 Source: [Sindri’s Notes](<http://www.jianshu.com/p/2b2290fa0beb>)

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:

![1](<https://allluckly.cn/images/blog/tuogao/tougao56/1.jpg>)

For more detailed theory, read Casa’s [Removing models and data objects](<http://casatwy.com/OOP_nomodel.html>). 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:

![1](<https://allluckly.cn/images/blog/tuogao/tougao56/2.jpg>)

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 `cell` are highly similar and can become similar display data.*

> **three kinds of `cell` can share one wrapper but correspond to different data objects.** *This concerns `cell` and data-object integration. If `cell` changes later, will the original data object still apply?*

> **The controller must coordinate different `cell` and `model`, creating excessive coupling.** *This is common. A factory can usually separate coordination logic, but this article uses `去model` approach.*

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](<http://www.jianshu.com/p/4847c9a1e19b>)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: ![1](<https://allluckly.cn/images/blog/tuogao/tougao56/3.jpg>)

```
@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:

![1](<https://allluckly.cn/images/blog/tuogao/tougao56/4.jpg>)

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:

![1](<https://allluckly.cn/images/blog/tuogao/tougao56/5.jpg>)

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](<https://allluckly.cn/en/journal/2016-06-12-adding-a-launch-advertisement-to-an-ios-app/>)

