iOS MVVM + RAC 簡單實例講解

在MVVM 中搀庶,view 和 view controller正式聯(lián)系在一起,我們把它們視為一個組件
view 和 view controller 都不能直接引用model党晋,而是引用視圖模型(viewModel)
viewModel 是一個放置用戶輸入驗證邏輯都弹,視圖顯示邏輯,發(fā)起網(wǎng)絡(luò)請求和其他代碼的地方
view 引用viewModel 肘交,但反過來不行(即不要在viewModel中引入#import UIKit.h,任何視圖本身的引用都不應(yīng)該放在viewModel中)(PS:基本要求扑馁,必須滿足)
viewModel 引用model,但反過來不行


本文里用MVVM模式凉驻,以及RAC雙向數(shù)據(jù)綁定腻要,來講解“未讀消息”的標記這個小栗子(小紅點的隱藏與顯示)。效果圖


思路講解:
viewModel 綁定整個視圖涝登,通過viewModel獲得列表model后雄家,創(chuàng)建cellModel(cell的viewModel),每個cell綁定cellModel胀滚,cell根據(jù)cellModel變化而變化

結(jié)構(gòu):
image.png

上代碼
viewController:viewWillAppear都去獲取第一條消息的ID

@interface TJMMineViewController ()<TJMMineHeaderDelegate, UITableViewDelegate, UITableViewDataSource>

@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (nonatomic, strong) TJMMineHeader *mineHeader;
@property (strong, nonatomic) IBOutlet TJMMineViewModel *viewModel;

@end

@implementation TJMMineViewController
#pragma  mark -  view life cycle
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [self configViews];
    [self configTableview];
}
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [_viewModel getSignalMessage];//viewWillAppear都去獲取第一條消息的ID
}

#pragma  mark - configViews
- (void)configViews {
    [self setTitle:@"我 的" fontSize:18 colorHexValue:0x73d7ff];   
    [self setRightNaviItemWithImageName:@"mine_setting" orTitle:nil titleColorHexValue:0 fontSize:0];
}

- (void)configTableview {
    self.tableView.tableHeaderView = self.mineHeader;
    [_tableView registerNib:[UINib nibWithNibName:@"TJMMineCell" bundle:nil] forCellReuseIdentifier:cMineCell];
}

#pragma  mark - UITableViewDelegate, UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.viewModel.dataSource.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    TJMMineCell *cell = [tableView dequeueReusableCellWithIdentifier:cMineCell forIndexPath:indexPath];
    cell.cellModel = self.viewModel.dataSource[indexPath.row];//綁定cellModel
    return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return self.viewModel.rowHeight;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [TJMUnloginView showWithLoginResult:^{
        TJMMineCellModel *cellModel = self.viewModel.dataSource[indexPath.row];
        [self performSegueWithIdentifier:cellModel.segueId sender:nil];
    }];
}

model.h

@interface TJMMineModel : NSObject

@property (nonatomic, copy) NSString *imageName;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *segueId;
@property (nonatomic, assign) BOOL isMessageItem;//如果不是消息那一行則為NO

@end

viewModel.h

@interface TJMMineViewModel : TJMListBaseViewModel

@property (nonatomic, assign) CGFloat rowHeight;
/**獲取消息列表的第一條數(shù)據(jù)趟济,并對比*/
- (void)getSignalMessage;
@end

viewModel.m
1.得到model array 后乱投,用RAC map 方法 將 [model] 轉(zhuǎn)換為包含model的[cellModel]

    NSArray *mapArr = [[array.rac_sequence map:^id _Nullable(id  _Nullable value) {
        TJMMineCellModel *cellModel = [[TJMMineCellModel alloc]init];
        cellModel.model = value;
        return cellModel;
    }] array];

2.獲取消息列表的第一條數(shù)據(jù),與本地數(shù)據(jù)對比顷编,如果不一樣則表示有新消息戚炫,想cellModel的 isExitUnreadMsg 屬性 標記為YES,反之標記為NO媳纬。(此時cell 已綁定了 cellModel 所以 isExitUnreadMsg 變化 cell 也會跟著變化)

NSString *unreadId = [[NSUserDefaults standardUserDefaults] stringForKey:kUnreadMessageId];

TJMMessageModel *model = [TJMMessageModel mj_objectWithKeyValues:successObj[@"data"]];
            TJMMessageRecordModel *record = model.records.firstObject;
            if (!unreadId) {
                [[NSUserDefaults standardUserDefaults] setObject:record.ID forKey:kUnreadMessageId];
            } else {
                 TJMMineCellModel *msgCellModel = self.dataSource[2];
                if (![unreadId isEqualToString:record.ID]) {
                    msgCellModel.isExitUnreadMsg = YES;
                } else {
                    msgCellModel.isExitUnreadMsg = NO;
                }
            }

完整代碼

- (id)init {
    if (self = [super init]) {
        //獲取Model
        NSArray *array = [TJMMineModel mj_objectArrayWithFile:[[NSBundle mainBundle] pathForResource:@"TJMMineModel" ofType:@"plist"]];
    NSArray *mapArr = [[array.rac_sequence map:^id _Nullable(id  _Nullable value) {
        TJMMineCellModel *cellModel = [[TJMMineCellModel alloc]init];
        cellModel.model = value;
        return cellModel;
    }] array];
    self.dataSource = [NSMutableArray arrayWithArray:mapArr];
    }
    return self;
}

#pragma  mark - lazy loading

- (CGFloat)rowHeight {
    if (!_rowHeight) {
        self.rowHeight = 70.f * TJMHeightRatio;
    }
    return _rowHeight;
}

- (void)getSignalMessage {
    TJMTokenModel *tokenModel = [TJMSandBoxManager getTokenModel];
    if (tokenModel) {
        NSString *unreadId = [[NSUserDefaults standardUserDefaults] stringForKey:kUnreadMessageId];
        NSDictionary *parameters = @{@"currentPage" : @1,
                                     @"pageSize" : @1,
                                     @"customerId" : tokenModel.userId
                                     };
        [TJMNetworkingManager GET:TJMApiBasicAddress(TJMGetMessage) isNeedToken:YES parameters:parameters progress:nil success:^(id successObj, NSString *msg) {
            TJMMessageModel *model = [TJMMessageModel mj_objectWithKeyValues:successObj[@"data"]];
            TJMMessageRecordModel *record = model.records.firstObject;
            if (!unreadId) {
                [[NSUserDefaults standardUserDefaults] setObject:record.ID forKey:kUnreadMessageId];
            } else {
                 TJMMineCellModel *msgCellModel = self.dataSource[2];
                if (![unreadId isEqualToString:record.ID]) {
                    msgCellModel.isExitUnreadMsg = YES;
                } else {
                    msgCellModel.isExitUnreadMsg = NO;
                }
            }
        } failure:^(NSInteger code, NSString *failString) {
            
        }];
    }
}

cell.h

@interface TJMMineCell : UITableViewCell

@property (weak, nonatomic) IBOutlet UIImageView *iconImageView;
@property (weak, nonatomic) IBOutlet TJMLabel *titleLabel;
@property (weak, nonatomic) IBOutlet UIView *dotView;
@property (nonatomic, strong) TJMMineCellModel *cellModel;

@end

cell.m
這里只說明消息那個小紅點的綁定双肤,
先看combineLatestWith:reduce: 方法,綁定兩個信號钮惠,判斷狀態(tài)返回一個新的信號茅糜;
所以dotView 的隱藏與否,取決于這個signal 是否滿足素挽,“reduce”后的判斷條件(即這個cell既要是“我的消息”---->isMessageItem == YES蔑赘,且還要有未讀消息isExitUnreadMsg == YES,才會顯示小紅點)

if (isMessageItem.boolValue) {
            if (isExitUnreadMsg.boolValue) {
                return @0;
            } else {
                return @1;
            }
        } else {
            return @1;
        }

完整代碼

- (void)setCellModel:(TJMMineCellModel *)cellModel {
    if ([cellModel isEqual:_cellModel]) return;
    _cellModel = cellModel;
    RAC(_iconImageView, image) = RACObserve(cellModel, image);
    RAC(_titleLabel, text) = RACObserve(cellModel, title);
    RAC(_dotView, hidden) = [RACSignal combineLatest:@[RACObserve(cellModel, isMessageItem), RACObserve(cellModel, isExitUnreadMsg)] reduce:^id (NSNumber *isMessageItem, NSNumber *isExitUnreadMsg){
        if (isMessageItem.boolValue) {
            if (isExitUnreadMsg.boolValue) {
                return @0;
            } else {
                return @1;
            }
        } else {
            return @1;
        }
    }];
}

cellModel.h

@interface TJMMineCellModel : TJMBaseViewModel

@property (nonatomic, strong) TJMMineModel *model;

@property (nonatomic, copy) NSString *imageName;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *segueId;
@property (nonatomic, assign) BOOL isMessageItem;

@property (nonatomic, assign) BOOL isExitUnreadMsg;
@property (nonatomic, strong) UIImage *image;

@end

cellModel.m 就是綁定model

- (void)setModel:(TJMMineModel *)model {
    if ([model isEqual:_model]) return;
    _model = model;
    RAC(self, title) = RACObserve(_model, title);
    RAC(self, isMessageItem) = RACObserve(_model, isMessageItem);
    @weakify(self);
    [RACObserve(_model, imageName) subscribeNext:^(NSString *  _Nullable x) {
        @strongify(self);
        self.image = [UIImage imageNamed:x];
    }];
    RAC(self, segueId) = RACObserve(_model, segueId);
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末预明,一起剝皮案震驚了整個濱河市缩赛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贮庞,老刑警劉巖峦筒,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異窗慎,居然都是意外死亡物喷,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門遮斥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來峦失,“玉大人,你說我怎么就攤上這事术吗∥炯” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵较屿,是天一觀的道長隧魄。 經(jīng)常有香客問我,道長隘蝎,這世上最難降的妖魔是什么购啄? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮嘱么,結(jié)果婚禮上狮含,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好几迄,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布蔚龙。 她就那樣靜靜地躺著,像睡著了一般映胁。 火紅的嫁衣襯著肌膚如雪木羹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天屿愚,我揣著相機與錄音汇跨,去河邊找鬼。 笑死妆距,一個胖子當(dāng)著我的面吹牛穷遂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播娱据,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蚪黑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了中剩?” 一聲冷哼從身側(cè)響起忌穿,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎结啼,沒想到半個月后掠剑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡郊愧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年朴译,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片属铁。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡眠寿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出焦蘑,到底是詐尸還是另有隱情盯拱,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布例嘱,位于F島的核電站狡逢,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏拼卵。R本人自食惡果不足惜奢浑,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望间学。 院中可真熱鬧,春花似錦、人聲如沸低葫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嘿悬。三九已至实柠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間善涨,已是汗流浹背窒盐。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留钢拧,地道東北人蟹漓。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像源内,于是被迫代替她去往敵國和親葡粒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容