Swift 1中的核心数据:创建纯Swift NSFetchedResultsControllerDelegate

在Swift中使用Core Data的路径不像高速公路,而更像是一条依山傍水的危险小径。

但是,像许多Swift问题一样,问题不是新的危险,而是现有的不安全做法正在被揭示。 一种方法是简单地重新创建不安全的做法,例如使用隐式展开的可选方法。 另一种方法是将所有内容包装在“ guard”和“ if let”的多个级别中,以使控制流隐藏在不必要的分支中。

无论如何,Objective-C的假设在框架中构建得越深,在与众不同的Swift世界中就越难使用。 即使系统已试图弥合差距,例如将具有混合结果的Objective-C泛型添加到NSFetchedResultsController中,这也是正确的。

编译器崩溃后,需要另一种方法。


与许多Cocoa委托一样,在Swift中使用NSFetchedResultsController的可能性受到它所施加的要求的限制:委托必须是NSObject,其方法必须是Objective-C,并且回调不能是通用的。

目标是使NSFetchedResultsControllerDelegate适应隐藏其旧的Objective-C包。 这可以通过将NSFetchedResultsController包装在一个适配器类中来完成,该适配器类确实满足旧的要求,并在纯Swift中传递新的委托回调。

第一步是将委托重新创建为Swift协议:

这里的接口几乎与NSFetchedResultsControllerDelegate完全匹配(省去了无关的controller(_:sectionIndexTitleForSectionName) )。 该关联类型被命名为ResultType,并且由于它将是NSFetchedResultsController的ResultType,因此具有NSFetchRequestResult约束,但是我们并没有通过要求编译器将Objective-C桥接到Swift泛型而混淆了编译器。

注意,我们仍然使用NSFetchedResultsController:我们还没有准备好重构它。 现在的目标是适应代表。 因此,我们保留NSFetchedResultsController及其相关的“ NSFetchedRequestResult”和“ NSFetchedResultsChangeType”。

然后可以构建适配器:

即使不对泛型进行解码,也很难避免发生的事情:来自NSFetchedResultsController的委托消息被转换并传递给FetchedResultsControllerDelegate实例。 每个细节都需要考虑,但是要逐行考虑:

类声明和属性

像大多数泛型一样,类的声明可能不是直觉的。 大致可以分为三个部分:

1) class FetchedResultsControllerAdapter

FetchedResultsControllerAdapter具有单个通用类型Delegate,它是我们之前创建的FetchedResultsControllerAdapterDelegate的实例。 AdapterDelegate在类型上必须是通用的,因为正如大多数Swift开发人员所了解的那样,由于它具有Self或关联的类型要求,因此只能用作通用约束。 在整个课程中,我们将其称为“委托”。

由于Delegate具有ResultType,因此我们也可以引用它,例如在Fetched Results Controller属性中:

private(set) var fetchedRequestController : NSFetchedResultsController

这是确保NSFetchedResultsController和Delegate的ResultType匹配所必需的。

2) NSObject, NSFetchedResultsControllerDelegate

FetchedResultsControllerAdapter是一个NSObject,它符合NSFetchedResultsControllerDelegate的要求。

3) where Delegate: AnyObject

对于内存管理存在此约束。 代表是保留周期的经典来源。 如果此对象强烈引用其委托,使其保持活动状态,并且如果该委托强烈引用该适配器(几乎可以肯定),则使其保持活动状态,则将永远不会释放这两个对象。

解决此问题的最佳方法是声明属性弱: weak var resultsDelegate : Delegate?
weak var resultsDelegate : Delegate?

但这只能通过引用类型来完成。 值类型没有引用计数,因为它们没有引用。

如果无法实现委托类绑定,还有其他方法可以防止保留周期。 委托(指的是持有对适配器的引用的客户端)可以在其`deinit`方法中显式取消适配器的实例。 或在委托中可以调用适配器将其委托设置为nil(尽管没有充分的理由说明为什么除了委托以外的其他东西都可以引用该类)。 如果需要,这将是可以接受的增加的复杂性; 但这还不需要,现在我们只需要使委托属性弱即可。

在里面

Init的目标是确保建立正确的代表安排-只有在大多数情况下,这才是可能的。

1) init(frc: NSFetchedResultsController, resultsDelegate: Delegate?) { //...

init方法可预测地设置此包装器类:在第一个参数中,它采用了已配置的FetchedResultsController,将各种Core Data职责排除在该类之外,以使其专注。 该属性(缩写为frc)为private(set),以强制客户端使用init方法。 它是可公开访问的,因此客户可以自由使用它。

第二个参数是通用委托。 任何客户端都可以提供符合该协议的任何类的实例,并且该实例将起作用。 考虑这种情况的一种方法是,对于每种具体类的使用,编译器都会创建该类的新版本,并为Delegate的每种使用替换具体类。 这样可以避免混淆Objective-C调用者。

2) self.frc = frc
self.resultsDelegate = resultsDelegate
super.init()
self.frc.delegate = self
self.frc = frc
self.resultsDelegate = resultsDelegate
super.init()
self.frc.delegate = self

super.init()的调用引人注目。 对于Swift开发人员而言,超类初始化器的动作一直是令人沮丧的原因,但规则通常具有很大的意义:如果该类不存在,则该类不能成为另一个对象的委托。直到它被称为超级。

这也是为什么在init方法中委托是可选的,而委托是一个可公共设置的可选变量的原因,即使该类没有委托也没有太大的目的:如果另一个类想要初始化一个FetchedResultsControllerDelegateAdapter作为其初始化过程的一部分,它将需要对super执行相同的调用,或者至少完成其自身属性的设置,其中可能包括此适配器。 如果适配器需要代理来作为初始化的一部分,那么它将严重困扰客户。

出于同样的原因,适配器不调用performFetch()本身,而是公开一个方法。 委托方法可以在设置委托之前立即启动,否则客户端可能无法准备委托回调,直到将来某个时候。 由于客户端可以自己访问控制器,因此此方法有点夸张,提醒该步骤是必需的。

释放回调

委托回调很简单,将方法几乎直接从@objc传递到Swift协议委托。

但是,@objc委托方法不会传入类型化的FetchedResultsControllers。 所有这些都只键入NSFetchedRequestResult。 可以将传入的控制器强制转换为适当的类型,但是由于此类仅每个都具有一个NSFetchedResultsController,因此传递它就很简单。 如果出于某种原因将该类设置为另一个NSFetchedResultsController的委托,则可能会导致问题,但是没有理由这样做。

使用网站

泛型的副作用是客户端必须创建一个有效的FetchedResultsControllerAdapter,并键入自己的名称:

class MyViewController: UIViewController, UIFetchedResultsControllerAdapterDelegate {

var fetchedResultsController = FetchedResultsControllerAdapter

这可能看起来很奇怪,因为FetchedResultsControllerAdapter和MyViewController之间的关系并不十分明显。 在这里可以使用一种创建非泛型具体类的技术:

class MyViewControllerFetchedResultsControllerAdapter : FetchedResultsControllerAdapter {}

尽管纯粹为了使用现场的可读性而增加或可能不需要复杂性。


此处采取的方法使FetchedResultsController摆脱了对其委托的遗留限制。 而且,该适配器类只需要编写一次,然后就被遗忘了。

通过这种修改,解放了的FetchedResultsController可以开始利用Swift的惊人功能来变得更加强大。