본문 바로가기
WWDC

WWDC 21 - Bring Core Data concurrency to Swift and SwiftUI

by vapor3965 2021. 7. 5.

목차


    • 세션보면서 정리한 내용입니다. 해석이 잘못된 경우가 있을수있으니 발견하시면 댓글로 남겨주시면 감사하겠습니다🙏🏻

    https://developer.apple.com/wwdc21/10017

     

    Bring Core Data concurrency to Swift and SwiftUI - WWDC21 - Videos - Apple Developer

    Discover how Core Data is adopting the new concurrency capabilities of Swift 5.5, leading to more concise, efficient, and safe...

    developer.apple.com

     


     

     

    요약

    • Swift5.5의 concurrency를  CoreData에 적용한 perform { } API가 생겼다 
    • Swift스러운 자연스러운 syntax들을 추가했다 ( AttributedType ) 
    • SwiftUI에서는 Lazy stack intialization,  dynamic configruation , section별 fetching기능이 생겼다. 

     


     

     

     

    사용자데이터를 디바이스에 저장하는데있어서 CoreData가 가장적합하다.

    CoreData는 CloudKit과도 공유되어진다. 

    그리고 모든플랫폼에도 사용될수있다.

     

     

    그리고 CoreData는 계속해서 Swift와 잘맞도록 하기위해 API를개선시켜왔다.

     

    이제는 새로운 concurrency model을 지원한다 ? 

     

    어떻게작동하는지 보기위해 지진앱을 예로들어보자 

     

    지진데이터는 USGS로부터 바당오고 CoreData를 이용하여 최근지진들을 저장한다. 

     

     

     

    UI를 그리기위한 View Context와

    USGS로부터 받는 데이터를 저장하기위한 background Context가있고,

    Container, 그리고 USGS의 Json데이터로부터 지진데이터를모은다. 

     

    간단하게 작동원리를 설명하면,

    USGS로부터 받은JSON데이터들을 JSON parser를 통해서 bacgrkoud context를 통하여 managed object 로 변환하여 저장한다.

    그리고 ViewContext는 변화들을 merge하여 UI에 업데이트한다.

     

    작년에, 우리는 데이터를 저장할때, batch operation을 이용하여 성능향상시켰다.

    이번에는 이기능들을 concurrently하게 작동하는지를 초점둘것이다.

     

    우선 데이터를 받고 저장하는방법을 다음과같이 크게 3가지로 나눌수있따.

     

    자 그럼 각단계들을 코드로 표현하면, 

    retrieveRemoteData ( 추상화한메소드 ) 를통해서 데이터를가져오고, 

    processData()를 통해서 데이터를 변환하고, 

    batchInsert한다. 

    하지만 이렇게작성하면 잠재적인 병목현상을 쉽게 상상할수있다.

     

    네트워크를통해서 받아오는작업, convert하는과정도 , import도 모두  비동기적으로 이뤄져야좋을것같다.

     

    그러므로, 비동기적인 메커니즘을 구현하거나, framework의 특정구현을 배워야할 필요가있다.

     

    Core Data의 특정 abstraction을 보자. 

     

    performAndWait는 클로저들을 실행시키는데, 이는 잠재적으로 호출하는쓰레드를 클로저가완료될때까지 block할수있다. 

     

     

    자 이것을 시각화하면 아래와같이표현할수있따.

    Thread1이 실행되다가  performAndWait로인해 Thread1이 blocke당했다. 

     

    만약 클로저가 끝나기까지 기다리지않고싶다면? 

    perform을 사용한다. 

     

     

     

    하지만, 올해 Swift는 새로운 비동기,동시성을위한 API를 발표했고, 이는 CoreData가 좀더 정확하게 API의 의도를 설명할수있도록 해준다.

     

     

     

    await 와 perform을이용하면 이제는 perform이후의 블록들은 기다려진다. 

    즉 await는 perform의 result를 기다리라고 지시한다.

    • 동시성구현을 더이상 내부구현으로 숨기지않아도된다.  ( 아래예를 보면 조금이해돼요!) 
    • 이것의장점은 swift와 더 통합되어있고, 이것덕분에 컴파일러는 자동적으로 많은 동시성버그들을 막을수있다 ( 경쟁상태, 데드락 ) 
    • 심지어 자원들을 더 효율적으로 사용할수있다.  

     

     

     


    자! 그러면 새롭게적용된 perform을 살펴보자! 

     

    이짧은코드가 고잊아히 많은 기능들이 함축되어있는것이다.

    자우선 perform메소드는  return타입으로 제네릭이적용됐다. 

    그리고 async 키워드가추가됐다. 이는 해당 API가 동시성을 채택한다고 알린다.

    그리고 가장눈에띌만한점이 클로저부분이 이제는 에러나 타입을반환한다!

     

    자그럼 사용된예시를 보기전에 기존코드를 보자.

     

    동시성이 구현내부에 숨겨져있기때문에,  에러를 반환하는 방법중 하나는 다음과같이된다. 

    이렇게 불편하게 에러를 먼저 선언하고,

    performAndWait 클로저안에서 실행하는 코드가 에러핸들링을통해 에러를 초기화하고,

    마지막에 에러를 체크한다. 

     

    하지만 perform으로, 비동기적으로 수행될경우에는 더 복잡해진다. 

    completion블록을 따로 사용하여 에러를 반환했다. 

     

    하지만?! await  + perform이라면, 

    에러가발생하면 간단하게 반환하면 된다.

     

     

    그럼 result는? 완벽하게구현된코드를 다음으로 살펴보자. 

     

    우선 완벽한코드를 살펴보기전에 무엇을할지 상상해보자

     

    5시간전에 발생한 지진들의개수를 찾고자할때, 다음과같이 코드를 구성할수있따.

     

    5시간전에발생한 지진들의 개수를 찾고자하는 request방법이다. 

    기존에는 아래와같이작성할수있었다. 

     

     

    하지만 이제는 try 와 await를 사용하여, 반환타입을 받아낸다. 

    이방법이 훨씬 쉽고, 표현력있다 

     

     

    하지만 주의할점이있는데,

    새로운 API는 손쉽게 value를 return 하지만, 

    클로저안에서만 safe하지, 이렇게 managed object를 반환타입으로받는거는 safe하지않다

     

    그러므로 safe하게 다른 context들사이에서 managed object를 참조하고싶다면, 

    OjbectIDResultType이나, dictionary타입으로  이용하라.

     

    또 다른예로 넘어가기전에, 

     

    아까설명못한 perform의 매개변수에있는 SchduledTaskType을 알아보자.

     

    Default는 immediate이다.

    이둘의 차이를 알기위해서는 먼저 managed object context안에서 무엇이 구체적으로 발생하는지를 생각하는것이 도움이 될것이다. 

     

     

    immediate는 

    만약 다른 execution context에있고 wait peform한다면, 스케쥴링되어질때까지 기다리고 처리된다.

    만약 같은 exectuion context에 있다면, 이작업은 바로 스케쥴링되어질수있다.

     

    enqueue는  같은 execution context라도 이작업은 가장맨뒤에 스케줄링되어진다. 

     

     

    자 다음 예제로가보면, import로직을  async throws 키워드와함께 메서드로 구성했다.

     

     

    자, 우리가 본것들을 요약하면, 다음과같이 나타낼수있다.

     

     

     

     

    그리고 Context뿐만아니라 다른타입으로 동시성을 지원한다.

    Container와 coordinator에 비슷한API를 추가했다.

     

    또한! 다양한 디버깅툴을 사용할수있다는점도 있다.

    scheme에서 다음과같이 체크하면 가능하다.

    ( thread나 address 둘중하나만 선택가능 ) 

     

    또한 특정 런타임플레그도 제공한다. 

     

    Swift의 Concurrency 지원뿐만아니라, Core Data는 새로운 API를 소개했다.   ( 자세한건 세션 참고!! ) 

     

     

     

    PersistentStore는 어떻게 데이터를 저장할것인지를 정의하는데, 

    다음과같이 4가지가있는데 이제는...

    좀더 swift스럽게 이름이바꼈다 

     

     

    또한 NSAttribute description에 AttributeType을 추가하여 더 자연스러운 syntax를 추가했다. 

    기존에는 NSAttributeType이였다. 

     

    그래서 AttributeType을 이용하여 해당 이름으로 시작하는 attribute가 있는지그리고 type이맞는지를 다음과같이 test코드를 작성할수있다.

     

     

     

     

     

     

     

     

     

     


    SwiftUI 

     

    자이제, SwiftUI와 작동하는 Core Data의 편리함들을 알아보자. 

    ( 아니.. 이남자분은 17년세션부턴가 계속 똑같은옷을 입으시네...ㅋㅋㅋ ) 

     

    이번에는 Lazy Stack Initialization, dynamic configuration, sectioned fetching이 추가됐다. 

     

    우선 Lazy stack initialization을보자.

     

    코드는 이렇다. 

    container를 ContentView가 초기화된후 environment modifier로 호출되어졌다.

    하지만 이제는 더이상 위와같은 트릭(?)은 필요하지않다. 

    FetchRequest property wrapper는 laze하게 이름을통해서 entity들을 찾기때문에...  ( 그때는 environment가 CoreDatastck이 준비된것을 보장하기때문에.... ) 

    그러므로 container프로퍼티를 삭제해도 안전하다. 

     

     

     

     


     

     

     

    이제 Fetch Request는 dynamic configuration을 지원한다. 

     

     

    자 그럼 앱으로확인해보자

    우선 기본적으로 지진앱은 최신순으로 정렬한다. 

    하지만 진도순도 추가하고싶다.

     

    우선, static 배열을 만든다. ( 시간과 진도순 )  이번에 새로 소개된 SortDescriptor 이다. 

     

     

     

    그리고 미리만들어둔 sort order를 프로퍼티로 추가하고, 

     

    각 시간순,진도순을 선택할수있는 매뉴를 추가한다.  

    그럼이제 선택하면 리스트가 해당 순으로 정렬된다!

     

    진도순으로 눌르니까 바로반영..!!

    짜잔~! 

     

     

     

    그리고이제는 필터링기능도넣고싶다!

     

    우선, search field의 text를 위한 state를 명시해야겠따. 

     

     

    그리고 바인딩프로퍼티도 추가한다.

    fetch requeset의 predicate에 사용될것! 

     

    자그럼 입력하는 UI가필요하니, searchable을 추가한다!

     

    그리고 sandwich를입력하니 바로 변경됨 ㅠㅠ!

     

     

     

    이것이 fetchRequest의 dynamic configuratoin이다!

     

     

     


     

    새로운 property wrapper인 SectionedFetchRequest가 생겼다.

    FetchRequest와 마찬가지로 dynamic configuration을 지원한다.

    그리고, FetchResultController처럼 section identifier를 받는다. 

     

     

     

    이것을적용하기는 굉장히쉽다. 

     

    우선 @FetchRequest대신  @SectionedFetchRequest를 사용하자. 

    지진은 day 프로퍼티를 가지고있으니, 섹션으로 day keyPath를 사용하자.

     

     

    그럼 이제 섹션별로 나타내기위해 body도 변경하자. 

     

     

     

    짜잔~ 날짜별 섹션으로 시간순으로정렬되어있다. 

     

     

     

     

    그리고 SwiftUI는 자동적으로 collapsable한 섹션을 제공한다.

     

     

     

    하지만 지금까지는 실제로 정렬을 변경하는것이 안전하지않다.

    시간과 지진진도가 완벽하게 연관성이있는것이아니기때문이다. 

     

    그러므로, static 배열을 section을 추가한것으로바꾸자.  ( 진도순, 날짜순 ) 

    즉 진도순섹션과 날짜순섹션으로 나타내고자함

     

     

     

    여기에서도 sectionIdentifier를 추가하자.

     

    짜잔~!

     

    댓글