移动开发精华

iOS - Controller 瘦身简析

2017-08-12  本文已影响56人  豆志昂扬

《iOS 常见架构一览》中提到, 由于iOS 开发模式中没有在设计上规范的子组件所在位置,若使用不当,会导致UIViewController过于臃肿。 下面聊聊几种给Controller瘦身的办法。

KeyBoard Manager

很多页面都需要在键盘状态改变后更新视图的状态,这是一个经典的非核心代码堆砌在Controller的样例。我们可以尝试定义一个简单的键盘事件管理类实现和Controller的分离。

@implementation EDKeyboardManager : NSObject

- (instancetype)initWithTableView:(UITableView *)tableView {
    self = [super init];
    if (!self) return nil;
    _tableView = tableView;
    return self;
}

- (void)addObservingKeyboard {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
}

- (void)removeObservingKeyboard {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidHideNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
}

- (void)keyboardWillShow:(NSNotification *)note {
    CGRect keyboardRect = [[note.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];

    UIEdgeInsets contentInsets = UIEdgeInsetsMake(self.tableView.contentInset.top, 0.0f, CGRectGetHeight(keyboardRect), 0.0f);
    self.tableView.contentInset = contentInsets;
    self.tableView.scrollIndicatorInsets = contentInsets;
}

- (void)keyboardDidHide:(NSNotification *)note {
    UIEdgeInsets contentInset = UIEdgeInsetsMake(self.tableView.contentInset.top, 0.0f, self.oldBottomContentInset, 0.0f);
    self.tableView.contentInset = contentInset;
    self.tableView.scrollIndicatorInsets = contentInset;
}

@end

这里也可以使用一些成熟的类库,如iOS开发第三方库一 IQKeyboardManager

View Controller 组合

如果一个View Controller由一些逻辑独立,可复用的视图组成,可以尝试通过分割成多个子View Controller, 而多个子View Controller通过iOS
暴露的原生API组装成目标视图。

//在ViewController 中添加其他UIViewController,currentVC是一个UIViewController变量,存储当前显示的viewcontroller
 FirstVC * first = [[FirstVC alloc] init];
[self addChildViewController:first];
//addChildViewController 会调用 [child willMoveToParentViewController:self] 方法,但是不会调用 didMoveToParentViewController:方法,官方建议显示调用
[first didMoveToParentViewController:self];
[first.view setFrame:CGRectMake(0, CGRectGetMaxY(myScrollView.frame), width, height-CGRectGetHeight(myScrollView.frame))];
[self.view addSubview:currentVC.view];

SecondVC * second = [[SecondVC alloc] init];
[self addChildViewController:second];
[self didMoveToParentViewController:second];
[second.view setFrame:CGRectMake(0,CGRectGetMaxY(myScrollView.frame), width, height-CGRectGetHeight(myScrollView.frame))];

MVP 模式

在MVP模式中, Presenter 通过桥接 Model和 View, 使View Controller不再直接操作Model, 从而组件之间耦合度更高,代码后续维护更容易。

class UserPresenter {
  //获取User数据
    private let userService:UserService
    weak private var userView : UserView?
     
    init(view:UserView){
        userView = view
    }
     
    func getUsers(){
        self.userView?.startLoading()
        userService.getUsers{ [weak self] users in
            self?.userView?.finishLoading()
            if(users.count == 0){
                self?.userView?.setEmptyUsers()
            }else{
                let mappedUsers = users.map{
                    return UserViewData(name: "\($0.firstName) \($0.lastName)", age: "\($0.age) years")
                }
                self?.userView?.setUsers(mappedUsers)
            }
             
        }
    }
}

MVVM模式

其实这里着重介绍的是数据绑定,而MVVM是通过数据绑定View和Model, 实现两者之间状态自动同步的典型。实现数据绑定的方式很多,KVO, KVC, ReactiveCocoa 等, 有兴趣的可以阅读文章MVVM和数据绑定

class ViewController: UIViewController {
    @IBOutlet private weak var userLabel: UILabel!
    private let viewModel: ViewModel
    private let disposeBag: DisposeBag

    private func bindToViewModel() {
        viewModel.myProperty
            .drive(userLabel.rx.text)
            .disposed(by: disposeBag)
    }
}

数据源

iOS开发者对于UITableView 和 UITableViewDataSource 应该不会陌生,对于一个相对复杂的UITableView,很有必要把UITableViewDataSource的实现和View Controller分离开来,同时也让复用有了可能。

class IceCreamListDataSource: NSObject, UITableViewDataSource
{
  let dataStore = IceCreamStore()
 
  // MARK: - Table view data source
 
  func numberOfSectionsInTableView(tableView: UITableView) -> Int
  {
    return 1
  }
 
  func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
  {
    return dataStore.allFlavors().count
  }
 
  func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
  {
    let flavor = dataStore.allFlavors()[indexPath.row]
    let cell = tableView.dequeueReusableCellWithIdentifier("IceCreamListCell", forIndexPath: indexPath)
    cell.textLabel?.text = flavor
    return cell
  }
}
 

最后

以上的种种策略,只是在开发者认为遇到上述问题时的解决策略,在决定是否重构代码之前,结合其可复用性,易用性和可维护性权衡利弊,防止走向"过分设计的"极端。

更多

获取更多内容请关注微信公众号豆志昂扬:

上一篇 下一篇

猜你喜欢

热点阅读