스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
Core Data 및 CloudKit 사용 최적화
Core Data 및 CloudKit 구현을 최적화할 수 있는 개발 주기의 세 부분을 함께 살펴보시기 바랍니다. 앱의 아키텍처 및 기능 세트를 분석하여 가정을 검증하고, 대용량 데이터 세트를 수집한 후 동작의 변화를 탐색하며, 작업 흐름을 개선하기 위해 실행 가능한 피드백을 얻을 수 있는 방법을 보여드리겠습니다. 이 세션을 최대한 활용하려면 데이터 모델을 CloudKit으로 동기화하는 방법을 숙지하는 것이 좋습니다.
리소스
관련 비디오
WWDC22
WWDC21
WWDC20
WWDC19
-
다운로드
안녕하세요, Nick Gillett입니다 Apple Core Data 팀의 엔지니어죠 이번 세션에서는 개발자 도구를 이용하여 NSPersistentCloudKitContainer를 사용한 앱에 관해 알아보죠 앱을 더 생산적이고 교육적인 방법으로 조사할 수 있는 자세한 방법을 알아보고
애플리케이션 작동을 분석하는 도구를 사용할 겁니다 NSPersistentCloudKitContainer를 이용하여 자세하고 실질적인 피드백을 제공하는 방법도 살펴보죠
저는 엔지니어링을 물의 순환이라고 생각합니다 일단 기능을 개발하기 전에 그 기능이 존재하는 공간에 대해 살펴보고 여기서 알게 된 사실을 바탕으로 각종 도구와 테스트를 이용하여 재현할 수 있는 환경에서 작업을 분석합니다 마지막으로, 동료들과 함께 결과를 검토한 뒤 피드백을 받죠 이러한 주기의 목표는 제가 작업하면서 배운 것들을 오랫동안 기록할 수 있죠 Apple 플랫폼에는 훌륭한 도구가 많은데 Xcode, Instruments, XCTest로 제가 배운 걸 기록합니다 이런 도구로 각종 진단 정보를 수집하면 실질적인 피드백을 제공할 수 있죠
이 세션은 수년 전 지식도 많이 다룹니다 NSPersistentCloudKitContainer와 오늘 보여 드릴 Core Data CloudKit 샘플 앱은 'CloudKit과 Core Data로 데이터를 공유하는 앱 만들기'와 'CloudKit으로 Core Data 사용하기'에서 상세히 다뤘죠 Xcode와 Instruments를 사용하여 테스트를 시행하는 방법과 Device Organizer로 기기 데이터를 검색하는 법도 보여 드리죠 필요하시다면 다음 세션을 시청하십시오 'Instruments 시작하기'와 'Xcode Organizer로 성능 문제 진단하기'를 통해 두 가지 중요한 도구에 관해 더 알아보세요 그러면 첫 번째 주기인 조사부터 시작하죠 저에게 조사의 주요 목표는 배우는 겁니다 프로그램의 작동 원리에 관해 제가 세운 모든 가정을 의심하고 확인하려고 하죠 이런 질문을 합니다 이 버튼을 탭 하면 어떻게 될까? 저장소에 저장 시 NSPersistent CloudKitContainer가 동기화될까? 데이터 규모가 크면 프로그램의 메모리가 바닥날까? Core Data의 관점에서 이 모든 질문에 대한 답은 프로그램이 작업하는 데이터의 영향을 받습니다 Core Data CloudKit 샘플 앱은 이 데이터 모델을 사용하죠
제목과 내용에 텍스트 영역이 있는 게시물들을 관리합니다 게시물은 대개 이미지인 첨부 파일과 관련이 있는데 크기가 클 수 있죠
따라서 ImageData는 대응 관계에 따라 저장하여 필요할 때 로딩합니다 저는 그 데이터 세트에 관한 조사에 집중하여 데이터의 형태, 구조, 분산을 바꿨을 때 샘플 프로그램이 어떻게 되는지 알아보죠
샘플 프로그램은 출시 이후 조사 방법이 내장돼 있었습니다 '게시물 1,000개 생성' 버튼은 말 그대로의 작업을 하죠 버튼을 누르면 샘플 데이터를 생성하여 짧은 제목의 게시물 1,000개를 만듭니다 게시물 테이블 뷰가 이 정도의 데이터는 쉽게 처리하죠 다음으로 제가 하는 질문은 모양과 크기가 다른 데이터를 어떻게 조사하는지입니다 '게시물 1,000개 생성' 버튼은 제가 붙인 용어인 알고리즘적 데이터 생성기를 실행하는데요 알고리즘적 데이터 생성기는 미리 정한 규칙을 따릅니다 '객체 1,000개 삽입'이나 '모든 영역에 값 넣기'나 '모든 영역의 값 지우기'처럼요 우리도 알고 보면 데이터를 생성합니다 코드나 SQL에서 특정한 데이터 세트를 만들거나 애플리케이션과 직접 상호작용하여 생성한 데이터 세트를 보관하여 나중에 사용하거나 분석하죠 큰 데이터 세트를 조사하기 위해 새 데이터 생성기를 정의합니다 바로 LargeDataGenerator로 이 안에 단일 메서드인 generateData를 넣어 새로운 데이터 세트를 만들죠 2개의 for 루프로 60개의 게시물을 생성할 수 있고 각각 11개의 첨부된 이미지와 연관돼 있습니다 그러면 총 660개의 이미지죠 이미지의 평균 사이즈가 10-15MB이므로 생성된 데이터 세트는 10GB의 데이터를 소모합니다 간단한 인터페이스로 데이터 생성기를 이런 테스트에서 쉽게 호출할 수 있죠 이 코드 한 줄이 테스트를 위해 10GB의 데이터를 생성합니다
또한 테스트에 검증 메서드를 추가하여 데이터 생성기가 제대로 동작하는지 확인할 수 있죠 각 게시물이 실제로 11개의 이미지를 첨부하는지를요
물론 데이터를 동기화하지 않으면 NSPersistentCloudKitContainer와 관련된 세션이 아니었을 겁니다 새로운 테스트를 만들어 그걸 해 보죠
NSPersistentCloudKitContainer가 사용할 인스턴스가 필요합니다 이를 도와줄 헬퍼 메서드를 만들었죠 다음은 LargeDataGenerator를 사용하여 컨테이너를 원하는 데이터로 채웁니다 마지막으로 컨테이너가 데이터 내보내기를 기다리죠 이 테스트를 위해 20분간 크기가 큰 데이터 세트의 업로드를 기다립니다
매의 눈을 가진 분이라면 이 테스트가 여러 종류의 이벤트를 기다린다는 걸 아시겠죠 여기서 컨테이너를 만들 때 컨테이너가 설정을 마치기를 기다립니다 그리고 여기서는 헬퍼 메서드로 XCTestExpectations를 만들어 컨테이너에서 이벤트를 내보내죠 더 자세히 살펴봅시다
이 메서드는 원하는 이벤트 유형과 NSPersistentCloudKitContainer의 인스턴스를 인수로 사용하죠 컨테이너 저장소에 저장할 때마다 기댓값을 생성하는데 XCTestCase의 expectationFor Notification 메서드를 사용하여 NSPersistentCloudKitContainer의 eventChanged 알림을 관찰하죠 알림 핸들러 부분에서는 수신 중인 이벤트가 이 기댓값에 대한 저장 유형이 맞다는 걸 인증하고 endDate가 nil이 아님을 확인하는 것으로 완료합니다 이 기술을 사용하면 NSPersistentCloudKitContainer의 이벤트와 테스트 제어 지점을 밀접하게 연관 지을 수 있죠 테스트에서 방금 내보낸 데이터를 가져올 새 컨테이너를 추가합니다 이 기술은 비법을 사용하죠 빈 저장 파일이 있는 NSPersistentCloudKitContainer의 인스턴스를 생성합니다 이를 통해 테스트에서 NSPersistentCloudKitContainer의 첫 번째 임포트 파일로 기기에서 모든 데이터를 받을 때 어떻게 되는지 알 수 있죠 테스트도 좋지만 저는 프로그램에서 데이터 세트가 어떻게 작동하는지 느껴 보고 싶습니다 그러려면 샘플 프로그램처럼 데이터 생성기를 사용자 인터페이스에 바인딩할 수 있죠 대규모 데이터 생성 버튼을 탭 하면 데이터 생성기가 데이터 세트를 만듭니다 다른 기기에서 테이블 뷰에 데이터가 생성되는 게 보이는데 NSPersistentCloudKitContainer가 생성 데이터를 다운로드 받는 거죠 각 포스트를 탭 하면 첨부 파일이 다운로드되고 조금씩 데이터가 채워지는데 이는 애플리케이션에서 기대하는 동작입니다 이 사용자 인터페이스는 알림 제어기를 위주로 작동하죠 LargeDataGenerator의 간단한 인터페이스로 코드 두 줄만 작성하면 새로운 알림을 추가할 수 있습니다 명확하고 간결하고 이해하기 쉽죠
이 섹션에서는 데이터 생성기의 개념을 이용해 프로그램의 원리를 살펴봤습니다 데이터 생성기는 원하는 방식으로 사용할 수 있죠 테스트나 커스텀 UI로 사용할 수도 있고 커맨드 라인 인자를 사용하거나 특정 사례에 맞는 모든 방식을 사용할 수 있습니다 이제 프로그램에 데이터를 채우는 방법을 알았으니 프로그램 동작을 어떻게 바꾸는지 분석하죠 이 섹션에서는 데이터 세트가 클 때 프로그램이 어떻게 동작하는지 분석할 겁니다
구체적으로는 Instruments로 LargeDataGenerator로 생성된 데이터 세트의 시간과 메모리 복잡도를 분석하죠 그리고 시스템 로그에 있는 다양한 정보를 살펴보겠습니다 거기서 볼 수 있는 작업 기록에는 NSPersistentCloudKitContainer와 CloudKit, 시스템 스케줄러와 푸시 알림에 관한 것들이 있죠 Instruments부터 시작하겠습니다 제가 테스트를 좋아하는 이유 중 하나는 Xcode로 테스트의 동작을 쉽게 분석할 수 있어서죠 제 테스트 사례에서는 오른쪽 클릭을 한 뒤 '프로필'을 선택합니다 Xcode가 테스트를 구축하고 자동으로 Instruments를 실행하죠 저는 Time Profiler를 더블 클릭하여 작업 중 테스트가 어디서 시간을 소모하는지 확인합니다
녹화 버튼을 누르면 Instruments가 프로그램을 실행하여 선택한 테스트를 수행하죠 이 테스트는 실행 시간이 꽤 오래 걸립니다 조금 건너뛰어서 이유를 보죠 Instruments가 이미 메인 스레드를 선택했고 오른쪽에서 테스트의 가장 무거운 스택이 나와 있습니다
조금 읽기 쉽게 바꾸죠
됐습니다 아래로 스크롤 하면 LargeDataGenerator가 섬네일 생성에 많은 시간을 쓰고 있군요 이게 버그인지 기능인지 어떻게 확인할까요?
LargeDataGenerator에는 이 코드에서 각 첨부 파일의 섬네일을 생성합니다 하지만 프로그램 데이터 모형에서 섬네일이 특별하다는 걸 알죠 연관된 imageData에서 필요시 연산합니다 따라서 이 줄은 불필요하고 데이터 생성기가 시간을 많이 낭비하고 있죠 그냥 지워도 됩니다 테스트 성능이 어떻게 달라지는지 볼게요 데이터 생성기를 업데이트하여 앱을 다시 빌드한 뒤 Instruments에서 테스트를 다시 실행할 수 있죠 별다른 변화는 없어 보이지만 몇 초 뒤에 테스트가 끝납니다 이전보다 훨씬 빨라졌죠 테스트가 어디에 시간을 썼는지 볼게요
오른쪽을 보면 가장 무거운 스택 트레이스는 저장소에 이미지를 저장하는 거죠 이런 규모의 데이터를 처리하는 테스트라면 정상적인 결과입니다
하나만 수정했는데 generateData 테스트 시간이 이 화면에서 이 화면으로 줄어들었죠 시간이 1/10밖에 안 걸렸습니다 이런 식의 테스트 분석이 항상 버그를 잡는 건 아니죠 어떤 때는 특정 데이터 세트를 작업할 때 앱이 어디서 시간을 쓰는지 알 수 있습니다 하지만 어느 쪽이든 중요한 교훈을 얻죠
이렇게 Time Profiler를 이용하여 데이터 세트의 어느 부분에서 시간을 쓰는지 알 수 있습니다 데이터 세트의 크기가 커서 테스트가 메모리를 얼마나 사용하는지 궁금하군요 Allocations Instrument를 이용해 봅시다 Xcode로 Instruments를 실행하여 테스트를 프로파일링하죠 Time Profiler Instrument 대신 Allocations를 더블 클릭하고
녹화를 클릭합니다
비록 이 테스트는 빠르게 실행됐지만 메모리를 많이 사용하네요 10GB가 넘었죠 그 말은 데이터 세트 전체가 테스트 실행 중 메모리에 있다는 겁니다 이유를 알아보죠
다양한 할당을 선택하여 살펴보겠습니다 아래쪽 창을 보면 크기가 큰 할당이 보이죠 더 자세히 알아보기 위해 이걸 클릭하고 테스트에 할당된 커다란 데이터 블록을 클릭하세요 이 블록은 할당됐지만 거의 2초간 풀려나지 않았죠 테스트에서는 평생과 다름없죠 왜 이렇게 오래 살아 있었을까요?
오른쪽의 스택 트레이스를 펼쳐 보면 됩니다
경험상 할당과 반환 스택 트레이스는 이 오브젝트가 CoreData에 의해 결점이 생겼고 managedObjectContext가 작업을 마쳤을 때 풀어 주었죠 그건 오브젝트가 fetch, autoreleasepool 또는 테스트 오브젝트에 의해 유지됐다는 의미입니다
문제가 되는 코드는 검증 부분에 있었군요 첨부 파일에서 이미지를 로딩하여 검증합니다 하지만, 이렇게 하면 첨부 파일과 연관된 이미지 데이터를 managedObjectContext에 등록하죠 이를 해결하는 방법은 여러 가지입니다 예를 들어 테이블 뷰에서 배치된 fetch를 이용하여 테이블을 스크롤 해서 지나가면 이미지를 풀어 주는 거죠 하지만 테스트 실행이 빨라 효율적이지 않습니다 방법을 바꿔야 하죠 게시물 대신 첨부 파일을 fetch 하여 검증할 수 있습니다 objectID만 fetch 한다면 managedObjectContext는 지시가 없으면 로딩된 오브젝트를 캡처하지 않겠죠
NSManagedObjectContext의 objectWithID 메서드를 이용하여 검증 때 첨부 파일을 fetch 할 수 있습니다 10개 첨부 파일을 검증할 때마다 context를 초기화하여 캐싱된 상태와 연관된 메모리를 풀어 주죠
이렇게 바꾼 걸 다시 실행하면 결과적으로 메모리 소비가 예상 및 수정 가능한 수준입니다 게다가 검증 작업이 사용하는 메모리가 LargeDataGenerator가 첨부할 때보다 줄어들었죠
수정 작업의 원리를 알아보기 위해서 특정한 할당을 살펴봅시다
먼저 작업할 할당 범위를 선택하죠 그리고 살펴볼 특정 크기를 선택합니다
소멸된 오브젝트를 활성화하여 이때 풀려난 걸 찾은 뒤 특정 할당을 선택하여 살펴볼 수 있죠
오른쪽에 Instruments가 할당 스택 트레이스를 보여 주지만 어디서 풀려났는지 보고 싶어 반환 이벤트를 선택합니다 저는 이 스택 트레이스의 의미를 아는데 NSManagedObjectContext가 이 뭉치를 유지하던 오브젝트를 비동기적으로 반환하여 메모리가 확보됐다는 걸 알죠 이 기법으로 테스트의 수위를 결정할 수 있고 메모리가 적은 시스템에도 실행되게 할 수 있습니다
Instruments와 테스트를 혼합하여 이 테스트에 이상적이지 않은 동작이 포함되었음을 발견했죠 해당 동작을 직접 해결하는 표적화된 수정 작업을 한 뒤 결과를 확인했습니다 또한, 시스템 로그는 프로그램과 시스템에 관한 많은 정보를 포함합니다 CloudKit, 스케줄러 푸시 알림처럼요 이제 MacBook Pro와 iPhone 사이에 하나의 게시물을 동기화할 겁니다 Mac에 새로운 게시물을 삽입하고 짧은 제목을 붙인 뒤 iCloud에 업로드하는 거죠 시스템 로그는 여러 이벤트를 포착합니다
제 iPhone과 동기화할 때 중간 단계를 포착하더라도 시스템 로그가 그와 관련된 이벤트를 포착하죠 MacBook Pro에서는 NSPersistentCloudKitContainer가 프로그램 프로세스인 CoreData CloudKitDemo에서 작동하죠 데이터가 저장소에 쓰일 때 dasd라는 시스템 서비스에 해당 데이터를 CloudKit로 내보내도 되는지 묻습니다 그래도 되면 dasd가 NSPersistentCloudKitContainer에 작업을 지시하죠 NSPersistentCloudKitContainer는 작업을 예정할 때 cloudd라는 프로세스를 사용하여 수정된 오브젝트를 CloudKit로 내보냅니다 Console 앱으로 각 프로세스의 로그를 관찰할 수 있죠 애플리케이션 로그는 애플리케이션 프로세스인 CoreDataCloudKitDemo를 확인합니다 여기에 내보내기가 완료되는 걸 선택했죠 예정 작업 로그를 보려면 dasd 프로세스의 로그나 애플리케이션의 저장 장치 로그를 봐야 합니다 여기서는 앱의 사적인 저장 공간의 내보내기 작업의 시작을 선택했죠 이 로그를 더 자세히 살펴봅시다 dasd를 이용해 NSPersistentCloudKitContainer로 생성된 작업은 특정 양식을 따르죠 작업 식별자는 특정한 프리픽스로 구성되는데 NSPersistentCloudKitContainer가 작업이 저장될 공간에 맞는 저장 식별자와 함께 사용합니다 dasd 로그에 포함되는 정보는 해당 작업의 실행 여부에 대한 서비스의 결정 방식이죠 프로그램의 작업 수행에 영향을 미치는 정책은 최종 결정과 함께 로그에 나타납니다
마지막으로, cloudd 프로세스가 CloudKit로부터 정보를 로그하고 해당 로그를 제가 작업 중인 컨테이너 식별자로 필터하죠 여기서는 연관된 기록 수정 작업을 선택하여 앞에서 언급한 것처럼 내보냅니다
수신하는 기기에 수정 사항을 임포트 하면 관찰해야 할 프로세스가 하나 더 있죠 apsd 프로세스는 푸시 알림을 수신하고 애플리케이션으로 전달합니다 이를 통해 NSPersistentCloudKitContainer가 내보내기 절차와 비슷한 일련의 작업을 시작하죠 dasd에 임포트를 수행할 시간을 요청하고 cloudd와 함께 CloudKit에서 새 오브젝트를 fetch 하고 로컬 저장 장치로 임포트 합니다
apsd는 앱의 푸시 알림을 수신하면 로그를 남기고 이 로그는 중요한 세부 사항을 여러 개 기록하죠 로그 메시지에는 컨테이너 식별자를 포함하며 푸시 알림을 발동한 구독 이름과 구역 식별자도 포함합니다 NSPersistentCloudKitContainer가 모두 관리하며 com.apple.coredata.cloudkit 프리픽스로 시작합니다
Console 앱도 좋지만 Mac으로 개발할 때는 Terminal의 log stream 명령어를 사용하여 앱과 함께 로그를 표시하죠
각 서술자에 대한 터미널 창이나 탭을 여는데 첫 번째는 애플리케이션입니다 다음은 cloudd의 로그로 CloudKit 서버 활동을 보죠 그다음은 apsd로 푸시 알림 로그가 나옵니다 마지막으로 dasd를 통해 NSPersistentCloudKitContainer가 어떤 작업을 예정하는지 알아보죠 이러한 서술자는 Console 앱에서 쿼리를 안내하기에도 좋습니다
우리가 사용하는 기기에 관한 정보가 정말 많죠 이를 찾고 분석하는데 사용하는 도구를 아는 게 어렵습니다 Instruments만 사용해도 런타임이나 메모리 성능 등 많은 정보를 알 수 있죠 시스템 로그가 포착하는 이벤트는 프로그램의 작업을 묘사하고 기저에서 시스템이 하는 작업도 알려 줍니다 개발 주기의 마지막 단계는 조처할 수 있는 피드백을 수집하고 제공하는 거죠 이 섹션에서는 기기에서 진단 정보를 수집하는 방법을 보여 드릴게요 우리의 목표는 이 정보를 사용하여 특정 목표와 관련된 실질적 피드백을 생성하는 겁니다 이 방법을 사용하면 본인과 고객의 기기 등 어디서나 피드백을 얻을 수 있죠 기기에서 진단 정보를 얻는 세 단계가 있습니다 먼저 CloudKit 로그 프로필을 설치해서 문제를 식별할 수 있는 로그 기능을 활성화하고 효과적으로 문제를 분류하죠 다음은 영향을 받은 기기로부터 sysdiagnose를 수집합니다 마지막으로 기기에 물리적으로 접근할 수 있다면 Xcode로 저장소 파일을 수집할 수 있죠 로그 프로필을 설치하려면 개발자 포털의 '프로필과 로그' 페이지로 가세요 CloudKit 프로필을 검색하고 링크를 탭 하여 다운로드합니다 일부 기기에서는 프로필을 설치하라는 알림이 뜨죠 하지만 iOS에서는 설정 앱을 통해 수동 설치해야 합니다
설정에서 다운로드된 프로필을 탭 한 다음 다운로드된 프로필을 탭 하여 설치하세요 단계에 따라 설치를 완료합니다 프로필을 설치한 뒤 기기를 재부팅 하면 반영되죠
기기를 재부팅 한 뒤 포착하려는 작업을 재현하고 sysdiagnose를 사용합니다 sysdiagnose는 특별한 버튼 조합인 키코드를 통해 진행되죠 이는 프로필의 설명 페이지에 나와 있습니다 iPhone에서는 두 볼륨 버튼과 전원 버튼을 2초간 누른 뒤 버튼을 떼면 되죠 그러면 잠시 후 설정에서 sysdiagnose를 볼 수 있습니다 프로필의 설명서 파일에 찾는 방법이 나와 있죠 설정의 '개인 정보 보호'에서 '분석 및 향상' 메뉴에서 '분석 데이터'를 선택하고 sysdiagnose를 찾을 때까지 스크롤 하십시오
sysdiagnose를 탭 한 뒤 공유 버튼을 탭 하면 여러 공유 방법 중에서 선택할 수 있죠 예를 들어, Mac으로 AirDrop 하여 분석할 수도 있습니다 마지막으로, 할 수 있다면 Device Organizer로 Xcode에서 저장 파일을 수집할 수 있죠 이 iPhone에서 파일을 수집하려면 설치된 앱 목록에서 샘플 앱을 클릭하고 공개 버튼을 클릭한 뒤 다운로드 컨테이너를 선택하고 내 다운로드 폴더에 저장합니다
모두 끝났으면 시스템 로그와 저장 파일을 분석할 수 있죠 log stream 명령어에 관해 이미 얘기했지만 sysdiagnose에서 log show 명령어를 통해 sysdiagnose를 볼 수 있습니다 앞에서 언급한 apsd 로그의 서술자를 복사했죠
log show 명령어의 마지막 인자는 사용할 logarchive입니다 아무것도 지정하지 않으면 실행 중인 기기의 시스템 로그를 표시하죠 저는 system_logs.logarchive를 지정하여 sysdiagnose에서 가져온 로그를 읽습니다 예를 들어 정확한 시간대를 지정하여 제가 관심 있는 이벤트가 발생한 시간에 집중할 수 있죠
앞에서 이야기했던 여러 가지 서술자를 혼합하여 프로그램과 관련된 작업을 통합 로그로 만들 수도 있는데 여기 애플리케이션 로그에서 시작하여 cloudd 로그와 apsd 로그를 넣고 마지막으로 dasd 로그를 넣습니다 이 강력한 명령어를 피드백 보고서에 포함하거나 팀원과 공유하여 모든 사람이 특정 로그에 집중하여 분석할 수 있죠
이 세션에서는 프로그램 동작을 분석하는 도구로 데이터 생성기나 Instrument의 분석 프로그램이나 시스템 로그를 사용하여 NSPersistentCloudKitContainer를 사용하는 앱으로부터 실질적인 피드백을 제공하고 수집하는 방법을 다뤘죠
저는 Nick Gillett이고 프레젠테이션을 진행할 수 있어 즐거웠습니다 감사합니다, 운동하며 링을 채우고 멋진 WWDC 보내세요
-
-
4:35 - Define a large data generator
class LargeDataGenerator { func generateData(context: NSManagedObjectContext) throws { try context.performAndWait { for postCount in 1...60 { //add a post for attachmentCount in 1...11 { //add an attachment with an image let imageFileData = NSData(contentsOf: url!)! } } } } }
-
5:07 - Testing a large data generator
class TestLargeDataGenerator: CoreDataCloudKitDemoUnitTestCase { func testGenerateData() throws { let context = self.coreDataStack.persistentContainer.newBackgroundContext() try self.generator.generateData(context: context) try context.performAndWait { let posts = try context.fetch(Post.fetchRequest()) for post in posts { self.verify(post: post, has: 11, matching: imageDatas) } } } }
-
5:33 - Sync generated data in test
func testExportThenImport() throws { let exportContainer = newContainer(role: "export", postLoadEventType: .setup) try self.generator.generateData(context: exportContainer.newBackgroundContext()) self.expectation(for: .export, from: exportContainer) self.waitForExpectations(timeout: 1200) }
-
6:35 - Expectation helper method
func expectation(for eventType: NSPersistentCloudKitContainer.EventType, from container: NSPersistentCloudKitContainer) -> [XCTestExpectation] { var expectations = [XCTestExpectation]() for store in container.persistentStoreCoordinator.persistentStores { let expectation = self.expectation( forNotification: NSPersistentCloudKitContainer.eventChangedNotification, object: container ) { notification in let userInfoKey = NSPersistentCloudKitContainer.eventNotificationUserInfoKey let event = notification.userInfo![userInfoKey] return (event.type == eventType) && (event.storeIdentifier == store.identifier) && (event.endDate != nil) } expectations.append(expectation) } return expectations }
-
7:18 - Import data model in test
func testExportThenImport() throws { let exportContainer = newContainer(role: "export", postLoadEventType: .setup) try self.generator.generateData(context: exportContainer.newBackgroundContext()) self.expectation(for: .export, from: exportContainer) self.waitForExpectations(timeout: 1200) let importContainer = newContainer(role: "import", postLoadEventType: .import) self.waitForExpectations(timeout: 1200) }
-
8:23 - Data generator alert action
UIAlertAction(title: "Generator: Large Data", style: .default) {_ in let generator = LargeDataGenerator() try generator.generateData(context: context) self.dismiss(animated: true) }
-
10:50 - Eagerly generating thumbnail in data generator
func generateData(context: NSManagedObjectContext) throws { try context.performAndWait { for postCount in 1...60 { for attachmentCount in 1...11 { let attachment = Attachment(context: context) let imageData = ImageData(context: context) imageData.attachment = attachment imageData.data = autoreleasepool { let imageFileData = NSData(contentsOf: url!)! attachment.thumbnail = Attachment.thumbnail(from: imageFileData, thumbnailPixelSize: 80) return imageFileData } } } } }
-
11:13 - Lazily generating thumbnail in data generator
func generateData(context: NSManagedObjectContext) throws { try context.performAndWait { for postCount in 1...60 { for attachmentCount in 1...11 { let attachment = Attachment(context: context) let imageData = ImageData(context: context) imageData.attachment = attachment imageData.data = autoreleasepool { return NSData(contentsOf: url!)! } } } } }
-
14:14 - Problematic verifyPosts implementation
func verifyPosts(in context: NSManagedObjectContext) throws { try context.performAndWait { let fetchRequest = Post.fetchRequest() let posts = try context.fetch(fetchRequest) for post in posts { // verify post let attachments = post.attachments as! Set<Attachment> for attachment in attachments { XCTAssertNotNil(attachment.imageData) //verify image } } } }
-
14:49 - Efficient verifyPosts implementation
func verifyPosts(in context: NSManagedObjectContext) throws { try context.performAndWait { let fetchRequest = Attachment.fetchRequest() fetchRequest.resultType = .managedObjectIDResultType let attachments = try context.fetch(fetchRequest) as! [NSManagedObjectID] for index in 0...attachments.count - 1 { let attachment = context.object(with: attachments[index]) as! Attachment //verify attachment let post = attachment.post! //verify post if 0 == (index % 10) { context.reset() } } } }
-
20:41 - Display logs using `log stream`
# Application log stream --predicate 'process = "CoreDataCloudKitDemo" AND (sender = "CoreData" OR sender = "CloudKit")' # CloudKit log stream --predicate 'process = "cloudd" AND message contains[cd] "iCloud.com.example.CloudKitCoreDataDemo"' # Push log stream --predicate 'process = "apsd" AND message contains[cd] "CoreDataCloudKitDemo"' # Scheduling log stream --predicate 'process = "dasd" AND message contains[cd] "com.apple.coredata.cloudkit.activity" AND message contains[cd] "CEF8F02F-81DC-48E6-B293-6FCD357EF80F"'
-
24:36 - Display logs with `log show`
log show --info --debug --predicate 'process = "apsd" AND message contains[cd] "iCloud.com.example.CloudKitCoreDataDemo"' system_logs.logarchive log show --info --debug --start "2022-06-04 09:40:00" --end "2022-06-04 09:42:00" --predicate 'process = "apsd" AND message contains[cd] "iCloud.com.example.CloudKitCoreDataDemo"' system_logs.logarchive
-
25:17 - Provide a predicate to `log show`
log show --info --debug --start "2022-06-04 09:40:00" --end "2022-06-04 09:42:00" --predicate '(process = "CoreDataCloudKitDemo" AND (sender = "CoreData" or sender = "CloudKit")) OR (process = "cloudd" AND message contains[cd] "iCloud.com.example.CloudKitCoreDataDemo") OR (process = "apsd" AND message contains[cd] "CoreDataCloudKitDemo") OR (process = "dasd" AND message contains[cd] "com.apple.coredata.cloudkit.activity" AND message contains[cd] "CEF8F02F-81DC-48E6-B293-6FCD357EF80F")' system_logs.logarchive
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.