驯服iOS中的大量控制器(第1部分)

更新:第2部分现在可用

无论您的iOS应用多么简单,您仍然必须遵循MVC软件架构模式。 MVC模式将应用程序的职责分散在模型,视图和控制器之间。 不幸的是,大多数时候,责任最终落在了控制者的肩膀上。 这种做法导致了所谓的大规模控制器

我在IndieDevStock上介绍了“在iOS中驯服大量控制器”。 如果您想观看我的会议视频,请购买远程通行证。

大型控制器是不遵守单一职责原理的视图控制器。 这些控制器可能正在访问数据,调用Web服务,创建UI元素以及执行与控制器没有直接关系的其他任务。 这导致了技术债务和维护方面的噩梦。

这篇文章的主要重点是向您展示一些可用于驯服大型控制器的技术。 我们将从一个非常简单的应用程序“ Grocry ”开始。 杂货应用程序负责跟踪购物清单。 Grocry应用程序的界面如下所示:

即使该应用程序非常简单,但指定的控制器中的代码仍然很繁重。 这是段中的代码片段。

 覆盖func viewDidLoad(){ 
  super.viewDidLoad() 
  initializeCoreDataManager() 
  //获取请求 
  let request = NSFetchRequest(entityName:“ ShoppingList”) 
  request.sortDescriptors = [NSSortDescriptor(key:“ title”,ascending:true)] 
  self.fetchedResultsController = NSFetchedResultsController(fetchRequest:请求,managedObjectContext:self.managedObjectContext,sectionNameKeyPath:nil,cacheName:nil) 
  self.fetchedResultsController.delegate =自我 
 尝试!  self.fetchedResultsController.performFetch() 
  } 

下面实现了负责初始化CoreData的initializeCoreDataManager:

 私人函式initializeCoreDataManager(){ 
 警卫让modelURL = NSBundle.mainBundle()。URLForResource(“ GrocryDataModel”,withExtension:“ momd”)else { 
  fatalError(“未找到GrocryDataModel”) 
  } 
 警卫队,让ManagedObjectModel = NSManagedObjectModel(contentsOfURL:modelURL)else { 
  fatalError(“无法初始化ManagedObjectModel”) 
  } 
 让persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel:managedObjectModel) 
 让fileManager = NSFileManager() 
 警卫让documentURL = fileManager.URLsForDirectory(.DocumentDirectory,inDomains:.UserDomainMask)。 
  fatalError(“无法获取文档URL”) 
  } 
 让storeURL = documentsURL.URLByAppendingPathComponent(“ Grocry.sqlite”) 
 打印(storeURL) 
 尝试!  persistentStoreCoordinator.addPersistentStoreWithType(NSSQLiteStoreType,配置:无,URL:storeURL,选项:无) 
 让类型= NSManagedObjectContextConcurrencyType.MainQueueConcurrencyType 
  self.managedObjectContext = NSManagedObjectContext(concurrencyType:类型) 
  self.managedObjectContext.persistentStoreCoordinator =持久性存储协调器 
  } 

您在屏幕快照中看到的UIAlertView使用以下代码显示:

  @IBAction func addShoppingListButtonPressed(){ 
 让alertController = UIAlertController(标题:“杂货店”,消息:“输入购物清单”,preferredStyle:.Alert) 
 让saveAction = UIAlertAction(title:“ Save”,样式:.Default){(action:UIAlertAction)在 
 让shoppingNameTextField = alertController.textFields![0] 
  self.saveShoppingList(shoppingNameTextField.text!) 
  } 
  alertController.addTextFieldWithConfigurationHandler {(textField)在 
  } 
  alertController.addAction(saveAction) 
  self.presentViewController(alertController,动画:true,完成:无) 
  } 

而且这个清单还在不断增加。

上面的代码可以正常工作。 但是看起来可能是骗人的。 每次将代码添加到上述控制器时,都会造成技术负担。 这意味着每次您在视图控制器中添加/删除/更新代码时,将花费越来越长的时间,这将导致麻烦。

CoreData堆栈

您可以执行的最明显的重构是将所有CoreData设置代码移出视图控制器,并移到单独的类中。 我们将单独的类称为“ CoreDataManager ”,它的唯一责任是设置CoreData堆栈。

 类CoreDataManager:NSObject { 
  varmanagedObjectObjectContext:NSManagedObjectContext! 
 覆盖init(){ 
 警卫让modelURL = NSBundle.mainBundle()。URLForResource(“ GrocryDataModel”,withExtension:“ momd”)else { 
  fatalError(“未找到GrocryDataModel”) 
  } 
  .....其他代码初始化CoreData堆栈 
  } 

将CoreData设置移到另一个类后,我们可以简单地从AppDelegate内部对其进行初始化。 实现如下所示:

  func应用程序(应用程序:UIApplication,didFinishLaunchingWithOptions launchOptions:[NSObject:AnyObject]?)-> Bool { 
  //初始化核心数据管理器 
让coreDataManager = CoreDataManager()
返回真
  } 

清洁得多吧! 不仅如此,现在您还可以进入ShoppingListTableViewController并删除所有负责设置CoreData堆栈的代码。 删除代码总是很有趣🙂

数据源:

目前,所有数据源方法都在ShoppingListTableViewController内部实现。 它们是UITableViewDataSource的委托方法。 这些方法包括

 覆盖func numberOfSectionsInTableView(tableView:UITableView)-> Int 
 覆盖func tableView(tableView:UITableView,numberOfRowsInSection部分:Int)-> Int 
 覆盖func tableView(tableView:UITableView,cellForRowAtIndexPath indexPath:NSIndexPath)-> UITableViewCell 

不用在ShoppingListTableViewController内部实现方法,我们可以将它们提取到数据源中。 这样,与数据源相关的所有内容都将在一个地方可用,而控制器将无需承担任何责任。

ShoppingListDataSource是我们新的数据源类,负责向UITableView控件提供必要的数据。

  class ShoppingListDataSource :NSObject,UITableViewDataSource,ShoppingListDataManagerDelegate { 
  var manager:ShoppingListDataManager! 
  var tableView:UITableView! 
  var cellIdentifier:String! 
 私有let cellConfigurationHandler:(CellType,ShoppingList)->() 
  init(管理器:ShoppingListDataManager,tableView:UITableView,cellIdentifier:String,cellConfigurationHandler:(CellType,ShoppingList)->()){ 
  self.manager =经理 
  self.tableView = tableView 
  self.cellIdentifier = cellIdentifier 
  self.cellConfigurationHandler = cellConfigurationHandler 
  super.init() 
  self.manager.delegate =自我 
  } 
  func numberOfSectionsInTableView(tableView:UITableView)-> Int { 
 返回self.manager.numberOfSections 
  } 
  func shoppingListDataManagerDidInsertShoppingList(indexPath:NSIndexPath){ 
  self.tableView.insertRowsAtIndexPaths([indexPath],withRowAnimation:.Automatic) 
  } 
  func tableView(tableView:UITableView,numberOfRowsInSection部分:Int)-> Int { 
 返回self.manager.numberOfItemsInSection(section) 
  } 
  func tableView(tableView:UITableView,cellForRowAtIndexPath indexPath:NSIndexPath)-> UITableViewCell { 
 守卫让细胞= tableView.dequeueReusableCellWithIdentifier(self.cellIdentifier,forIndexPath:indexPath)为?  CellType else { 
  fatalError(“未定义ShoppingListTableViewCell!”) 
  } 
 让shoppingList = self.manager.objectAtIndexPath(indexPath) 
  self.cellConfigurationHandler(cell,shoppingList) 
 返回单元 
  } 

正如您在上面的代码中看到的那样,ShoppingListDataSource符合UITableViewDataSource协议并实现了所有必需的委托方法。

ShoppingListDataSource的最重要部分是带有多个参数的初始化代码。

  init(manager:ShoppingListDataManager,tableView:UITableView,cellIdentifier:String,cellConfigurationHandler:(CellType,ShoppingList)->()){ 

除了类型为ShoppingListDataManager的管理器参数外,大多数参数都是自解释的。 经理负责与CoreData通信以检索记录并将其提供给数据源。

ShoppingListTableViewController现在可以使用数据源,如以下实现所示:

  class ShoppingListTableViewController3:UITableViewController,{ 
 私人var dataSource:ShoppingListDataSource ! 
 私人var manager:ShoppingListDataManager! 
  varmanagedObjectContext:NSManagedObjectContext! 
 覆盖func viewDidLoad(){ 
  super.viewDidLoad() 
  self.manager = ShoppingListDataManager(managedObjectContext:self.managedObjectContext) 
  self.dataSource = ShoppingListDataSource(管理器:self.manager,tableView:self.tableView,cellIdentifier:“ ShoppingListTableViewCell”,cellConfigurationHandler:{(cell:UITableViewCell,shoppingList:ShoppingList)在 
  }) 
  self.tableView.dataSource = self.dataSource 
  } 

现在,由于职责已移至ShoppingListDataSource和ShoppingListDataManager类,因此您可以从ShoppingListTableViewController删除与NSFetchedResultsController关联的所有代码。

ShoppingListDataManager初始化代码如下所示:

  class ShoppingListDataManager:NSObject,NSFetchedResultsControllerDelegate { 
  var委托:ShoppingListDataManagerDelegate! 
  varmanagedObjectContext:NSManagedObjectContext! 
  var fetchedResultsController:NSFetchedResultsController! 
  init(managedObjectContext:NSManagedObjectContext){ 
  self.managedObjectContext = managedObjectContext 
  let request = NSFetchRequest(entityName:ShoppingList.entityName) 
  request.sortDescriptors = ShoppingList.sortDescriptors 
  request.predicate = NSPredicate(值:true) 
 self.fetchedResultsController = NSFetchedResultsController(fetchRequest:请求,managedObjectContext:self.managedObjectContext,sectionNameKeyPath:nil,cacheName:nil) 
  super.init() 
  self.fetchedResultsController.delegate =自我 
 尝试!  self.fetchedResultsController.performFetch() 
  } 

如前所述,ShoppingListDataManager负责通过NSFetchedResultsController与CoreData通信。 它唯一需要的参数是对NSManagedObjectContext的引用。

通过引入ShoppingListDataSource和ShoppingListDataManager,我们已经见证了许多代码改进。 我们的ShoppingListTableViewController不再包含数据源和NSFetchedResultsController委托方法。 该代码更好,更精简,更易于理解和更改。

在下一部分中,我们将介绍Swift扩展以及它如何帮助创建可重用和可维护的代码。

下载代码