Core Data查询接口(CDQI)是一个类型安全、流畅、直观的库,用于在Swift中操作Core Data。CDQI将执行Core Data所需代码的数量大幅减少,并通过允许方法链和消除魔法字符串,极大地改善了代码的可读性。CDQI有点像jQuery或LINQ,但针对Core Data。
特性
- 流畅接口,即链式方法
- 大量有用的重载
- 过滤器比较中的类型安全
- 过滤、排序、分组、聚合表达式、限制等
- 可选地消除了在Core Data中常用到的魔法字符串
- 查询重用,即链式调用无副作用
- 支持iOS 9+、macOS 10.11+、tvOS 9+和watchOS 2+
- Swift 4
概述
基本上,CDQI是一个允许使用流畅语法创建(和执行)fetch请求的工具。在大多数情况下,这将许多行代码减少为单行(但仍然高度可读)。
let swiftDevelopers = managedObjectContext.from(Developer.self).
filter{ any($0.languages.name == "Swift") }.
order(ascending: false, {$0.lastName})
.limit(5)
.all()
集成
Carthage
在您的 Cartfile
文件中,添加以下行
github "prosumma/CoreDataQueryInterface" ~> 6.0
CocoaPods
将以下内容添加到您的 Podfile
中。如果之前没有添加的话,您还需要添加 use_frameworks!
pod 'CoreDataQueryInterface', '~> 6.0'
属性代理
为了使用如上示例中的 $0.languages.name
这样的表达式,必须创建代理对象。在项目根目录的 bin
文件夹中有一个名为 cdqi
的简单工具,可以完成这项任务。运行此工具之前,请确保每个 NSManagedObject
在您的 Swift 项目中都有一个对应的类。
cdqi Developers
它将递归地搜索所有子目录,直到找到一个名为 Developers.xcdatamodeld
的托管对象模型。然后检查该模型的 当前版本 并为每个 NSManagedObject
生成代理类。默认情况下,这些代理类放在托管对象模型相同的目录中,并与模型并排放置。cdqi
提供了许多选项来更改此行为,但在大多数情况下,默认选项则是什么您想要的。要获取更多选项,请执行 cdqi --help
。
请注意,当使用自动生成的 Core Data 类时,您应该使用 --public
标志创建代理,否则您将遇到编译错误。
类型安全
CDQI 支持过滤器表达式的类型安全。在表达式 $0.languages.name
中,name
属性已在 Core Data 模型中定义为字符串,因此它只能与字符串进行比较。以下将无法编译
$0.languages.name == 4
为了支持扩展性,CDQI 的类型安全实际上比上述描述更为复杂。Swift 的 String
类型能够参与字符串属性的比较,因为它实现了 TypedExpressionConvertible
extension String: TypedExpressionConvertible {
public typealias CDQIComparisonType = String
public static let cdqiStaticType = NSAttributeType.stringAttributeType
public var cdqiExpression: NSExpression {
return NSExpression(forConstantValue: self)
}
}
通过实现 TypedExpressionConvertible
协议,并将它的 CDQIComparisonType
作为 String
的同义词 typealias
定义,可以为类型实现 CDQI 字符串比较。要参与数值比较,则需要将 CDQIComparisonType
定义为 NSNumber
。
设想有一个 Weekday
枚举,我们希望将 Core Data 的一个 Int32
属性与其进行比较。而不是使用代码 $0.weekday == Weekday.Monday.rawValue
,我们可以使其更加简洁。
public enum Weekday: Int {
case Sunday = 1
case Monday = 2
case Tuesday = 3
case Wednesday = 4
case Thursday = 5
case Friday = 6
case Saturday = 7
}
extension Weekday: TypedExpressionConvertible {
public typealias CDQIComparisonType = NSNumber
public static let cdqiStaticType = NSAttributeType.integer32AttributeType
public var cdqiExpression: NSExpression {
return NSExpression(forConstantValue: NSNumber(value: rawValue))
}
}
现在我们可以说 $0.weekday == Weekday.Monday
。任何类型都可以使用此技术参与 CDQI 过滤比较。
查询重用
CDQI 尽可能在所有地方使用值类型。大多数 CDQI 方法,如 filter
,order
等,返回值类型 Query<M, R>
。这使得以下技术变得可能:
let projectQuery = Query<Project, Project>()
let swiftProjectQuery = projectQuery.filter{ any($0.languages.name == "Swift") }
第二条语句对第一条语句没有副作用。
示例
在 Examples
文件夹的单元测试和“Top Hits”示例应用中可以找到大量示例,但这里简要展示几个。
let developerQuery = managedObjectContext.from(Developer.self)
// predicate: languages.@count == 3 AND ANY languages.name == "Rust"
// developersWhoKnowThreeLanguagesIncludingRust is an array of Developer entities
let developersWhoKnowThreeLanguagesIncludingRust = developerQuery.filter{ $0.languages.cdqiCount() == 3 &&
any($0.languages.name == "Rust") }.all()
// predicate: ANY languages.name == "Haskell"
// haskellDevelopers is an array of dictionaries, e.g., [["firstName": "Haskell", "lastName": "Curry"]]
let haskellDevelopers = developerQuery.
filter{ developer in any(developer.languages.name == "Haskell") }.
select{ developer in [developer.firstName, developer.lastName] }.all()
// Instead of using $0, we can create a proxy up front.
let project = Project.CDQIAttribute()
// We can do a query in a single line
var swiftProjectNames: [String] = managedObjectContext.from(Project.self).
filter(any(project.languages.name == "Swift")).
order(project.name).array(project.name)
// Or we can build it up in multiple lines
var projectQuery = managedObjectContext.from(Project.self)
projectQuery = projectQuery.filter(any(project.languages.name == "Swift"))
projectQuery = projectQuery.order(project.name)
swiftProjectNames = projectQuery.array(project.name)