GraphQL iOS: Swift 앱에서 클라우드 함수 사용하기
이 글에서는 Back4app에서 NodeJS 클라우드 코드와 함께 GraphQL을 사용하는 방법을 보여드렸습니다.
그리고 이 가이드에서는 Apollo GraphQL iOS 클라이언트를 Swift와 함께 사용하는 방법을 보여드렸습니다.
이제 이 모든 것을 한데 모아 정말 쉬운 코드를 만들어 복잡한 작업을 많이 만들어 봅시다. 또한 클래스를 변경할 때에도 API 코드를 자동으로 생성하는 XCode를 사용해 보겠습니다.
좋죠? 그럼 커피를 마시며 함께 해봅시다!
Contents
시작하기 전에…
하나의 앱이 생성된 Back4app 계정이 필요합니다. 아직 계정이 없는 경우 이 가이드를 따라하시면 금방 모든 것을 갖추실 수 있습니다.
도움이 될 몇 가지 소프트웨어…
또한 작업 속도를 높이기 위해 몇 가지 도구를 사용할 것입니다.
물론 원한다면 모든 작업을 수동으로 할 수도 있지만, 특히 초보자에게는 이 방법을 권장하지 않습니다.
패키지를 수동으로 추가하면 패키지를 수동으로 유지 관리해야 하므로 시간이 지나면 번거로울 수 있습니다.
여기서 소개하는 패키지 관리자를 반드시 사용할 필요는 없지만 수동 관리 대신 최소한 일부 패키지 관리자를 사용할 것을 강력히 권장합니다. 앞서 말했듯이 수동 관리는 시간이 지남에 따라 문제가 생길 수 있습니다.
저는 iOS 패키지 관리에는 Cocoapods를, 데스크톱 관리에는 NPM을 사용할 예정이지만, 다시 한 번 말씀드리지만 무엇이든 마음에 드는 것을 자유롭게 사용하셔도 됩니다.
Cocoapods 설치 지침은 이 사이트에서 찾을 수 있습니다.
NPM 설치 지침은 이 사이트에서 확인할 수 있습니다.
클래스 만들기
세 가지 클래스를 구현할 예정입니다: 소유자, 개, 품종입니다.
이 모델은 이를 암시합니다:
- 소유자는 여러 개의 반려견을 가질 수 있습니다.
- 개는 하나의 품종만 가질 수 있습니다.
속성은 다음과 같습니다:
- 소유자
- 이름 (문자열)
- 나이 (숫자)
- 주소 (문자열)
- hasDogs(소유자가 여러 개의 반려견을 가질 수 있으므로 반려견과의 관계)
- Dog
- 이름 (문자열)
- 생일(날짜)
- 품종(개는 하나의 품종만 있으므로 품종에 대한 포인터)
- 품종
- 이름 (문자열
그래픽 또는 프로그래밍 방식으로 해당 클래스를 생성하고 일부 데이터를 채웁니다. 제 클래스는 이렇게 완성되었습니다:
Breed
Dog
Owner
클라우드 코드를 시작하세요!
클라우드 코드 시간입니다. 하지만 이번에는 그것에 대해서도 더 자세히 알아보고 싶습니다.
Cloud Code가 NPM 모듈과 호환된다고 말씀드린 적이 있나요? 맞습니다! 그리고 많은 시간을 절약할 수 있습니다!
여러분이 갖고 싶었던 멋진 기능을 다른 사람이 이미 구현한 적이 있나요? 글쎄요, NPM 모듈이라면 사용할 수 있습니다!
이 튜토리얼을 사용하여 모듈을 사용하는 방법에 대한 문서가 이미 있으므로 이 구현에서는 강아지의 생일을 사용하고 월 단위로 나이를 계산할 수 있도록 Moment를 사용하겠습니다.
따라서 패키지.json 파일은 다음과 같습니다:
{ "의존성": { "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("retrieveDogIncludingBreedBreedByName", async req => { /* 이 함수는 견종 세부 정보(이름)를 포함한 특정 견종 세부 정보를 견종 이름으로 검색합니다. 반려견과 품종의 모든 속성이 검색됩니다. Dogs가 없으면 null이 검색됩니다. */ 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(); let newDog = { "dogName": results[i].get("name"), "dogAgeInMonths": ageInMonths } dogs.push(newDog); } 개를 반환합니다; });
네 가지 메서드를 노출하는 schema.graphql 파일은 다음과 같이 작성되었습니다:
확장 유형 Query { getAllOwnersAgeDescending: [OwnerClass!]! @resolve(to: "retrieveOwnersOrderedByAgeDescending") getAllGermanShepherds: [DogClass!]! @resolve(to: "retrieveAllGermanShepherds") getThisDogWithBreed (name:String): [DogClass!]! @resolve(to: "retrieveDogIncludingBreedBreedByName")! getDogsAgesInMonths: [DogAge!]! @resolve(to: "retrieveDogsNamesWithAgesInMonths") } 유형 DogAge { dogName: String! dogAgeInMonths: String! }
구문을 이해하지 못한다면 ‘추가 단계’ 섹션에서 이 문서를 확인하세요.
getDogsAgesInMonths에서 [DogAge!!!]를 주목하세요. 시스템에서 생성된 객체를 검색할 것이므로 GraphQL이 해석할 스키마가 없으므로 클라이언트가 해석할 수 있도록 유형도 만들어야 합니다.
테스트… 테스트 중…
테스트할 시간입니다!
Parse GraphQL 콘솔로 이동하여 쿼리를 실행하고 결과를 확인해 보겠습니다:
getAllOwnersAgeDescending을 쿼리합니다:
쿼리{ getAllOwnersAgeDescending { name age 주소 } }
getAllGermanShepherds를 쿼리합니다:
쿼리 { getAllGermanShepherds { name birthday } }
getThisDogWithBreed를 쿼리합니다:
query{ getThisDogWithBreed(name: "Fido"){ name birthday breed{ name } } }
getDogsAgesInMonths를 쿼리합니다:
쿼리{ getDogsAgesInMonths }
모든 것이 작동합니다! 잘됐네요! 이제 할 일은…
일부 XCoding
Back4app에서 필요한 모든 것이 준비되었습니다. 이제 XCode에서 이를 사용할 차례입니다.
이 가이드에서는 이를 실행하는 방법을 보여드렸지만 이번에는 좀 더 발전된 방법으로 여러 가지 메서드가 있고 그 중 하나가 예상 변수가 있으므로 몇 가지 변경이 필요합니다.
아직 해당 글을 읽지 않으셨다면 매우 상세하게 설명되어 있으니 꼭 읽어보시기 바랍니다.
먼저 문서에 설명된 대로 애플리케이션을 만들고 Apollo 클라이언트를 설치해 보겠습니다.
그런 다음 메인 스토리보드 파일을 열고 오른쪽 상단의 개체 단추를 클릭한 다음 테이블 보기를 뷰 컨트롤러로 끌어다 놓습니다:
모서리를 끌어서 뷰 컨트롤러의 전체 뷰와 일치하도록 테이블 뷰의 크기를 변경합니다:
그리고 화면 오른쪽 하단의 새 제약 조건 추가 버튼을 클릭하여 네 면 모두에 대한 제약 조건을 만듭니다. 네 면(빨간색 마커)을 클릭하고 제약 조건 추가 버튼을 클릭하여 새 제약 조건을 할당합니다:
이제 테이블 보기를 선택한 상태에서(클릭하여 선택) 프로토타입 셀을 추가해 보겠습니다. 오른쪽 상단의 속성 검사기를 클릭하고 프로토타입 셀 상자에서 0을 1로 바꿉니다:
이제 새로 만든 프로토타입 셀을 클릭하여 선택하고 적절한 식별자를 지정합니다: “cell”.
나중에 코드에서 해당 셀을 식별하는 데 이 식별자를 사용할 것입니다.
이제 테이블 보기에서 데이터 소스와 델리게이트를 뷰 컨트롤러에 연결해야 합니다.
키보드에서 Control 키를 누른 상태에서 테이블 보기를 클릭하고 뷰 컨트롤러 상단에 있는 노란색 아이콘(마우스오버 시 뷰 컨트롤러라고 표시된 아이콘)으로 드래그합니다.
거기에서 테이블 보기 링크를 놓으면 작은 팝업이 표시됩니다. 팝업에서 데이터 소스 및 위임자를 모두 선택합니다:
이제 기본 사항은 끝났지만 Back4app에서 데이터를 검색한 후에도 해당 테이블 보기에서 새로 고침을 호출해야 합니다. 그러기 위해서는 아웃렛을 만들고 이를 사용자 인터페이스에 연결해야 합니다.
가장 쉬운 방법은 화면 오른쪽 상단의 보조 편집기 표시를 클릭하면 UI와 코드를 나란히 볼 수 있습니다.
그런 다음 테이블 보기를 Control 클릭하고 코드에 드래그합니다:
놓으면 팝업이 나타납니다. 이름을 입력하고 연결을 클릭합니다:
이렇게 하면 멋진 아울렛이 연결되었으므로(줄 번호에 작은 원이 채워진 것을 확인하세요) 이제 해당 테이블 뷰에 대한 코드를 호출할 수 있습니다:
이 모든 작업이 완료되었으므로 이제 ViewController.swift 파일로 이동하면 됩니다.
그래프QL 파일
GraphQL가이드에서 GraphQL 파일을 만드는 방법을 설명했습니다. 이 프로젝트의 경우 저는 이렇게 끝냈습니다:
쿼리 findAllOwners{ getAllOwnersAgeDescending{ name age 주소 } } 쿼리 findAllGermanShepherds{ getAllGermanShepherds { name } } 쿼리 findThisDog ($name: String!){ getThisDogWithBreed(name: $name){ name birthday breed{ name } } } 쿼리 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. // Copyright © 2019 Venom. 모든 권리 보유. // import UIKit import Apollo ViewController 클래스를 만듭니다: UIViewController, UITableViewDelegate, UITableViewDataSource { @IBOutlet 약한 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-애플리케이션-아이디": "lAcIOgR0ndd7R4uMqovhNAoudpi6tMsrOI9KCuyr", "X-Parse-클라이언트-키": "f1wwm6uWqQoxazys3QQrrtY4fKuQuxYvNYJmYQJP" ] let url = URL(문자열: "https://parseapi.back4app.com/graphql")! 반환 ApolloClient( networkTransport: HTTPNetworkTransport( url: url, 구성: 구성 ) ) }() 재정의 함수 viewDidLoad() {. super.viewDidLoad() // 뷰를 로드한 후 추가 설정을 수행합니다. apollo.fetch(query: FindAllOwnersQuery()) { result in 가드 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 { // 이 함수는 데이터 소스와 델리게이트를 통해 테이블 뷰에서 호출됩니다. // 스토리보드에서 설정한 식별자로 표시할 셀을 생성합니다. // 테이블 뷰가 작동하려면 필수입니다. let cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "cell") cell.textLabel?.text = list[indexPath.row] return(cell) } }
실행해 봅시다!
코드를 실행할 시간입니다! Command + R을 누르고 모든 것이 올바르면 테이블 보기에 이름이 표시되어야 합니다:
멋지지 않나요?
이제 코드를 변경하여 다른 GraphQL 메서드를 실행하고 어떻게 작동하는지 확인할 수도 있습니다!
GraphQL 쿼리를 변경하여 getAllGermanShepherds 메서드를 실행해 보겠습니다:
재정의 함수 viewDidLoad() {자바 super.viewDidLoad() // 뷰를 로드한 후 추가 설정을 수행합니다. apollo.fetch(query: FindAllGermanShepherdsQuery()) { result in 가드 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() } } }
그리고 결과는…
쿼리에 변수를 전달하려면 어떻게 해야 할까요?
재정의 함수 viewDidLoad() { super.viewDidLoad() // 뷰를 로드한 후 추가 설정을 수행합니다. apollo.fetch(query: FindThisDogQuery(name: "Fido")) { result in 가드 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() } } }
이제 우리는 피도가 퍼그라는 것을 알았습니다:
그렇다면 새로운 타입을 반환하는 메서드는 어떨까요? 그것도 테스트해 봅시다:
override func viewDidLoad() { super.viewDidLoad() // 뷰를 로드한 후 추가 설정을 수행합니다. apollo.fetch(query: AgesInMonthsQuery()) { result in 가드 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() } } }
와 그 결과입니다:
결론
클라우드 코드는 멋집니다. GraphQL은 정말 멋집니다. Apollo는 iOS 네이티브 애플리케이션에 새로운 차원의 놀라운 기능을 제공합니다. 이 세 가지를 모두 함께 사용하면 매우 복잡한 애플리케이션을 아주 적은 노력으로 만들 수 있는 강력한 힘을 갖게 됩니다.
이제 쉽게 만들고 시간이 지나도 유지 관리할 수 있는 앱의 완전한 작동 흐름을 만들 수 있게 되었으니, 다음에는 무엇을 만들 것인가가 관건입니다.
도움이 되셨기를 바랍니다! 곧 더 많은 기능이 추가될 예정입니다! 계속 지켜봐 주세요!
Swift 앱에서 클라우드 기능을 사용하려면 어떤 소프트웨어를 사용해야 하나요?
선택에 따라 달라집니다. 사용하기 쉽고 운영하기 쉬운 소프트웨어를 사용하는 것이 좋습니다. 저는 위의 실습에서 아래 두 가지를 사용했습니다.
iOS 패키지 관리: Cocoapods
데스크톱 패키지 관리: NPM
NPM의 매력은 무엇일까?
클라우드 코드는 NPM 모듈과 호환됩니다. 정말 좋은 기능입니다. 시간을 크게 절약할 수 있고, 경쟁사보다 우위를 점할 수도 있습니다. 따라서 NPM과 클라우드 코드의 호환성은 여러분에게 큰 도움이 될 것입니다.