티스토리 뷰
우연히 GraphQL을 써볼 기회가 생겼는데요, Apollo라는 라이브러리를 이용해 GraphQL을 적용해보려고 합니다!
https://www.apollographql.com/docs/ios/get-started
해당 포스팅은 위 공식문서의 튜토리얼을 바탕으로 작성했습니다.
1. apollo-ios 설치하기
우선 패키지를 설치해주어야겠죠? Apollo 라이브러리 자체는 서버부터 다양한 클라이언트 언어와 프레임워크를 지원하고 있어요! 이중 iOS 패키지를 찾아서 설치해주면 됩니다. File > Add Packages > 깃허브 주소를 입력해서 설치해주세요!
주소: https://github.com/apollographql/apollo-ios
⚠️ 이때 ApolloCodeGenLib를 선택하지 않도록 주의해주세요. 만약 Lib 파일이 포함되면 빌드 시 cannot find type 'apollocodegen' in scope 라는 에러가 뜹니다. 코드제너레이터는 macOS 환경에서만 동작해야하기 때문에 iOS 빌드파일에 추가되면 안됩니다.
2. Schema 파일 추가
여기서 스키마란 GraphQL의 스키마를 말합니다!
스키마 파일은 서버측에서 정의해놓은데로 작성해주시면 됩니다. 만약 서버측에서도 Apollo 라이브러리를 사용하고 있다면 아래와 같은 Apollo 툴에서 다운 받으실 수 있습니다.
옵션은 JSON과 SDL이 있는데요, 이따 설정에 SDL(.graphqls)파일로 설정을 해줄거라 SDL로 받아주시면 됩니다.
받은 파일은 프로젝트 파일에 포함시켜주세요! (위치는 상관 X)
3. Cli설정
Apollo는 코드제너레이터를 사용하는데요, 이 코드제너레이터는 스키마 파일을 토대로 오퍼레이션(쿼리)를 Swift에서 사용할 수 있는 형태로 변경해줍니다
코드 제너레이터를 사용하기 위해선 cli 파일(실행 파일)이 있어야하기 때문에 이를 다운로드 해주어야합니다.
공식문서에선 Apollo 라이브러리를 설치하고 프로젝트 아이콘을 우클릭하면 위와 같은 옵션이 생긴다고 하는데요, 저는 Xcode 버전 문제로(아마도) 해당 옵션이 뜨지 않더라구요. 그래서 패키지 프로젝트를 만들어서 Pakage.swift 파일을 통해 다운받아서 옮겨 주었습니다.
⚠️ Pakage.swfit 이용 방법
1. 우선 패키지 프로젝트를 하나 만들어줍니다.
2. 패키지 프로젝트 상위 터미널에서 아래 명령어를 입력합니다.
3. 그럼 apollo-ios-cli 라는 파일이 생기는데 이걸 원하는 프로젝트 폴더에 옮겨주면 됩니다!swift package --allow-writing-to-package-directory apollo-cli-install
+ 만약 원래부터 패키지로 프로젝트파일을 관리하고 있었다면 그대로 사용하시면 됩니다!(Tuist등)
4. code generation configuration 파일 설정
그다음 코드제너레이터를 사용하기 위해 config 파일을 하나 만들어야하는데요, 위에서 받은 Cli에서 생성 방법을 제공하고 있습니다.
./apollo-ios-cli init --schema-name ${MySchemaName} --module-type ${ModuleType}
cli가 있는 상위 폴더에서 터미널을 열고 위와 같은 명령어를 입력해주면 되는데요,
이때 MySchema은 스키마 네임스페이스명을 작성해주시면 되고 (임의로 작성해주시면 됩니다!)
MoudelType은 프로젝트 형태에 따라 변경해주시면 됩니다. 자세한 사항은 공식문서 ModuleType을 참고해주세요.
일반적인 싱글타겟 어플리케이션이라면 embeddedInTarget(name:) 를 작성하면 됩니다.
앱이름(타겟)이 MyApp이라면 다음과 같은 명령이 됩니다.
./apollo-ios-cli init --schema-name MySchema --module-type embeddedInTarget --target-name "MyApp"
5. 쿼리 작성
이제 기본 세팅은 거의 되었습니다! 이제 직접 사용할 gql 쿼리문을 작성해주겠습니다.
최상위 디렉토리(혹은 원하는 곳)에 우클릭 후 Emtpy파일을 선택해주시고
확장자가 .graphql인 파일을 생성해줍니다.
내부에는 사용하고자하는 graphql 질의문을 작성해줍니다.
6. 코드제너레이터 실행
이제 코드제너레이터를 통해 위 질의문을 Swift에 맞게 변환해야합니다. 참고로 질의문에 변경사항이 있거나, 새로운 질의를 추가할땐 꼭 이 작업을 해주셔야합니다.
사용법은 간단히 아래 명령어를 입력해주면됩니다! (cli가 있는 상위폴더에서 실행)
./apollo-ios-cli generate
프로젝트 폴더내에 이런식으로 지정한 네임스페이스(MySchema)에 따라 Schema, Operations 폴더가 생기면 성공입니다!
⚠️ 만약 Xcode내에서 보이지 않는다면 수동으로 파일을 추가해서 연결해주세요!
7. Client 구성
마지막으로 프로젝트 내에서 사용할 Client를 구성해주면 됩니다!
import Foundation
import Apollo
let apolloClient = ApolloClient(url: URL(string: "http://localhost:4000/graphql")!)
apolloClient.fetch(query: HeroNameQuery()) { result in
guard let data = try? result.get().data else { return }
print(data.hero.name) // Luke Skywalker
}
이런식으로 쿼리를 받아올 수 있는데요, 편하게 사용하기 위해서 싱글턴 클래스로 감싸주겠습니다.
⚠️ 만약 Query 객체를 찾지 못한다면 MySchema.HeroNameQuery() 와 같이 네임스페이스를 붙여서 작성해보세요!
import Foundation
import Apollo
class HeroService {
static let shared = HeroService()
private init() { }
let apolloClient = ApolloClient(url: URL(string: "http://localhost:4000/graphql")!)
func requestTest() {
apolloClient.fetch(query: MySchema.HeroNameQuery()) { result in
guard let data = try? result.get().data else { return }
print(data.hero.name) // Luke Skywalker
}
}
}
// 사용
HeroService.shared.requestTest()
GraphQL을 이용한 Service 구성을 완료했습니다!!
+ 인터셉터 설정
실제로 API를 적용하기 위해선 여러 인터셉터가 필요한 경우가 있는데요, 이러한 인터셉터를 설정하는 방법도 알아보겠습니다.
우선 저는 API호출에 토큰값을 넣어야해서 모든 요청에 인증 헤더를 추가해주는 인터셉터를 설정해보겠습니다.
1. 먼저 Intercepter를 구성해줍니다.
import Foundation
import Apollo
class AuthorizationInterceptor: ApolloInterceptor {
func interceptAsync<Operation: GraphQLOperation>(
chain: RequestChain,
request: HTTPRequest<Operation>,
response: HTTPResponse<Operation>?,
completion: @escaping (Result<GraphQLResult<Operation.Data>, Error>) -> Void) {
request.addHeader(name: "Authorization", value: "Bearer Token")
chain.proceedAsync(request: request,
response: response,
completion: completion)
}
}
인터셉터를 구성하기 위해서는 ApolloIntercetpor라는 프로토콜을 채택해서 해당 메서드를 구현하면 됩니다.
주의할점은 원하는 작업을 마친 후에 꼭 chan.proceedAsync를 호출해주어야 다음 인터셉터 동작으로 넘어갈 수 있으므로 주의해서 작성해야합니다. (proceed외에도 chain.handleErrorAsync로 에러를 방출할 수도 있습니다.)
2. IntercepterProvider를 구성해줍니다.
import Foundation
import Apollo
struct NetworkInterceptorProvider: InterceptorProvider {
// These properties will remain the same throughout the life of the `InterceptorProvider`, even though they
// will be handed to different interceptors.
private let store: ApolloStore
private let client: URLSessionClient
init(store: ApolloStore, client: URLSessionClient) {
self.store = store
self.client = client
}
func interceptors<Operation: GraphQLOperation>(for operation: Operation) -> [ApolloInterceptor] {
return [
MaxRetryInterceptor(),
CacheReadInterceptor(store: self.store),
AuthorizationInterceptor(), //✅
NetworkFetchInterceptor(client: self.client),
ResponseCodeInterceptor(),
JSONResponseParsingInterceptor(),
AutomaticPersistedQueryInterceptor(),
CacheWriteInterceptor(store: self.store),
]
}
}
이때 아래 그림에 있는 인터셉터들은 기본 동작에 필요한 인터셉터들이므로 지우시면 안되고 그대로 작성해주셔야합니다. 또한 원하는 동작이 적절한 시점에 이루어질 수 있게끔 기본 인터셉터들 사이에 만든 인터셉터를 끼워주셔야합니다. 명칭들을 보시면 대강 어떤 역할인지 알 수 있죠? 저는 요청을 보내기 전 헤더를 추가하는 동작을 원하므로 NetworkFetch 인터셉터 직전에 추가해주었습니다.
3. 만든 인터셉터 체인을 가진 Client를 생성합니다.
struct CusomApolloClient {
static let apolloClientWithIntercepter: ApolloClient = {
// The cache is necessary to set up the store, which we're going
// to hand to the provider
let cache = InMemoryNormalizedCache()
let store = ApolloStore(cache: cache)
let client = URLSessionClient()
let provider = NetworkInterceptorProvider(store: store, client: client) // ✅
let url = URL(string: "https://xxx.xx.xxx.xx/graphql")!
let requestChainTransport = RequestChainNetworkTransport(
interceptorProvider: provider,
endpointURL: url
)
// Remember to give the store you already created to the client so it
// doesn't create one on its own
return ApolloClient(networkTransport: requestChainTransport, store: store)
}()
}
기본적인 Client가 아닌 새로 정의한 Intercepter provider를 가지는 클라이언트를 선언해주고 앞으로 대신 사용합니다!
캐시나 스토어 부분도 필요에 따라 설정, 변경해주시면 됩니다.
// 사용예시
class HeroService {
static let shared = HeroService()
private init() { }
private let apolloClient: ApolloClient = CusomApolloClient.apolloClientWithIntercepter
func requestHeroName() {
apolloClient.fetch(query: HeroNameQuery() ) { result in
guard let data = try? result.get().data else { return }
print(data)
}
}
}
인터셉터에 대한 자세한 설명은 공식문서 Advanced networking configuration 부분을 참고해주세요!
마치며
이것저것 설정할 것들이 많아 좀 복잡했네요.. 😅 그래도 튜토리얼과 문서가 꽤 잘되어 있어서 무사히 설정할 수 있었습니다 👍 SPM, Xcode, Pod등 각각 방법에 대해서도 상세히 작성되어 있으니까 다른 패키지 매니저를 쓰신다면 공식문서를 참고해주세요!
GraphQL학습은 아래 사이트에 있는 내용을 읽어보며 했는데, 내용이 많지 않고 정리가 잘 되어 있어 쉽게 이해할 수 있어서 추천드립니다!
https://graphql-kr.github.io/learn/
읽어주셔서 감사합니다!
ref.
[iOS - swift] 4. GraphQL - Apollo의 request pipeline, Interceptor, token, header
'iOS' 카테고리의 다른 글
[iOS] Healthkit 사용기 - 아키텍처, 테스터블하게 만들기 (2) | 2023.07.30 |
---|---|
[iOS] iOS에서 Hot Reloading 사용하기 - InjectIII 소개 (0) | 2023.05.31 |
[Swift] DI 라이브러리 소개 및 비교 - Factory, Swinject, Needle, swift-dependencies (1) | 2023.05.26 |
[iOS] 긴 문자열, URL 등의 정적 리소스 관리하기! - .plist, .rtf 이용 (0) | 2023.05.23 |
[iOS] 로컬 Push Notification(푸시 알림) 시간 · 주기 설정하기 + 관리 (0) | 2023.05.17 |
- Total
- Today
- Yesterday
- ios
- SWM
- 코디네이터 패턴
- MVVM
- coordinator pattern
- Bloking/Non-bloking
- design pattern
- DocC
- swift
- SwiftUI
- RX
- 아키텍쳐 패턴
- MVC
- Flutter
- Swift Concurrency
- 비동기/동기
- healthkit
- programmers
- Flux
- 소프트웨어마에스트로
- combine
- MVI
- reactive programming
- 노션
- Architecture Pattern
- TestCode
- 프로그래머스
- GetX
- 리액티브 프로그래밍
- notion
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |