스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
CKTool JS 소개
CKTool JS를 사용하여 iCloud 컨테이너를 관리 및 자동화하는 방법을 확인하세요. 컨테이너의 스키마를 관리하고 레코드를 쉽게 수정하며 데이터를 즉석에서 조작할 수 있도록 CKTool JS를 구성하는 방법을 보여드립니다. 또한 자동화 및 툴링 작업 흐름에 CKTool JS를 통합하는 방법을 알아보겠습니다. 이 세션을 최대한 활용하려면 CloudKit 스키마, JavaScript 및 npm을 숙지하시는 것이 좋습니다.
리소스
관련 비디오
WWDC22
WWDC21
-
다운로드
♪♪ CKTool JS를 소개합니다 안녕하세요, 저는 CloudKit팀 엔지니어 Kent입니다 여러분이 CloudKit 접근할 수 있는 새로운 라이브러리를 소개하게 돼서 정말 기쁩니다 이 라이브러리를 구성하는 방법을 설명해 드린 다음 스키마를 관리하는 법을 알려 드릴 겁니다 CKTool JS를 이용해 사용자 데이터에 접근하는 방법도요 그럼 시작해 보죠!
CloudKit는 지속성 있는 기술입니다 이 기술을 통해 앱의 데이터를 iCloud에 저장할 수 있죠 앱에서 CloudKit를 사용하면 여러 기기와 웹에서 데이터를 최신 상태로 유지할 수 있습니다 앱을 구축하기 위해서 Apple 플랫폼에서 CloudKit 프레임워크를 통해 iCloud 저장소에 접근하거나 웹의 CloudKit JS에 접근할 수 있습니다 자동화 및 툴링을 위해서 Xcode는 macOS용 cktool을 제공합니다 이제 새롭게 CKTool JS를 이용해 변경을 자동화하고 iCloud와 상호 작용할 수 있습니다
CKTool JS는 Xcode 13에 도입된 cktool 명령행 유틸리티와 동일한 작업을 수행하고 비슷한 사용 사례를 지원합니다 실제로 CKTool JS는 레코드 유형을 추가하거나 기록하는 CloudKit Console의 기능 구현에 사용됩니다
CKTool JS를 사용하여 앱 컨테이너를 관리하고 스키마 작업을 수행할 수 있습니다 스키마를 업데이트하고 재설정하는 겁니다 JavaScript로는 할 수 없었던 것들이죠
CKTool JS에서 고유 식별자 또는 복잡한 쿼리를 통해 기존 기록을 불러올 수 있습니다 게다가 새 레코드를 만들고 업데이트할 수도 있죠 CKTool JS에는 TypeScript에 대한 엄격한 유형 정의가 따릅니다 이런 유형 정의는 컴파일 시간 검사를 활성화하고 클라이언트 라이브러리의 잘못된 사용을 알리죠 그리고 지원되는 IDE에서 코드를 완성할 수 있습니다 덕분에 CKTool JS 코드를 쉽게 편집할 수 있습니다
또한 새로운 라이브러리는 Node.js와 브라우저를 동시에 지원합니다 CKTool JS는 npm 패키지로 배포됩니다 이를 통해 JavaScript 파이프라인의 일부로 통합할 수 있죠 그러면 트리 쉐이킹과 번들링 같은 기능을 사용할 수 있습니다 이런 패키지에 대한 업데이트도 추적할 수 있는데 출시 이력이 npm에서 다 공개되기 때문이죠
다음 패키지는 배포용 CKToo JS의 일부입니다 알아두세요 이 패키지는 @apple 범위 내에 있으며 이름 시작 부분에 'cktool.'을 사용하는 규칙을 따르죠 여러분이 쓸 주요 패키지는 cktool.database입니다 iCloud와의 통신을 활성화하려면 타깃 플랫폼에도 다른 패키지를 사용해야 합니다 Node.js에는 cktool.target.nodejs를 쓰고 웹 브라우저에는 cktoo.browser를 쓰는 거죠
cktool.database는 자동으로 세 개의 패키지를 더 불러옵니다 cktool.core와 cktool.api.base cktool.api.database죠 CKTool JS는 iCloud와 직접 소통해서 허가를 먼저 받아야 합니다 여러분이 호출하려는 작업에 따라 관리 토큰이나 사용자 토큰이 필요합니다 두 종류의 토큰 모두 CloudKit Console에서 얻을 수 있죠
관리 토큰은 관리 작업에 접근하는데 사용되고 그 범위가 팀 및 사용자로 지정됩니다 스키마 불러오기와 내보내기 스키마 유효성 검사와 함께 컨테이너를 생산 모드로 재설정하는 작업이 포함되죠 사용자 토큰은 팀과 컨테이너로 범위가 지정되고 컨테이너의 사용자 데이터에 대한 접근도 활성화됩니다 이 승인 토큰을 얻는 방법과 CloudKit의 지속적인 통합 과정은 'cktool과 선언형 스키마를 사용한 CloudKit 자동화 테스트'를 확인하세요
CKTool JS를 언제든지 사용하려면 사용할 수 있도록 구성해야 하죠 CKTool JS를 구성하기 전에 CloudKit 스키마를 구성하는 요소를 간단히 훑어 보겠습니다 CloudKit에서는 구조화된 방식으로 데이터가 저장됩니다 같은 값을 가진 데이터는 레코드로 함께 저장되죠 레코드는 레코드 유형의 인스턴스입니다 레코드 유형이 구성하는 레코드의 프로퍼티는 필드라고 하죠 사용자 정의 필드 외에도 CloudKit에 시스템 필드를 추가합니다 레코드의 ID인 레코드 네임 같은 거죠 제가 작업한 동전 수집 앱을 예로 들어 볼게요 동전을 국가별로 모을 거니까 어떤 종류의 프로퍼티를 저장할지 레코드 유형은 이미 정해진 거죠 제가 이름과 ISO 코드를 저장해 뒀습니다 레코드 유형은 '국가'로 이름 붙였습니다 ISO 코드는 독특하게 국가를 식별하기 때문에 제 레코드 유형에 포함시키는 게 중요하죠
저는 이 정보를 저장하기 위해 국가 이름을 넣어서 국가 유형 레코드를 만들었습니다
특정 국가의 동전에 대한 레코드 유형도 있는데 이 두 가지를 서로 연결시키고자 합니다 동전의 레코드 유형은 동전과 국가간 관계를 저장합니다
레코드 유형과 관계가 결합해 스키마를 만듭니다 이런 요소들의 현재 상태를 고려해서 스키마의 현재 버전을 만들 수 있죠 앱을 개발하면서 여러분의 스키마를 발전시키고 앱의 수명이 다할 때까지 여러 스키마를 사용할 수 있습니다
제 앱의 스키마가 데이터 구조를 만드는 동안 저는 iCloud에 저장하려고 하는데 데이터는 앱 컨테이너에 저장됩니다 컨테이너는 고유한 식별자를 가지며 개발자 팀과 연관됩니다 CloudKit로 작업할 때 두 가지를 염두에 둬야 합니다 개발 환경은 사용자의 방해를 받지 않고 변화를 주기에 안전한 장소입니다 여기에서 스키마의 변경 사항을 실험하고 개발해야 합니다 사용자들이 앱과 상호 작용할 때 사람들은 생산 환경에서 상호 작용하게 됩니다 생산 환경에는 여러분의 앱을 위한 실시간 데이터가 포함됩니다 이제 CloudKit가 데이터를 저장하는 법을 살펴봤으니 CKTool JS를 구성하는 법을 설명해 드릴게요 CKTool JS는 iCloud와 연결되기 때문에 몇 가지 정보를 수집해야 합니다 올바른 컨테이너로 작업하는 법을 알고 스크립트가 작업을 수행할 수 있도록 하는 거죠
작업하려는 컨테이너에 사용할 팀 ID와 컨테이너 ID가 필요합니다 스키마를 다루려면 관리 토큰이 필요하고 스크립트가 데이터에 접근하려면 사용자 토큰도 필요합니다 모든 값은 CloudKit Console에서 얻을 수 있습니다 스크립트를 실행할 환경도 개발 환경인지 운영 환경인지 여러분이 정해야 합니다 앞으로 예시에서는 개발 환경을 예로 들겠습니다 CKTool JS를 구성할 때마다 다음 값들이 필요합니다 예를 들어, 제가 Node.js용 스크립트를 입력 중이라고 치죠 CKTool JS에서 사용할 객체와 함수를 불러옵니다 이 경우에 CommonJS를 사용해서 기호를 불러올 수 있습니다 구성 정보를 다 수집했으면 그 정보를 저장할 객체를 생성합니다 인증 토큰을 저장하기 위해 객체를 생성하고 관리 토큰이나 사용자 토큰을 보관합니다 팀 ID와 컨테이너 ID, 환경이 CKTool JS에 전달된 공통 값으로 이 값을 보관할 객체를 생성할 수 있습니다 createConfiguration 팩토리 함수로 iCloud와 어떻게 연결하는지 CKTool JS에 알리는 구성 객체를 인스턴스화합니다 createConfiguration은 플랫폼 종속형입니다 이 경우에 Node.js에 대한 적절한 구성이 반환됩니다 타킷 패키지에서 불러온 함수기 때문이죠 그다음 이전에 선언한 보안 객체와 구성 객체를 전달해 API 객체를 초기화합니다 API 객체에는 iCloud와 연결되는 비동기 메서드가 포함되어 있습니다 이제 여러분의 스크립트에서 CKTool JS를 사용할 수 있습니다 이번에는 CKTool JS를 이용해서 컨테이너의 스키마를 관리하는 법을 알아보죠 제 앱에 다음과 같은 정보를 저장하고자 합니다 '2007년에 발행된 미국 10센트 동전' 이 동전은 구리와 니켈로 구성돼 있으며 가치는 미국 달러의 1/10입니다 이 데이터를 어떻게 저장할지 생각한 후에 동전의 구성에 대한 정보를 저장하기로 했습니다 동전에 대한 다른 세부 사항과는 별개의 레코드로 남기는 거죠 그래서 저는 10센트짜리 동전에서 구리 함유율과 니켈 함유율을 별개로 저장했습니다 제 컨테이너의 스키마에서 두 가지의 레코드 유형을 찾았습니다 '동전' 유형은 국가별 참조와 발행 연도, 액면 가격이 참조로 되어 있습니다 '구성 요소' 레코드 유형은 동전에 대한 설명이 저장되어 있는데 동전을 만든 재료와 그 비율이 적혀 있죠 이제 제 앱의 스키마를 정했으니 CloudKit Schema Language에 텍스트 파일을 생성할 수 있습니다 스키마 파일에는 .ckdb 확장자를 사용해야 합니다
CloudKit Schema Language에 대한 자세한 내용은 '워크플로우에 텍스트 기반 스키마 통합' 문서를 참고하세요
컨테이너를 위해 생성한 스키마 파일은 CKTool JS를 사용해 적용합니다 새로운 스키마를 적용하기 전에 컨테이너의 개발 스키마를 재설정해서 생성 중인 스키마와 맞춰야 합니다 resetToProduction 메서드를 통해 이 작업을 수행할 수 있습니다 이전에 선언했던 defaultArgs 객체를 전달해 이 메서드를 호출합니다 스키마가 생성 중이 아니라면 모든 레코드 유형이 삭제됩니다 그렇지 않으면 개발 스키마가 생산 환경 상태로 돌아갈 겁니다 이건 비동기 호출이기 때문에 메서드는 약속 객체를 반환합니다
CKTool JS에는 컨테이너 스키마를 내보내고 불러올 수 있는 메서드가 있습니다 exportSchema와 importSchema 메서드로 작업을 수행할 수 있고 컨테이너의 관점에서 이름이 지정됩니다 exportSchema 메서드를 이용해 컨테이너에서 내보낼 스키마를 다운받고 importSchema 메서드를 이용해 컨테이너에 불러올 스키마를 업로드합니다 이걸 함께 사용하면 스키마의 진화를 관리할 수 있죠
컨테이너에 스키마를 적용하는 헬프 함수를 만들 수 있습니다 우선, CKTool JS에서 File 객체를 불러옵니다 그리고 Node.js에서 fs와 path 모듈을 불러옵니다 이제 다음을 수행할 비동기 함수를 정의하세요 그러면 Node.js 버퍼에서 스키마 파일의 내용을 판독합니다 업로드할 CKTool JS File 인스턴스를 생성합니다 마지막으로 importSchema를 이용해 콘텐츠를 서버에 업로드합니다 이전에 선언된 defaultArgs 객체는 importSchema 메서드로 전달된다는 걸 기억하세요 이제 그것들을 합칠 수 있습니다 스키마를 불러오는 헬퍼 함수와 resetToProduction 메서드는 동시에 실행되지 않기 때문에 순서대로 실행되는지 봐야 합니다 그렇게 하기 위해서 프로미스를 연결합니다 오류가 일어나면 프로미스는 거부합니다 CKTool JS가 가진 관리 능력 외에도 데이터를 읽고 입력할 수도 있습니다 CKTool JS 레코드에 사용되는 필드 값은 서버로 전송되기 전에 클라이언트 측에서 검사하는 레코드 유형과 범위입니다 전달된 값이 올바른 값이 아니거나 값의 허용 범위를 벗어난 경우에는 예외가 발생합니다 JavaScript에서 표현할 수 없는 큰 숫자의 경우에는 CKTool JS 유형을 대신 사용할 수 있습니다 숫자를 CKTool JS Int64에 강제로 적용하려면 toInt64 함수를 사용하세요 이중 부동 소수점 값을 강제 적용하려면 toDouble 함수를 사용하면 됩니다 TypeScript를 입력하는 경우 강제 함수를 쓰지 않으면 컴파일러가 잘못된 값을 지정합니다
CKTool JS 레코드의 필드 값은 필드 값 팩토리 함수를 사용해 생성합니다 2007년에 발핸된 동전이라면 그 값을 makeRecordFieldValue.int64 팩토리 함수에 전달하고 Int64를 포함하는 레코드 필드 값을 생성합니다 팩토리 함수가 전달된 값에서 레코드 필드 값을 생성할 수 없는 경우엔 예외로 간주합니다
레코드에서 작동하는 메서드에 보내는 공통 값을 보관하는 객체를 생성했습니다 컨테이너ID와 환경 데이터베이스 유형, 구역 이름은 필요한 경우가 많아서 databaseArgs 객체에 포함시켰습니다 레코드를 쿼리하기 위해서 queryRecords 메서드를 사용합니다 이 과정을 쉽게 하기 위해 핼퍼 함수를 생성해서 고유한 3자리 ISO 코드와 일치하는 국가를 찾습니다 이 경우 databaseArgs 객체의 콘텐츠를 전달합니다 쿼리를 포함하는 본문과 함께 말이죠 쿼리 객체의 경우에는 recordType 값뿐 아니라 단일 필터 객체까지 지정합니다 필터 객체는 해당 국가의 ISO 코드 세 자리가 함수가 찾고 있는 것과 동일한 쿼리라는 걸 보여 줍니다 성공적으로 찾은 경우에는 발견한 레코드의 컬렉션은 response.result.records 프로퍼티에 들어갑니다 이 컬렉션에서 첫 번째 객체를 반환합니다
원시값을 createRecord가 처리하는 필드 값으로 변환하기 위해 makeCoinFieldValues라는 헬퍼 함수를 사용합니다 필드 값으로 변환할 제 동전의 원시 프로퍼티에 대해 적절한 레코드 필드 팩토리 함수를 호출합니다 하지만 국가 필드는 레퍼런스를 생성해야 합니다 동전 레코드에서 해당하는 국가 레코드로 전달된 내용으로 레퍼런스를 생성합니다
동전 레코드 필드 값을 취하는 헬퍼 함수를 생성하고 createRecord 요청을 서버로 보냅니다 이 함수에서는 전에 선언한 databaseArgs 콘텐츠와 본문을 전달합니다 딕셔너리 본문에는 recordType와 필드 값이 포함되어 있습니다 변환에 성공했다면 response.result.record가 반환됩니다
헬버 함수를 호출하기 전에 이 동전에 참고할 올바른 국가의 기록을 불러와야 합니다 앞서 정의한 국가 쿼리 함수를 사용합니다 그다음 필드 값 딕셔너리를 전달해 coinCreateRecord를 호출합니다 미리 작성해 놓았던 makeCoinFieldValues 헬퍼 함수로 산출된 값이죠 원시 코인 값은 해당 헬퍼 함수로 전달됩니다 이러면 비동기적 레코드가 생성되고 새로운 레코드가 반환됩니다
레코드를 업데이트하기 위해 updateRecord 메서드를 사용합니다 이 헬퍼에 전달된 필드에서 동전과 일치하는 레코드 이름을 업데이트할 헬퍼 함수를 생성합니다 그다음 databaseArgs 객체의 콘텐츠와 레코드 네임 레코드 유형과 새 레코드 필드 값으로 updateRecord를 호출합니다 성공하면 업데이트된 레코드가 제가 헬퍼 함수로 반환했던 response.result.record 프로퍼티로 들어갑니다
아까 만든 동전 레코드를 업데이트하기 위해 헬퍼 함수를 호출해 레코드 이름과 필드 값을 전달합니다 makeCoinFieldValues로 필드 값을 생성합니다
레코드를 삭제하려면 API 객체에서 deleteRecord 메서드를 호출합니다 databaseArgs 객체의 콘텐츠를 전달하고 삭제할 레코드의 레코드 네임도 함께 전달합니다 CKTool JS에 대해 알아 가는 시간이었길 바랍니다 여러분도 직접 해 보세요 자동화 및 툴링 목적에 맞게 CKTool JS를 구성합니다 스키마를 재설정하고 불러옵니다 JavaScript를 사용해 데이터를 읽고 쓸 수도 있죠 통합 시나리오에서 CKTool JS를 사용하려면 GitHub에 있는 CloudKit 샘플 보고서를 확인해 보세요 자세한 건 developer.apple.com에서 CKTool JS 문서를 참고하세요
-
-
6:43 - Create security and default arguments objects
// Create security object and setup default args const { CKEnvironment } = require("@apple/cktool.database"); const security = { "ManagementTokenAuth": "<YOUR_MANAGEMENT_TOKEN>", "UserTokenAuth": "<YOUR_USER_TOKEN>" }; const defaultArgs = { "teamId": "<YOUR_TEAM_ID>", "containerId": "<YOUR_CONTAINER_ID>", "environment": CKEnvironment.DEVELOPMENT };
-
7:17 - Create configuration and API objects
// Create configuration and API objects const { createConfiguration } = require("@apple/cktool.target.nodejs"); const { PromisesApi } = require("@apple/cktool.database"); const configuration = createConfiguration(); const api = new PromisesApi({ "configuration": configuration, "security": security });
-
10:00 - Reset to production and import schema
// Create a function to apply a schema const { File } = require("@apple/cktool.target.nodejs"); const fs = require("fs/promises"); const path = require("path"); const importMySchema = async () => { const schemaPath = "<YOUR_SCHEMA_FILE>.ckdb"; const buffer = await fs.readFile(schemaPath); const file = new File([buffer], schemaPath); await api.importSchema({ ...defaultArgs, "file": file }); } // Chain the calls api.resetToProduction(defaultArgs) .then(() => importMySchema());
-
11:36 - Factory functions
// Create fields with factory functions. const { makeRecordFieldValue } = require("@apple/cktool.database"); const value = makeRecordFieldValue.int64(2007);
-
12:02 - Create database arguments object
// Create a database arguments object. const { CKDatabaseType, CKEnvironment } = require("@apple/cktool.database"); const databaseArgs = { "containerID": "<YOUR_CONTAINER_ID>", "environment": CKEnvironment.DEVELOPMENT, "databaseType": CKDatabaseType.PRIVATE, "zoneName": "_defaultZone" };
-
12:16 - Query for records
// Define helper function for querying records const { CKDBQueryFilterType } = require("@apple/cktool.database"); const countryQueryRecordForCountryCode3 = async (countryCode3) => { const response = await api.queryRecords({ ...databaseArgs, "body": { "query": { "recordType": "Countries", "filters": [{ "fieldName": "isoCode3", "fieldValue": makeRecordFieldValue.string(countryCode3), "type": CKDBQueryFilterType.EQUALS }] } } }); return response.result.records[0]; }
-
12:58 - Create field values
// Define a helper function for creating field values const { makeRecordFieldValue, CKDBRecordReferenceAction } = require("@apple/cktool.database"); const makeCoinFieldValues = ({ countryRecordName, issueYear, nominalValue }) => ({ "country": makeRecordFieldValue.reference({ recordName: countryRecordName, action: CKDBRecordReferenceAction.DELETE_SELF }), "issueYear": makeRecordFieldValue.int64(issueYear), "nominalValue": makeRecordFieldValue.double(nominalValue) });
-
13:26 - Create a record
// Define helper method for creating coins const coinCreateRecord = async (fields) => { const response = await api.createRecord({ ...databaseArgs, "body": { "recordType": "Coins", "fields": fields }, }); return response.result.record; }
-
13:48 - Call record creation helper method
// Call coin creation method with field values const countryRecord = await countryQueryRecordForCountryCode3("USA"); const coinRecord1 = await coinCreateRecord( makeCoinFieldValues({ "countryRecordName": countryRecord.recordName, "issueYear": 2007, "nominalValue": 0.10 }) );
-
14:16 - Define update record helper function
// Define helper method for updating coins. // Note that recordChangeTag is required const coinUpdate = async (recordName, recordChangeTag, fields) => { const response = await api.updateRecord({ ...databaseArgs, "recordName": recordName, "body": { "recordType": "Coins", "recordChangeTag": recordChangeTag, "fields": fields } }); return response.result.record; }
-
14:44 - Update a record with field values
// Call coin updating method with field values. // Note that the recordChangeTag of the record // to update is passed to the coin update function. const countryRecord = await countryQueryRecordForCountryCode3("USA"); const updatedCoinRecord1 = await coinUpdate( coinRecord1.recordName, coinRecord1.recordChangeTag, makeCoinFieldValues({ "countryRecordName": countryRecord.recordName, "issueYear": 2010, "nominalValue": 0.10 }); );
-
14:57 - Delete a record
// Deleting a record await api.deleteRecord({ ...databaseArgs, "recordName": coinRecord1.recordName });
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.