본문 바로가기
WWDC

WWDC 19 - Modern Swift API Design

by vapor3965 2021. 6. 22.

목차


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

     

     

    https://developer.apple.com/videos/play/wwdc2019/415/ 

     

    Modern Swift API Design - WWDC 2019 - Videos - Apple Developer

    Every programming language has a set of conventions that people come to expect. Learn about the patterns that are common to Swift API...

    developer.apple.com

     

     

     


    관련내용

    • swift5.1 배워보자
    • swift의 API디자인에 핵심패턴들을 배워보자.
    • siwftUI, Combine, RealityKit 의 예제도배워보자.
    • property wrapper도 이뜸.
    • writable key path

     

    요약

    • 👍value와 reference를 같이사용할때, reference를 value semantic으로만드는방법을배움 - COW
    • 👍KeyPath Member Lookup이 무엇인지, 배움 어떠한 타입이든지 접근할수있다.
      • KeyPathMemberLookup을통해 더 간단하게 , 더효과적으로 작성가능함.
    • 👍Property Wrapper를 배움. - boiler plate를 줄여주며, expressive 한 API를 얻을수있다.
      • 근본적으로, 데이터접근에 대해 policy를 세우는 것!
      • 커스텀으로 작성할수있다.
      • 구현하기위해서는 wrappedValue- 연산프로퍼티를 정의해야한다.
      • 그리고, 내부에 추가적인 저장프로퍼티를 가질수있는데, 그것은 porjectedValue로 선언해야한다.
        • 이는 $ (dollar sign)으로 접근할수있다.
        • 😀 그래서, Combine에서 타입에 대해 @Published 를 사용하는경우에, $로 접근하는 이유가 그거구나!!!
      • 초기화하지않고 생성하면, swift는 자동적으로 init()메서드를사용한다

     

     

     


    Swift핵심 

    C 와 Objective-C는 prefix접두사를 가져야한다.
    모든심볼들이 글로벌네임스페이스안에있기때문?

     

    그래서, Objective-C와 동일한 swift기능들은 prefix를 가지려고한다.

    Swift는 명확성을추구하기때문에, Swift만의 라이브러리는 prefix를 가지지않는다.

    🤔아래가 prefix 표기법이라는데 이게맞남?

     

     

    언제 Refrence, Value를 쓸까?

    • 일반적으로 struct를 사용하고, class가 필요할때 class를 사용한다.
    • type이 identity를 가져야할때는 class를사용한다.
      • struct의 equality와는 다른것이다.
    • 값이공유되고싶을때 class를사용한다.

    이렇게 class이면, 그냥 변수로 할당해도 전혀문제가되지않으니까! 

     

     

     

     

    value타입안에 class타입이있는경우,
    객체를 복사하면 다복사되지만, 내부 class타입은 참조를복사하게된다.
    즉, 해당 내부 class타입은 공유되어진다는 뜻이다.

    이것을방지하기위해서는 reference를체크하여, copy-on-write를 구현한다. - isKnownUniquelyReferenced ( )

    • 그러면, 해당객체는 reference타입프로퍼티를 가지고이으면서도 완벽하게 value의미를 갖도록 하게할수있다.
      • 즉 class타입을 사용해야하면서도 value타입을유지할수잇게된다.
        ( 그리고, class타입이 유니크하게 복사되어지기위해서는, NSCopying 프로토콜을 채택하여, copy() 메서드를구현한다. )

     

    아래와같이, Texture안에서 접근하고자하는 프로퍼티에 대해 computed property로 만들어서, 이를 get,set에 reference를 확인하여 copy-on-write를 구현한다!!!



    ⬇️아래로하게되면, texture를 get하여 얻어서 texture내부의 특정프로퍼티를 변경하게되면 공유되어지기때문에 완벽한 해결법이아니다.

     

     

     

    Protocol and Generic

    • swift에만있는, protocol은 strurct, enum을 채택할수있다.
    • 더이상 클래스로 계층구조를 만들지않고서도, 다른 value타입들이 공유되어지는 코드를 만들고싶다면, protocol을사용하면된다.

    그렇다고 우선적으로 protocol을 입력하면서시작하지는 말아라.

    • 우선 완벽한 concrete 타입을 작성하고, 그후에 공통되는부분들이있는 부분들을 발견한다.
    • 그다음, generic코드가 필요한지 확인한다.
    • 그다음, 존재하는 protocol안에서 괜찮은 솔루션이있는지 확인한다.
    • 그다음, protocol로 바로사용하지말고, generic 타입으로 가능한지고려한다.

    기존의타입들을 extension하여 구현된protocol을 모두 채택하게하는것보다,
    protocol없이 extension하는게 컴파일러가 처리하는데있어서 후러씬 더 쉽다.
    왜냐하면, 굳이필요하지않은 프로토콜witness table들을 만들필요가없기때문ㅇ디ㅏ.

     

    ( SIMD안에서, 특정타입이 FLoatingPoint일경우에만!

    수많은 프로토콜이잇는 프로젝트에서, 이러한접근법을사용하여 ,많은 프로토콜을줄임으로써, 컴파일타임을 개선할수잇다.

    is-a relationship 보다는


    has-a relationship을 고려하라.


     


    👍Key Path Member Lookup

    • dynamic member attribute를 추가함으로써, 해당타입은 정의한 타입의 모든 프로퍼티에대해 접근할수있게되어진다.
      Swift.5.1 부터 생긴.

    사용방법

    우선, @dynamicMemberLookup attribut 를 tag한다.


    그렇게하면, 컴파일러는 특정 dynamic member subscript를 작성하도록 요구한다.
    subscript는 KeyPath를 받는다.

    • 이를 구현하게되면, KeyPath를 통해 접근되어지는 프로퍼티들은 자동적으로 computed porperty로 노출되어진다.
    • 아래는, SIMD Storage 타입으로받아, Scalar로 return한다.



    그리고 keyPath를넣어준다.


    이렇게되면 자동적으로 해당 GeemotricVector 타입은 SIMD가갖고이쓴ㄴ 모든 프로퍼티를 가질수있게된다.

    • 즉, 아래와같이! xcode의 autucompletion에 SIMD의 모든프로퍼티들이 나타난다!!!!
    • 5.1에서부터는 type safe하다 ? 또한 컴파일타임에 많은것이되어진다. ?

    그러므로, 이제 GeometricVector는 SIMD타입의모든프로퍼티들에대해접근할수있으므로, SIMD타입의 value를 거치지않고 작성이가능해진다!!

    그리고 이러한 프로퍼티에 대해 forwarding에만 유용한것이 아니다.
    복잡한 로직은 subscript에 작성할수있다.

     

     

     

    👍Reference타입의 모든 프로퍼티들에 대해 Copy-on-Write 기능적용


    아래와같이, class 타입인 Texture타입의 모든프로퍼티에 대해 copy-on-write를 하고싶다면 어떻게해야할가?

    • 매번 똑같은 코드를 작성하는것은 정말 별로다

    dyanmic member attribute를 추가하고,
    그다음 WritableKeypPath로 작성해준다.

    • 모든 Texture프로퍼티에대해 get,set다하고싶기때문!
      그다음 generic 타입으로 반환한다!

    get은 마찬가지로 keyPath로 반환하고,
    set도 마찬가지로, unique referecne check하고, set해준다!!

    즉! 이렇게하면, 하나의 subscript로, 모든 Texture (클래스타입) 의 프로퍼티에 대해 copy-on-write기능이가능해진다!!

    또한, 이러한 KeyPath member Lookup 은 Property Wrappers( swift5.1에서소개된) 와도 정말 잘어우린다

     

     

     

     

     

     


    👍Propperty Wrappers

    • 내가작성한 computed properties의 바깥에서, 를통해서 code reuse를 얻는것?
      And the idea behind property wrappers is to effectively get code reuse out of the computed properties you write.

    imageStoreage를 직접적으로 사용자가 접근하지않게하기위해, public으로 computed property로만들어두었다.

    • 하지만 실질적인 이것은?!

    이것의 실직적인 코드는?!
    👍단순 지루하고긴 lazy variable image를 구현한것이다!!

    • lazy는 단순히, 지연되는 저장프로퍼티라고 해석할수있지만,
    • 그것의진짜의미는, 사용자가직접적인코드접근을 막으면서, 호출할경우에만 인스턴스를 반환하도록하는 것으로 해석할수있다.
    • 즉, 저장프로퍼티를 래핑하는방법!!!

    하지만, 위의 lazy의경우 문제가있다?

     

    아래의 다른예제를보면,

    • late initialized로,

    거의동일하지만, get,set에서 다르다.
    아래예제는 get에 mutating 을 붙이지않느다.
    즉, 값이 있는경우에만 받을수있고, 없는경우는 fail하게된다.

    • 즉, set하거나, 읽기전에 이니셜라이즈되어야한다.

    뭐, 이것도 문제가있다라고한다. - ㅁ -


    Property Wrapper의 목적


    이러한 바탕들이 property wrapper의 아이디어다.
    👍boilerplate 를 제거하고, 더 expressive 한 API를 얻기위해서다.

    • 핵심은 접근키워드와, @LateInitialized wrapper를 사용했다.
    • 해당 wrapper 키워드는 커스텀이며, 특정한 Policy를 제공한다.
    • 그리고 훨씬 읽기쉽게해주어 실제 의미를 파악하게해준다.
      • 늦게이니셜라이저된다라고, Lazy이면서, 초기화전에 사용하면 fail한다라고 알려준다.

    @LateInitiazlied wrapper 구현하기.

    • 위와똑같은 late lazy initalized 이다.
    • Value 라는 Generic을 사용했다.
    • 이렇게하면, 어떻나 타입이든지, Late 하게 initialized되도록 할수있따!!

    👍propertyWrapper attribute를 적용하면,
    몇개의 요구사항이생긴다.
    그것은 가장첫번째는 value 프로퍼티를 갖는것.
    -> wrapperValue 로 바뀜!

    두번째로는, 이니셜라이저에 파라미터가없는점이다.


    실제 property wrapper적용된 사용

    • 아래와같이 사용한다면, 컴파일러는 두개의프로퍼티로 해석한다.

     

    • 아래와같이, stored property와 computed property로 해석해낸다 .
      • 즉, LateInitialized으 으로초기화하는 $text 저장프로퍼티와, 그안에구현된 value를 접근하는 get,set 연산프로퍼티로.

    또 다른 예

    아까 위에서, reference 와 mutable상태에서 value semantic을 적용하기위해 여러코드를 작성했었다.
    하지만, property wrapper를 사용하면어떨까?

    특별한점은, init에 파라미터를 주었는데, 파라미터가없는 init과마찬가지인거다.
    만약 Init에 파라미터를 준다면, default로 value를 넣어준다 ?

    • 여튼, 특정 인스턴스를 넣어주면, 새롭게 카피하도록 하게했다.

    실제사용방법


    위의코드는 아래와같이 컴파일되어진다.

    코드에작성한것처럼, init( initalValue ) 를 호출하게하고, 카피한다.

    👍하지만? 새로운인스턴스를 카피할필요가있나?
    없으니, 아래와같이 extension으로 PropertyWrapper하여, 새로운 Init을 만들어준다!

    그리고 , 이를사용할때는, 이니셜라이저를 아래와같이, 직접적으로 DefensivCopying( withoutCopying: ) 을 적어주도록한다.

    • 주의할점은 $path를 이용했다.
    • 왜냐, 컴파일러가 $path 저장프로퍼티를 생성하기때문!

    👍하지만! 위의코드도 영 boilerplage이니, 다음과같이 더줄일수있다!

    프로퍼티위의 바로 주석다는것처럼 적용하는것!

    즉, property wrapper는,
    데이터접근에 대해 policy를 정의한다.
    어떻게 데이터가 저장될지,
    어떻게 접근할지를 정의할수있다.

    property wrapper를 통해 아주무궁무진하게 다양하게사용되어질수잇다.



    또는, 특정쓰레드로 정의할수도있따?


    또는, 커맨드라인관련해서 이렇게적용할수도있따?

     

     

     

     


    👍SwiftUI에서의
    property Wrapper

     

     

    SwiftUI에서는 굉장히 PropertyWrapper을 많이사용한다.
    SwiftUI에는 몇몇 정의된 propertywrapper가 있다.

    흥미로운점은, $slide.title 이다.
    $는 컴파일러가만들어낸 backing stored property이다.
    Slide는원래 title을 가지고있지않는데, 다른 데이터모델과 binding되어있는상태로써,
    이는 Property wrapper + Key path Member Lookup와 결합된ㄴ상태다?


    자세한구현은 모르지만, 아래와같이 DynamicMemberLookup도 구현한상태이다.
    Keypath를받는다.
    그리고새로운 Binding< > 을반환한다.

    그래서 실제사용예는,
    $를붙이면 Binding타입으로 접근하게되고,

    $slid.title은 keyPath를 통해접근하여, Binding의인스턴스가된다.

    뭐…여튼, 다른뷰들에서도 공통적으로사용된다는 뭐 그런뜻인가

    'WWDC' 카테고리의 다른 글

    WWDC 16 - Understanding Swift Performance  (0) 2021.06.22
    WWDC 20 - What's new in Swift  (0) 2021.06.22
    WWDC 19 - Optimizing App Launch  (0) 2021.06.22
    WWDC 19 - Combine In Practice  (0) 2021.06.19
    WWDC 19 - Introducing Combine  (0) 2021.06.19

    댓글