GraphQL iOS:在 Swift 应用程序中使用云函数

这篇文章中,我向你展示了如何在 Back4app 中使用 GraphQL 与 NodeJS 云代码。

本指南中,我向你展示了如何使用 Swift 使用 Apollo GraphQL iOS 客户端。

现在,让我们将这一切结合起来,通过编写非常简单的代码来完成大量复杂的操作。而且,即使我们更改了类,XCode 也会自动为我们生成任何 API 代码。

听起来不错吧?那就去喝杯咖啡吧!

开始之前

您需要一个已创建应用程序的 Back4app 账户。如果您还没有账户,请按照本指南操作,很快就能拥有一切。

几款对我们有帮助的软件…

我们还将使用一些软件来加快速度。

当然,如果你愿意,也可以手动完成这一切,但我不建议这样做,尤其是对于初学者。
手动添加软件包的同时,还需要手动维护软件包,时间久了会很麻烦。

你不一定要使用我在这里演示的软件包管理器,但我强烈建议你至少使用一些软件包管理器,而不是进行手动管理。正如我之前所说,手动管理时间长了会变得古怪。

我将使用Cocoapods进行 iOS 软件包管理,使用NPM进行桌面软件包管理,但还是那句话,你想用什么就用什么吧。

安装Cocoapods的说明可在本网站找到。

NPM的安装说明可在本网站找到。

创建我们的类

我们将创建 3 个类别:主人、狗和品种。

我们的模型意味着

  • 主人可以拥有多条狗
  • 一条狗只能有一个品种

我们的属性将是

  • 所有者
    • 姓名(字符串)
    • 年龄(数字)
    • 地址(字符串)
    • hasDogs (与狗的关系,因为主人可以有多条狗)
    • 名称(字符串)
    • 生日(日期)
    • 品种(指向品种,因为一只狗只有一个品种)
  • 品种
    • 名称(字符串

以图形或编程方式创建这些类,并填充一些数据。我的类最终是这样的

screen-shot-2019-08-15-at-12-08-46

品种

screen-shot-2019-08-15-at-13-50-39

screen-shot-2019-08-15-at-12-15-45

主人

带上云代码,宝贝!

是时候来点 “云代码 “了。但这次我还想更深入地了解一下。

我告诉过你云代码与NPM模块兼容吗?它是兼容的!它能为你节省大量时间!

你知道你想拥有的那些很酷的功能,别人已经做到了吗?如果是NPM模块,你就可以使用!

本教程中已经有一篇关于如何使用模块的文章,所以在本教程中,我将使用Moment,这样我们就可以使用狗狗的生日来计算它的月龄。

因此,我的 package.json 文件将如下所示:

{
      "dependencies":{
           "moment":"*"
      }
}

* 表示我将使用最新版本。

我的云代码最终如下。代码注释得很清楚,所以我就不多说了,但你可以在第一行看到我使用了 Moment NPM 模块:

// 实例化 Moment NPM 模块
const moment = require('moment')

Parse.Cloud.define("retrieveOwnersOrderedByAgeDescending", async req => {
    /*
        此函数检索按年龄降序排列的所有所有者。
        如果没有所有者,则检索空数组。
    */
    const query = new Parse.Query("Owner"); 
    query.descending("age")
    const results = await query.find();

    返回结果;
});

Parse.Cloud.define("retrieveAllGermanShepherds", async req => {
    /*
        该函数将检索所有品种为 "德国牧羊犬 "的狗。
        将检索 Dogs 的所有属性。
        如果没有 Dogs,则检索空数组。
    */
    const breedQuery = new Parse.Query("Breed");
    breedQuery.equalTo("name", "German Shepherd") 
    const query = new Parse.Query("Dog"); 
    query.matchesQuery("breed", breedQuery);
    const results = await query.find();
    返回结果;
});

Parse.Cloud.define("retrieveDogIncludingBreedByName",async req => {
    /*
        此函数按狗名检索特定狗的详细信息,包括其品种详细信息(名称
        将检索 Dogs 和 Breed 的所有属性。
        如果没有狗,则检索为空。
    */
    const query = new Parse.Query("Dog"); 
    query.equalTo("name", req.params.name);
    query.include("breed")
    const results = await query.find();
    返回结果;
});

Parse.Cloud.define("retrieveDogsNamesWithAgesInMonths",async req => {
    /*
        该函数使用 NPM 模块检索以月为单位转换生日属性的 Dogs 名称。
        只检索 Dogs 的名称,并以月为单位计算年龄。
        如果没有 Dogs,则检索一个空数组。
    */
    let dogs = [];
    const query = new Parse.Query("Dog"); 
    const results = await query.find();

    for (let i = 0; i < results.length; i ++){
        // 使用 NPM 模块 Moment 计算以月为单位的年龄
        let ageInMonths = moment.duration(moment().diff(results[i].get("birthday"))).humanize();
        
        让 newDog = {
            "dogName": results[i].get("name")、
            "dogAgeInMonths": ageInMonths
        }

        dogs.push(newDog);
    }

    return dogs;
});

我的 schema.graphql 文件暴露了我的四个方法,结果如下:

extend type Query {
  getAllOwnersAgeDescending:[OwnerClass!]@resolve(to: "retrieveOwnersOrderedByAgeDescending")
  getAllGermanShepherds:[DogClass!]@resolve(to: "retrieveAllGermanShepherds")
  getThisDogWithBreed (name:String):[DogClass!]@resolve(to: "retrieveDogIncludingBreedyName")
  getDogsAgesInMonths:[DogAge!]  @resolve(to: "retrieveDogsNamesWithAgesInMonths")
}

类型 DogAge {
    dogName:String!
    dogAgeInMonths:String!
}

如果你不理解语法,请查看本文的 “额外的小步骤 “部分。
请注意 getDogsAgesInMonths 中的 [DogAge!]!。由于我将检索一个系统生成的对象,它不会有供 GraphQL 解释的模式,所以我必须创建类型,以便我们的客户端可以解释它。

测试…测试…

是时候进行一些测试了!

让我们进入 Parse GraphQL 控制台,运行我们的查询,检查其结果:

查询 getAllOwnersAgeDescending:

查询{
  getAllOwnersAgeDescending {
    名称
    年龄
    地址
  }
}

screen-shot-2019-08-16-at-09-17-50

查询 getAllGermanShepherds:

查询 {
  获取所有德国牧羊人 {
    姓名
    生日
  }
}

screen-shot-2019-08-16-at-09-46-24

getThisDogWithBreed 的查询:

查询{
  getThisDogWithBreed(name: "Fido"){
    名字
    生日
    犬种{
      名字
    }
  }
}

screen-shot-2019-08-16-at-09-50-05

getDogsAgesInMonths 的查询:

query{
  获取狗的月龄
}

screen-shot-2019-08-16-at-09-50-51

一切正常!好极了!现在是时候…

一些 XCoding

我们已经从 Back4app 启动并运行了所需的一切。现在是时候在 XCode 上使用它们了。

本指南展示了如何启动和运行,但这一次我们的内容更先进一些:我们有多个方法,其中一个是期望变量,因此我们需要做一些修改。

如果你还没有关注那篇文章,我强烈建议你关注一下,因为它非常详细。

首先,让我们按照文章中的描述创建应用程序并安装 Apollo 客户端。

然后,打开 Main.storyboard 文件,单击右上角的 Objects(对象)按钮,将表视图拖放到视图控制器中:

screen-shot-2019-08-19-at-13-00-29

通过拖动四角来改变表视图的大小,使其与视图控制器的整个视图相匹配:

screen-shot-2019-08-19-at-13-01-01

单击屏幕右下角的 “添加新约束 “按钮,为四边创建约束。单击四个边(红色标记),然后单击 “添加约束 “按钮来分配这些新约束:

screen-shot-2019-08-19-at-13-01-17

现在,选中表格视图(单击选中),添加一个原型单元格。单击右上角的属性检查器,将 “原型单元格 “框中的 0 替换为 1:

screen-shot-2019-08-19-at-13-01-30

现在,点击新创建的原型单元格选中它,并给它一个好的标识符:”单元格”。
我们稍后将在代码中使用它来标识该单元格。

screen-shot-2019-08-19-at-13-13-07

现在,我们必须将数据源和委托从表视图链接到视图控制器。
按住键盘上的 Control 键,单击表视图并将其拖动到视图控制器顶部的黄色图标上,即悬停时显示 “视图控制器 “的图标。
释放表视图链接后,会弹出一个小窗口。在弹出窗口中选择数据源和委托:

screen-shot-2019-08-19-at-13-02-09

现在,这将涵盖基本内容,但我们仍需要在从 Back4app 获取数据后调用刷新表视图。为此,我们必须创建一个 Outlet 并将其连接到用户界面。

最简单的方法是点击屏幕右上角的显示助理编辑器,这样就可以同时看到用户界面和代码。

然后控制点击表视图并将其拖动到代码中:

screen-shot-2019-08-19-at-17-10-53

松开后,会弹出一个窗口。填写名称并点击连接:

screen-shot-2019-08-19-at-17-08-14

完成后,你就会看到一个漂亮的 Outlet 连接(注意行号上填充的小圆圈),这样我们就可以调用该表视图的代码了:

screen-shot-2019-08-19-at-17-08-34

完成这一切后,我们就可以进入 ViewController.swift 文件了,因为现在是时候……

我们的 GraphQL 文件

我们的 GraphQL 指南介绍了如何创建 GraphQL 文件。在本项目中,我的文件是这样的

查询 findAllOwners{
    getAllOwnersAgeDescending{
        名称
        年龄
        地址
    }
}

查询 findAllGermanShepherds{
    获取所有德国牧羊人 {
        名称
    }
}

query findThisDog ($name:字符串!){
    getThisDogWithBreed(name: $name){
        名称
        生日
        犬种{
            名字
        }
    }
}

query agesInMonths{
    getDogsAgesInMonths
}

一些 Swift 代码

本指南中,我们再次介绍了如何在 Swift 中配置 Apollo,但今天的代码有一些变化。
由于我们要在表视图中以图形方式显示数据,因此必须包含两个方法供数据源和委托执行。这两个方法是

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

由于代码中已有说明,我就不多介绍了。请记住,这两个方法对于我们的表格视图的工作是强制性的。

完整的代码应该是这样的

//
// ViewController.swift
// GraphQLApollo
//
// 由 Venom 于 19/08/19 创建。
// 版权所有 © 2019 Venom。保留所有权利。
//

导入 UIKit
导入Apollo

类 ViewController:UIViewController, UITableViewDelegate, UITableViewDataSource {
    @IBOutlet weak var tableView:UITableView!
    
    var list = [] as [String] // 该数组将保存我们要显示的值
    
    // Apollo客户端初始化。
    // 更多信息请点击:https://www.back4app.com/docs/ios/swift-graphql
    let apollo:ApolloClient = {
        let configuration = URLSessionConfiguration.default
        configuration.httpAdditionalHeaders = [
            "X-Parse-Application-Id":"lAcIOgR0ndd7R4uMqovhNAoudpi6tMsrOI9KCuyr"、
            "X-Parse-Client-Key":"f1wwm6uWqQoxazys3QrrtY4fKuQuxYvNYJmYQJP"。
        ]
        
        let url = URL(string: "https://parseapi.back4app.com/graphql")!
        
        return ApolloClient(
            networkTransport:HTTPNetworkTransport(
                url: url、
                configuration: 配置
            )
        )
    }()
    
    覆盖 func viewDidLoad() {
        super.viewDidLoad()
        // 在加载视图后进行任何其他设置。
        
        apollo.fetch(query:FindAllOwnersQuery()) { result in
            keep let data = try? result.get().data else { return }
            
            for name in data.getAllOwnersAgeDescending {
                //为我们的列表数组添加每个名称
                self.list.append(name.name!)
            }
            
            //下面一行将在我们获取数据后强制重新加载表视图
            DispatchQueue.main.async { self.tableView.reloadData() } }
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // 这个函数被我们的表视图调用。
        // 表视图通过数据源和委托调用此函数。
        // 它只是返回表视图要显示的对象数量。
        // 表视图必须使用该函数。
        return list.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // 该函数由我们的表视图通过数据源和委托调用。
        // 表视图通过数据源和委托调用此函数。
        // 它创建要显示的单元格,并使用我们在 Storyboard 上设置的标识符。
        // 表视图必须使用该函数。
        let cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "cell")
        cell.textLabel.text = list[indexPath.row].
        return(cell)
    }
}

让我们运行它!

是时候运行我们的代码了!点击 Command + R,如果一切正常,你就能在表格视图中看到我们的名字了:

screen-shot-2019-08-19-at-17-26-09

很酷吧?
现在,我们甚至可以修改代码,执行其他 GraphQL 方法,看看效果如何!

让我们更改 GraphQL 查询,以执行 getAllGermanShepherds 方法:

    覆盖 func viewDidLoad() {
        super.viewDidLoad()
        // 在加载视图后进行其他设置。
        
        apollo.fetch(query:FindAllGermanShepherdsQuery()) { result in
            keep let data = try? result.get().data else { return }
            
            print(data.getAllGermanShepherds)
            
            for name in data.getAllGermanShepherds {
                //为列表数组添加每个名称
                self.list.append(name.name!)
            }
            
            //下面一行将在我们获取数据后强制重新加载表视图
            DispatchQueue.main.async { self.tableView.reloadData() } }
        }
    }

结果…

screen-shot-2019-08-20-at-08-45-40

如果我们想向查询传递一个变量,该怎么办?

    覆盖 func viewDidLoad() {
        super.viewDidLoad()
        // 在加载视图后进行其他设置。
        
        apollo.fetch(query.FindThisDogQuery(name))findThisDogQuery(name: "Fido")) { result in
            keep let data = try? result.get().data else { return }
            
            print(data.getThisDogWithBreed)
            
            for name in data.getThisDogWithBreed {
                //为列表数组添加每个名称
                self.list.append(name.name!+ " - " + (name.breed?.name!)!)
            }
            
            //下面一行将在我们获取数据后强制重新加载表视图
            DispatchQueue.main.async { self.tableView.reloadData() } }
        }
    }

现在我们知道 Fido 是哈巴狗了:

screen-shot-2019-08-20-at-08-52-29

那返回新类型的方法呢?我们也来测试一下:

   覆盖 func viewDidLoad() {
        super.viewDidLoad()
        // 在加载视图后进行其他设置。
        
        apollo.fetch(query.AgesInMonthsQuery)AgesInMonthsQuery()) { result in
            keep let data = try? result.get().data else { return }

            print(data.getDogsAgesInMonths)

            for dog in data.getDogsAgesInMonths {
                //为我们的列表数组添加每个名字
                self.list.append(dog.dogName + " - " + dog.dogAgeInMonths)
            }

            //下面一行将在我们获取数据后强制重新加载表视图
            DispatchQueue.main.async { self.tableView.reloadData() } }
        }
    } 

以及结果:

screen-shot-2019-08-20-at-10-16-21

结论

云代码很酷。GraphQL 超酷。Apollo 为您的 iOS 本机应用程序带来了新的精彩。三者结合在一起,你就能以最小的代价创建非常复杂的应用程序。

现在,您已经能够为应用程序创建一个易于创建和长期维护的完整工作流程,那么问题来了:您下一步要创建什么呢?

希望您喜欢这篇文章!我们即将推出更多内容!敬请期待!

哪些软件可以用来在swift app中使用云功能?

这取决于你自己的选择。你应该选择那些你容易上手和操作的软件。我在上面的实践中使用了以下两个。
-Cocoapods 用于 iOS 包管理
-NPM 用于桌面包管理

NPM 到底有何魔力?

Cloud Code 与 NPM 模块兼容。这是一件很棒的事情。它将帮助您节省大量时间,并让您在竞争中占据优势。因此,NPM 与 Cloud Code 的兼容性将为您带来巨大的提升。


Leave a reply

Your email address will not be published.