Swift 与 GraphQL 的结合体,轻量级且易于使用的框架。SociableWeaver 采用声明式的编程风格,使 GraphQL 查询在 Swift 代码中看起来非常自然。通过使用 Swift 5.1 的函数构建器和 CodingKeys
,SociableWeaver 在创建对象和字段时消除了对不安全字符串和字典的需求。
Xcode 11.x 或带有 Swift Package Manager 的 Swift 5.1x 工具链。
对于使用 .xcodeproj
的项目,最好的方法是导航到 File > Swift Packages > Add Package Dependency...
。 从那里只需输入 https://github.com/NicholasBellucci/SociableWeaver
作为包仓库的 url,并使用 master 分支或最新的版本。 Master 将始终与最新版本保持一致。 另一种方法是简单地将 .package(url: "https://github.com/NicholasBellucci/SociableWeaver.git", from: "0.1.0")
添加到你的 Package.swift
文件的 dependencies
中。
将以下条目添加到你的 Cartfile
并运行 $ carthage update SociableWeaver
github "NicholasBellucci/SociableWeaver"
SociableWeaver 支持 GraphQL 提供的所有功能。 为了充分利用此框架,只需确保使用的任何 Codable
模型都包含 CodingKeys
。 例如
public struct Post: Codable {
public enum CodingKeys: String, CodingKey, CaseIterable {
case id, title, content
}
public let id: String
public let title: String
public let content: String
public init(id: String, title: String, content: String) {
self.id = id
self.title = title
self.content = content
}
}
如果无法使用 CodingKeys
,SociableWeaver 确实支持字符串。 强烈建议将其作为最后的手段使用,因为它会使查询更难以管理。
GraphQL 的核心是查询对象上的特定字段,并仅返回所需的内容。 使用 SociableWeaver 构建带有字段的对象非常容易。
Weave(.query) {
Object(Post.self) {
Field(Post.CodingKeys.id)
Field(Post.CodingKeys.title)
Field(Post.CodingKeys.content)
}
}
query {
post {
id
title
content
}
}
参数是 GraphQL 的关键部分,允许进行更精确的查询。 SociableWeaver 支持对象和字段上的参数。
唯一的要求是参数的值符合 ArgumentValueRepresentable
。 诸如 String
、Int
、Bool
等核心类型已经符合。 枚举将需要符合 EnumValueRepresentable 协议。
Weave(.query) {
Object(Post.self) {
Field(Post.CodingKeys.title)
Object(Post.CodingKeys.author) {
Field(Author.CodingKeys.id)
Field(Author.CodingKeys.name)
.argument(key: "lastName", value: "Doe")
}
Object(Post.CodingKeys.comments) {
Field(Comment.CodingKeys.id)
Field(Comment.CodingKeys.content)
}
.argument(key: "filter", value: CommentFilter.recent)
}
}
query {
post {
title
author {
id
name(lastName: "Doe")
}
comments(filter: RECENT) {
id
content
}
}
}
支持可选项,并且可以包含在查询中。 如果应包含可选项并且该值为 nil,则生成的 GraphQL 值为 null
。
为了包含可选项,请确保在不包含 ?
的情况下获取属性的参数值。 这将导致查询参数为 age: null
。
public struct Author: Codable {
public enum CodingKeys: String, CodingKey, CaseIterable {
case id, name, age, birthplace
}
...
public let age: Int?
...
}
extension Author: ArgumentValueRepresentable {
public var argumentValue: String {
var params: [String: String?] = [:]
...
params["age"] = age.argumentValue
...
let paramStrings: [String] = params.compactMap { argument in
guard let value = argument.value else {
return nil
}
return "\(argument.key): \(value)"
}
return "{ \(paramStrings.joined(separator: ",")) }"
}
}'
在同一请求中多次查询单个对象时,别名是关键。
Weave(.query) {
Object(Post.self) {
Object(Post.CodingKeys.comments) {
Field(Comment.CodingKeys.id)
Field(Comment.CodingKeys.content)
}
.argument(key: "filter", value: CommentFilter.recent)
.alias("newComments")
Object(Post.CodingKeys.comments) {
Field(Comment.CodingKeys.id)
Field(Comment.CodingKeys.content)
}
.argument(key: "filter", value: CommentFilter.old)
.alias("oldComments")
}
}
query {
post {
newComments: comments(filter: RECENT) {
id
content
}
oldComments: comments(filter: OLD) {
id
content
}
}
}
GraphQL 片段可以在构建复杂的查询时提供帮助。 SociableWeaver 使它们非常简单,并允许将正确的引用放置在查询中的相应位置。 在 FragmentBuilder
的帮助下,可以将 FragmentReference
添加到需要字段的对象,并将 Fragment
添加到操作本身。
let authorFragment = FragmentBuilder(name: "authorFields", type: Author.self)
let query = Weave(.query) {
Object(Post.self) {
Object(Post.CodingKeys.author) {
FragmentReference(for: authorFragment)
}
Object(Post.CodingKeys.comments) {
Field(Comment.CodingKeys.content)
Object(Comment.CodingKeys.author) {
FragmentReference(for: authorFragment)
}
}
}
Fragment(authorFragment) {
Field(Author.CodingKeys.id)
Field(Author.CodingKeys.name)
}
}
query {
post {
author {
...authorFields
}
comments {
content
author {
...authorFields
}
}
}
}
fragment authorFields on Author {
id
name
}
操作名称不是必需的,但可以使查询更具唯一性。
Weave(.query) {
Object(Post.self) {
Field(Post.CodingKeys.id)
Field(Post.CodingKeys.title)
Field(Post.CodingKeys.content)
}
}
.name("GetPostAndContent")
query GetPost {
post {
id
title
content
}
}
由于在使用 SociableWeaver 进行查询时不需要直接的 JSON,因此可以在方法中定义变量,并作为参数传递到查询中。
queryPost(id: 1)
func queryPost(id: Int) {
Weave(.query) {
Object(Post.self) {
Field(Post.CodingKeys.title)
Field(Post.CodingKeys.content)
Object(Post.CodingKeys.author) {
Field(Author.CodingKeys.id)
Field(Author.CodingKeys.name)
}
}
.argument(key: "id", value: id)
}
}
query {
post(id: 1) {
title
content
author {
id
name
}
}
}
GraphQL 中的指令允许服务器影响查询的执行。 两个指令是 @include
和 @skip
,它们都可以添加到字段或包含的片段中。 该示例定义了 true 或 false,但在实际查询中,这些值将是布尔变量。
请注意,Skip 始终优先于 include。 此外,任何最终没有字段的对象/片段都将从查询中删除。
let query = Weave(.query) {
Object(Post.self) {
Field(Post.CodingKeys.title)
Field(Post.CodingKeys.content)
.include(if: true)
Object(Post.CodingKeys.author) {
Field(Author.CodingKeys.name)
}
.include(if: false)
Object(Post.CodingKeys.comments) {
Field(Comment.CodingKeys.content)
.include(if: true)
.skip(if: true)
Object(Comment.CodingKeys.author) {
Field(Author.CodingKeys.name)
.skip(if: true)
}
}
}
}
query {
post {
title
content
}
}
突变的工作方式与简单查询相同,应在应写入数据时使用。 Object.schemaName
将替换初始化程序中包含的对象或键的名称。
Weave(.mutation) {
Object(Post.self) {
Field(Post.CodingKeys.id)
Field(Post.CodingKeys.title)
Field(Post.CodingKeys.content)
}
.schemaName("createPost")
.argument(key: "title", value: "TestPost")
.argument(key: "content", value: "This is a test post.")
.argument(key: "author", value: "John Doe")
}
mutation {
createPost(title: "TestPost", content: "This is a test post.", author: "John Doe") {
id
title
content
}
}
在接口或联合类型上进行查询时,内联片段非常有用,因为它们允许返回底层类型。
Weave(.query) {
Object(Post.self) {
Field(Post.CodingKeys.title)
Field(Post.CodingKeys.content)
Object(Post.CodingKeys.comments) {
Field(Comment.CodingKeys.content)
Object(Comment.CodingKeys.author) {
InlineFragment("AnonymousUser") {
Field(Author.CodingKeys.id)
}
InlineFragment("RegisteredUser") {
Field(Author.CodingKeys.id)
Field(Author.CodingKeys.name)
}
}
}
}
}
query {
post {
title
content
comments {
content
author {
... on AnonymousUser {
id
}
... on RegisteredUser {
id
name
}
}
}
}
}
可以自定义 GraphQL 元字段,并且可以识别为具有两个前导下划线。 __typename
元字段是 GraphQL 默认值,可用于返回查询结果中的对象类型。
可以使用 MetaFieldType.custom
定义自定义元字段。 此枚举采用一个关联的 String,该 String 不需要包含名称前的双下划线。 例如:.custom("schema")
结果为 __schema
。
Weave(.query) {
Object(Post.self){
Field(Post.CodingKeys.title)
Field(Post.CodingKeys.content)
Object(Post.CodingKeys.author) {
MetaField(.typename)
Field(Author.CodingKeys.name)
}
}
}
query {
post {
title
content
author {
__typename
name
}
}
}
SociableWeaver 开箱即用地支持分页,并且可以轻松自定义。 支持的功能包括切片、边和页面信息包含。
GraphQL 中的切片非常适合获取响应中指定数量的对象。 使用 SociableWeaver,可以使用 Object.slice
方法指定此值。
Weave(.query) {
Object(Post.CodingKeys.comments) {
Field(Comment.CodingKeys.id)
Field(Comment.CodingKeys.author)
Field(Comment.CodingKeys.content)
}
.slice(amount: 2)
}
{
comments(first: 2) {
id
author
content
}
}
基于游标的分页被描述为 GraphQL 提供的最强大的分页类型。 通过声明对象的分页类型来设置此分页。
Weave(.query) {
Object(Post.CodingKeys.comments) {
Field(Comment.CodingKeys.id)
Field(Comment.CodingKeys.author)
Field(Comment.CodingKeys.content)
}
.slice(amount: 2)
.paginationType(.cursor)
}
{
comments(first: 2) {
edges {
cursor
node {
id
author
content
}
}
}
}
包含页面信息(例如是否存在下一页或结束游标)非常灵活,并支持自定义模型。
Weave(.query) {
Object(Post.CodingKeys.comments) {
Field(Comment.CodingKeys.id)
Field(Comment.CodingKeys.author)
Field(Comment.CodingKeys.content)
}
.slice(amount: 2)
.paginationType(.cursor)
.pageInfo(type: PageInfo.self,
keys: PageInfo.CodingKeys.startCursor,
PageInfo.CodingKeys.endCursor,
PageInfo.CodingKeys.hasNextPage)
}
{
comments(first: 2) {
edges {
cursor
node {
id
author
content
}
}
pageInfo {
startCursor
endCursor
hasNextPage
}
}
}
SociableWeaver 提供了一些自定义类型,这些类型有助于构建更自然外观的查询。 这些类型可能已包含在示例中,也可能没有,但也会在本节中定义,以提供更清晰的说明。
添加 ForEachWeavable 结构是为了在您可能想要在循环中添加对象或字段时使用。 有关此用例的更多讨论,请参见 此处。
let authors = [
Author(id: "1", name: "John", age: 17, birthplace: [:]),
Author(id: "2", name: "Jane", age: 29, birthplace: [:]),
Author(id: "3", name: "Adam", age: 41, birthplace: [:])
]
let query = Weave(.query) {
ForEachWeavable(authors) { author in
Object("postsForAuthor") {
Field(Author.CodingKeys.id)
Field(Author.CodingKeys.name)
Field(Author.CodingKeys.age)
Field(Author.CodingKeys.birthplace)
}
.argument(key: "id", value: author.id)
}
}
{
postsForAuthor(id: "1") {
id
name
age
birthplace
}
postsForAuthor(id: "2") {
id
name
age
birthplace
}
postsForAuthor(id: "3") {
id
name
age
birthplace
}
}
由于函数构建器当前的限制,目前不接受单个元素。 因此,每个函数构建器初始化程序都有一个对应于单个元素的初始化程序。 BuilderType.individual
已设置为指定对象或片段仅由一个元素组成时的情况。 所有初始化的 builderType
参数的默认值为 .individual
。 这意味着不需要传递它,并且会产生相同的结果。
Object(Post.CodingKeys.author) {
Field(Author.CodingKeys.name)
}
Fragment(authorFragment, .individual) {
Field(Author.CodingKeys.name)
}
提供此枚举是为了在初始化具有模型或编码键的对象和字段时允许进行自定义。 默认为驼峰式。
Field(Comment.CodingKeys.createdAt)
.caseStyle(.lowercase)
public enum CaseStyleOption {
case lowercase
case uppercase
case capitalized
case camelCase
case pascalCase
case snakeCase
case kebabCase
}
GraphQL 枚举值表示为大小写形式的 case 名称。 因此,应作为参数值传递的 swift 中的自定义枚举可以符合 EnumValueRepresentable
。 此协议符合 ArgumentValueRepresentable
,并扩展为将 argumentValue
作为 case 值的大写版本提供。
enum PostCategories: EnumValueRepresentable {
case art
case music
case technology
}
Object(Post.self) {
...
}
.argument(key: "category", value: PostCategories.technology)
/// Result: post(category: TECHNOLOGY) { ... }
SociableWeaver 现在和将来都将采用 MIT 许可。 有关详细信息,请参见 LICENSE。