iOS - Controller 瘦身简析
在《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
}
}
最后
以上的种种策略,只是在开发者认为遇到上述问题时的解决策略,在决定是否重构代码之前,结合其可复用性,易用性和可维护性权衡利弊,防止走向"过分设计的"极端。
更多
获取更多内容请关注微信公众号豆志昂扬:
- 直接添加公众号豆志昂扬;
- 微信扫描下图二维码;
![](https://img.haomeiwen.com/i1411204/028e2a1c709bbbac.jpg)