본문 바로가기
WWDC

WWDC 18 - Core Data Best Practices

by vapor3965 2021. 7. 2.

목차


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


    https://developer.apple.com/wwdc18/224

    Core Data Best Practices - WWDC18 - Videos - Apple Developer

    As your app gains more customers and becomes more feature-rich, you may find yourself with new problems to solve. Core Data is a powerful...

    developer.apple.com



    요약

    • PersistentContainer는 보다 쉽게 Core data에 필요한 준비작업들을 해주고 쉽게 작업할수있다. 물론 서브클래스하여 사용할수있다.
    • Fetchced results controller를 사용한다면 항상 최신데이터로 유지하는경우에 적합하다.  
    • 데이터가 많아질수록 쿼리문의 속도가 떨어질수있으므로, denormalization의 기법을 이용하는것도 하나의방법이된다.
    • 동시성이 필요한 여러작업들은 query generations을 이용한다.
    • Persistent History tracking 을 통하여 업데이트된 변화를 알수있다. 뿐만아니라 UI와 관련된경우, 필요한 업데이트만 필터링할수도있다.
    • 반복적인작업들은 batch operations을 이용하면 상당한 성능개선을 보여준다




    우선 앱을만들어보자.
    사진찍는앱인데, 이를 친구들에게 공유할수도있고, 코멘트도얻을수있는 앱이다.


    앱데이터는 어떻게관리할까?
    물론 온라인으로도할수있지만, 주로 여행다닐때 사진을찍기때문에 연결이 안좋을수있다.
    그러므로 잘 정리된 데이터로 로컬에저장해야겠다.

    그러므로 Core data가 필요하다.

    우선 이미지와 코멘트를 다음과같이 모델로 표현할수있다.
    이미지,타임스탬프 attribute가있고,
    포스트와 코맨트의 releationship이 있다.



    Core Data는 persistent store coordinator를 제공한다.
    이 coordinator는 앱의모델과 stores' version등을 compare하고, 자동적으로 forward하게해준다.


    또한 managed object context를 통해서 안전하고, 빠르게, 데이터를 접근하도록 해준다.




    위의 모든요소들을 세팅하는데는 ( 아래와같이 ) 모델을 찾고, 로드하고, store를 어디에 유지할지등을 요구로한다.

    하지만 앱을 옮길때마다? 많은에러를 발생할수있기때문에
    Core Data는 container type를 제공한다.

    PersistentContainer

    • 획기적으로 boilerplate 를 줄이고,
    • 단순히 이름을통해서 참조한다.
    • 많은 스택을 캡슐화하고

    • 앱이커지더라도 쉽게 작업할수있다.

    예를들어, 모델을 앱내의 새로운 프레임워크에 포함하고자할때,
    Xcode에서 새로운 프레임워크타겟을 만들고, 코드들을 옮길수있다.

    여기까지가 우리가바라던대로지만, NSPersistentContainer는 모델을 더이상찾을수가없게된다.
    왜냐하면 default로 main 번들에서만 체크하기때문이다.
    앱의 번들들을 모두찾는것은 매우느려질수있기때문이다.

    이제는 모델을 찾아내고, 다른 이니셜라이저를 통해서 Container를 만들수있다.


    또한 NSPersistentContainer는 서브클래스되어지는것을 알고, 모델을찾을때 서브클래스의타입을 사용한다.
    그러므로 우리는 간단하게 서브클래스할수있다.




    store의 location을 변경하는 것은 store을 로드하기전에 URL을 수정하면된다.

    위에처럼 하지않고 대신 아래와 같이 간단하게 할수있다.
    Persistent store descriptions을 만들때 defaultDirectoryURL을 호출하기때문에,
    단순히 path에 component만 추가해주면된다.

    • 그러므로, 캐시나 다른 종류를 위한 container들을 다른위치에 저장하기에 좋은 방법이된다.


    자, 이제여기까지 Core data을 알아봤고, view controller를 살펴보자
    나만의 포스트만보여주는 뷰컨트롤러와 모든포스트를 보여주는뷰컨트롤러로 나뉜다.
    딱봐도 디테일뷰들이 중복될것이고, 코드를 많이작성해야할것같다.


    그렇게하지말고,
    아래와같이 간단하게분리하자.
    즉 포스트의 리스트를 보여주는 뷰컨트롤러와
    하나의 디테일 포스트만 보여주는 뷰컨틀롤러로!




    CoreData와 뷰컨트롤러를 이용할때,
    리스트뷰들은 fetch request를 이용애햐하고,
    detail view는 managed object를 이용해야한다.
    또한 뷰컨트롤러는 managed object context가 필요하거나 container view context 또는 main queue context가 필요하다.


    어떻게 다른 뷰컨트롤러에 전달할수있을까?

    • 보여지기전에 세팅해주면된다.

    segue를 통해서 prepare를 오버라이드하거나,

    스토리보드를 참조해서 할수도있다.

    또는 코드로도


    이제 fetch request와, context가 있으니, 이것들을 함께사용하기전에
    더나은 퍼모먼스를위해 fetch request를 좀더 수정해보자.
    다음과 같이 두개의옵션중 하나를 설정해야한다. - fetch limit, batch size

    • 리스트형식뷰에서는 batch size를 정하는것이 더 낫겠다. 한번에스크린안에서 얼마나셀이있는지알면서, 한번에보여줘야하니까.

    하지만 최신데이터를 항상유지하려고하는 경우에는 Fetched results controller가 더 잘맞다.

    • adaptor와, delegate protocol를 작성한다.
    • 물론 fetch request, context가 필요하다.
    • 뿐만아니라 리스트뷰의 섹션과같이 그러한 개념들도 지원한다.


    만약 날짜벼로 섹션으로 그루핑하고싶다면, Xcode에의해 만들어진 Post 타입을 extension하여, computed property로 만들어주고,
    이것을 fetched results controller's 이니셜라이저에 넣으면 할수있다.


    하지만 단순히 리스트를넘어 더 복잡한 뷰라면 어떻게할것인가?
    아래와같이 날짜마다 포스트수를 나타내는 차트라면?


    우선, fetched reuqest의 힘을 과소평가하지말라,
    아까처럼 day 프로퍼티를 이용하여 Fetch request를 수행할수있다.

    우선 날짜를정하고,
    day 프로퍼티로 그룹으로만들어주고,
    expression을 정의한다.

    • NSExpression에 대해 더궁금하다면 공식문서를 확인하라.



    이렇게해서 차트를위한 데이터를 찾을수있지만, 모든포스트를 하나씩읽기땓문에, 년마다,달마다, 또는 그이상 훨씬더 많은데이터일경우에는 힘들수있다.
    그러므로 denormalization이 필요하다 ( 역정규화 )

    • 읽기성능을 향상시키기위해, 저장공간을 더 사용하면서, 같은 데이터나 메타데이터를 추가적으로 더 저장한다.

    그러므로 카운팅하는 데이터를 store에 저장하도록한다.
    즉 추가적인 model을 만든다. ( 날짜별로 포스트의개수 )


    그러므로 훨씬더 Fetchrequest를 간결하게작성할수있게된다.


    하지만 이제 카운팅을관리하는 ( 포스트가 추가되면 +해줘야하고, 포스트가이동하거나 삭제되면 - 해줘야하는 작업 )게 필요하다.
    여러방법이있게씾만, context를 save할때 같이 카운팅업데이트해주는게 제일 낫겠다.
    데이터베이스에 커밋하기전에 context의 상태들을 미치게한다. 이는 하나의 트렌젝션으로 발생한다.
    이러한방법은 굉장히 괜찮다!











    앱은 커질수록 더복잡해지고, 관리하기가 힘들어게된다. ( Core data도마찬가지로 )



    그래서 Core data 에는 도움이되도록 하는 아래와같은 방법들을 제시한다.


    우선 아래와같은 유저의메트릭스는


    개발자의메트릭스로 해석해볼수있다.


    좀더 확실히하기위해, 아래의앱을 예로들어볼것이다.


    유저가사용할수있는 몇가지기능이있다.
    첫번째로, +를 누르면 하나의 포스트를 데이터베이스에 추가할수있다.


    또한 서버로부터 데이터베이스로 저장할수있다.


    또한반대로, 데이터베이스로부터 서버로업로드할수잇다.



    뭐, 몇개없는 기능같지만, 이것들이 동시에 발생하면...????
    Chaos다.


    그리고 사용자한테는 아래와같은 끔찍한 유저경험을 선사할수있다.



    위와같은 문제점들은, 2016년에 What's New in Core Data session에 소개된 Query generations을 통해 도움을줄수있다.!

    • 자세한건 해당세션을 참고하도록!
    • Query generations 의 목표는 object contexts를 작업완료로부터 분리하는것이다.
    • 동시에 다른 context가 데이터베이스에 쓰기하는것과 상관없이 항상 일관된 결과를 제공한다.


    다음과같이 한줄로 적용할수있다.


    그리고 업데이트할때쯤, NSManagedObjectContextDidSave notification을 이용해서 업데이트할수있다.


    그래서 이러한것을통해 적절한시간에 UI에 데이터변화를 표현할수있게해준다.


    만약 UI에 나타나지않는것을, (예를들어 코멘트같은 ) 서버로부터 데이터를 데이터베이스에 추가하는경우는 어떻게할까
    이경우에는 UI에 표현할필요없다.
    그래서 우리는 이러한업데이트는 history tracking을 이용하여 걸러낼수있다.
    Persistent History Tracking 은 iOS11에서부터 소개되었고, 작년 session에 소개했었다.
    각 트랜잭션의 persistent record를 얻는데있어서 좋은방법이다. 그리고 몇가지이유로 유용하다.


    아래와같이 PersistentHistory는 프로퍼티들을 제공한다.





    아래와같이 데이터베이스에 포스트들이 추가되어지려고할때, UI는 refresh되어야할것이다.

    그래서 우리는 objectIdNotifcation을 이용해서 반영할수있을것이다. 위에서 object contextDidSave와 굉장히 유사한 API이다.


    하지만, 코멘트와같이 유저에게 보여줄필요없는것은!!!????



    아래와같이 트랜잭션들에서 changes안에서 필터링한다.
    여튼, 이것을통해서 불필요한 UI업데이트를하지않을수있다.


    '

    보시다시피앱에서는 post에서도 아주적은 양만사용하고있다 ( image , title )

    그러므로 이것을을 이용해서 더 괜찮게 필터링할수있따.




    하지만 Core data는 새로운 interactions지원하면서 도와준다.
    데이터가가 더 복잡해지고 커질수록, 몇몇 에디팅연산은 비쌀수있다.
    예를들어, 포토브라우저같은겨우, 반복적인 작업들( 예를들어 멀티셀렉션같은) 을더 쉽게 수행하기위한 새로운 기능들을 추천한다.


    그래서 새로운기능ㅇ은 batch operations이다.
    몇줄로 포토들을 선호도인지아닌지로 마킹할수있따.

    또는 한줄로 삭제할수도있다.


    아래와같이 batch Delete Request는 object delete와는 다르게훨씬 데이터에베이스가 커질수록 훨씬더 메모리를 적게사용한다.




    batchoperation으로는 고질적인문제가있는데, 그것은 save notification을 생성하지않는다.
    하지만 history tracking을통해 해결할수있다.
    batch delete,update가발생한 데이터베이스로부터 트랜잭션들을 fetch할수있다.










    ( 여기서부터는 좀 어려워서 세세하게 작성하지않았어요 😢 )

    core data는 더나은 빌드와 테스트를 위해서 무엇을해줄수있을까?
    우선 첫번째로 NSKeyedArchiver API가 바꼈다. ( 더 안전한코드를 도입했따? )
    그러므로 value transformer도 바꼈다. 그러므로 이전에사용했던 API들은 아래와 같이 새로운 API를 적용하길바란다.



    모델에디터에서 transformable property를명시할수있다.
    나중에 릴리즈되는 Xcode에서는 이는 default로된다.


    또는 코드로 모델을 만들때는, 이렇게 세팅할수있다.



    또한 Scheme configuration을 다음과같이 설정하기를 추천한다.
    그러면 디버깅을위한 다양한 로그들을 제공한다 ?




    Core data는 다양한 인덱싱을 제공한다. R-Trees를 이용한 인덱스뿐만아니라.

    • 다양한쿼리와 최적화된 쿼리를 만들수있다.

    쿼리타입도 바꿀수있따.

    이를 이용해서 predicate에 적용할수있다







    Core Data를 사용하는데있어서 test를 활용하자.



    이렇게 container를 확인하는 테스트뿐만아니라 적은 데이터들을 연산하는 테스트도 가능하고, 이는 메모리안에서 실행되기때문에 굉장히빠르다는 장점이있다.


    만약에 application delegate안에 persistent container가 작성됐다면 아래와같이 작성할수있다
    하지만 주의해야한다? application을통해서 writing하기때문에 다음에도 영향이미친다??




    10만개 쿼리문을 다음과같이도 작성할수있다.



    timestamp 인덱스를 이용한 (R-tree) query문의 성능을보고자 작성했던 코드다.









    댓글