TIL(Today I Learned)

1월 30일(일)

학습내용

  • SwiftUI(CS193p Lecture 6)
    • protocol
      • 함수와 변수를 갖고 있지만 실제 구현은 없는 것.

          protocol Moveable {
              func move(by: Int)
              var hasMoved: Bool { get }
              var distanceFromStart: Int { get set }
          }
        
      • 프로토콜을 여러개 채택할 수 있다.

          class Car: Vehicle, Impoundable, Leasable {}
        
      • 프로토콜은 어떻게 사용되는가?

        • 드물게 타입으로서 사용된다.

            func travelAround(using moveable: Moveable) // Car, PortableThing 등을 넣을 수 있다.
            let foo = [Moveable] // Car, PortableThing 등을 저장할 수 있는 배열
          
        • 일반적으로 struct, class, enum 이 준수해야할 행위를 규정?하기 위해 사용된다.

            struct EmojiMemoryGameView: View
            class emojiMemoryGame: ObservableObject
          
        • “don’t cares” 를 “somewhat cares”로 만들기 위해 사용된다.

            struct MemoryGame<CardContent> where CardContent: Equtable
          
          • 제네릭 타입을 특정 프로토콜을 준수하는지 여부로 제한할 수 있다는 의미이다.
          • protocol-oriented programming을 위한 핵심
        • extension을 특정 프로토콜로 한정하기 위해 프로토콜을 사용한다.

            extension Array where Element: Hashable { ... }
          
          • Hashable을 준수하는 배열만 기능 확장하게 된다.
        • 함수의 작동을 제한하기 위해 프로토콜을 사용된다.

            init(data: Data) where Data: Collection, Data.Element: Identifiable
          
          • Collection이면서 원소는 Identifiable한 인자만 받을 수 있도록 제한하는 것이다.
        • 두 엔티티 간의 상호 조약?을 위해 프로토콜을 사용한다.
          • DropDelegate
            • 시스템은 DropDelegate를 채택한 타입만 Drag & Drop을 가능하게 한다. (타입은 DropDelegate에서 요구하는 함수와 변수를 반드시 구현해야한다.)
        • Code Sharing의 이점을 위해 프로토콜을 사용한다.
          • 프로토콜의 extension을 통해 구현을 추가할 수 있다.
          • 예) 뷰들이 foregroundColor, font 등의 modifier를 갖는다.
          • 프로토콜의 함수나 변수의 기본 구현을 extension을 통해 가능하다.
          • 이는 POP(프로토콜 지향 프로그래밍)의 키포인트이다.
          • func filter(_ isIncluded: (element) → Bool) → Array
          • 이 함수는 애플에 의해 구현되었으며, Array, Range, String, Dictionary 등에서 사용된다.
          • 이는 Sequence Protocol 을 extension 하여 filter를 추가하였기 때문이다.
          • Array, Range, String, Dictionary 모두 Sequence 프로토콜을 채택하였다. 그래서 이들 모두 filter 함수가 저절로 추가가 된 것이다.
        • Adding Protocol implementation
          • 프로토콜을 Constrains and gains 이라고 생각할 수도 있다. (교수님 왈)
            • 아래처럼 테슬라가 Vehicle을 채택함으로써 프로토콜에서 요구하는 함수와 변수를 구현해줘야하는 제약이 따르지만, 프로토콜의 확장기능을 그대로 얻을 수 있다는 이점이 있다.

                struct Tesla: Vehicle {
                    // Tesla is constrained to have to implement everything in vehicle
                    // but it gains all the capabilities a Vehicle has too
                }
                extension Vehicle {
                    func registerWithDMV() {}
                }
              
        • View
          • View 프로토콜을 채택하면 body를 구현해줘야 하지만 아래의 기본 구현들을 얻을 수 있다.

              protocol View {
                  var body: some View
              }
                                
              extension View {
                  func foregroundColor(_ color: Color) -> some View { /* 구현 */ }
                  func font(_ font: Font?) -> some View { /* 구현 */ }
                  func blur(radius: CGFloat, opaque: Bool) -> some View { /* 구현 */ }
                  ...
              }
            
        • 왜 프로토콜을 사용하는가?
          • 프르토콜을 통해 프로토콜을 채택한 타입이 할 수 있는 것이 무엇인지를 알 수 있다.
            • 프로토콜이 요구하는 기능과 변수를 구현해야 하기 때문이라고 생각한다.
          • 프로토콜을 통해 특정 행위가 가능한 타입을 요구 할 수 있다.
            • 입력 파라미터가 특정 프로토콜을 준수하는 타입의 데이터만 넘길 수 있도록 할 수 있을 것이라 생각한다.
          • 프로토콜을 통해 기능에 초점을 맞추고, 세부 구현에 대해서는 숨길 수 있다.
            • 프로토콜을 준수한 타입의 기능이 어떻게 구현되어있는지에 대해서 알 필요 없이, 준수헀다는 것만으로 타입을 사용할 수 있기 때문이라고 생각한다.
        • Generics + Protocols
          • 프로토콜도 제네릭을 이용하여 내부의 프로퍼티를 일반화 시킬 수 있다.
          • 다른 타입들은 <>을 이용하는 것과는 달리, associatedtype이라는 키워드를 이용한다.

              protocol Identifiable {
                  associatedtype ID
                  var id: ID { get }
              }
            
          • ID 를 Hashable로 제한하기 위해서는 아래와 같이 할 수 있다.

              protocol Identifiable {
                  associatedtype ID where ID: Hashable // 또는 associatedtype ID: Hashable
                  var id: ID { get }
              }
            
        • Hashable

            protocol Hashable {
                func hash(into hasher: inout Hasher) // Hasher는 combine 함수를 가지고 있다.
            }
                          
            struct Foo: Hashable {
                var i: Int
                var s: String
                func hash(into hasher: inout Hasher) {
                    hasher.combine(i)
                    hasher.combine(s)
                }
            }
          
        • Protocol Inheritance
          • Hashable은 == 연산자를 이용해 두개 값의 해쉬 값이 같은지 비교할 수 있어야 하기 때문에 Equatable을 상속한다.

              protocol Hashable: Equatable {
                  func hash(into hasher: inout Hasher) 
              }
            
          • Protocol Inheritance은 클래스의 상속과 다르다.

        • Equatable
          • Equatable의 lhs와 rhs는 Self 타입인데, Self는 Equatable을 채택한 타입 자기 자신을 의미한다. 이를 Self-referencing protocol 이라고 하는데, 이는 일반 타입으로는 사용할 수 없다. 예를 들어, var x: [Equatable]로 사용할 수 없다.

              protocol Equatable {
                  static func ==(lhs: Self, rhs: Self) -> Bool
              }
            
            • 따라서 서로 다른 타입의 값을 == 연산은 불가능하다. (자기자신과 같은 타입만 인자로 받을 수 있으므로)
          • View도 Self-referencing protocol이어서, var myView: View 이런식으로 사용할 수 없다.

        • @escaping
          • ViewBuilder 클로저를 파라미터로 받을때 escaping으로 받지 않으면 “Assigning non-escaping parameter ‘content’ to an @escaping closuere”라는 오류가 나타난다.
          • init의 외부(프로퍼티 및 메서드)로 클로저가 전달되기 때문이다.
          • 또한 escaping이 아니면 컴파일러는 인라인으로 해당 클로저를 삽입한다. (클로저를 객체로 만들어서 참조하는 것이 아니라)
            struct AspectVGrid<Item, ItemView>: View where ItemView: View, Item: Idetifiable {
                var items: [Item]
                var aspectRatio: CGFloat
                var content: (Item) -> ItemView
                          
                init(items: [Item], aspectRatio: CGFloat, @ViewBuilder content: @escaping (Item) -> ItemView) {
                    self.items = items
                    self.aspectRatio = aspectRatio
                    self.content = content
                }
            }
          
    • Shape [51:37]
      • Shape 뷰를 상속하는 프로토콜이다. 따라서 모든 shape는 뷰이다. RoundedRectangle , Circle 등이 있다.
      • Shape는 foreground color 을 채움으로써 그려진다. .stroke()나 .fill()을 이용하여 선이나 면에 대한 스타일 변경이 가능하다.

          func fill<S>(_ whatToFillWith: S)  some View where S: ShapeStyle
        
        • fill의 파라미터 타입은 Generic 타입이며, ShapeStyle을 채택한 것을 입력으로 받을 수 있다.
        • 예) Color, ImagePaint, AngularGradient, LinearGradient 등
      • 나만의 Shape을 만들려면?
        • Shape 프로토콜을 채택해야한다.
        • Shape 프로토콜의 extension에 body var이 기본적으로 구현되어 있다. (따로 구현할 필요 없다.)
        • 하지만 path 메서드를 구현해야한다.

            func path(in rect: CGRect)  Path {  return a Path }
          
          • Path은 lines, arcs, bezirer curves 등을 통해 구성된다.
      • Shape는 뷰와 다르게, animate이 가능하다.
        • 따라서 내부 프로퍼티에서 animate 동안 변하는 프로퍼티는 var로 선언해준다.

            struct Pie: Shape {
                var startAngle: Angle
                var endAngle: Angle
                ...
            }
          
      • Shape의 Path를 그릴때 사용되는 좌표 시스템의 원점은 좌상단이며, 오른쪽으로 x가 증가하고 아래로 갈 수록 y 값이 증가하는 좌표평면이다. (일반적으론 좌하단이 원점이고, 오른쪾으로 갈수록 x가 증가하고, 위로 올라갈수록 y가 증가하지만 Shape는 반대이다.) [1:19:34]

참고