Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SwiftUI 학습 내용 #5

Open
joseph704 opened this issue Nov 11, 2022 · 1 comment
Open

SwiftUI 학습 내용 #5

joseph704 opened this issue Nov 11, 2022 · 1 comment
Assignees
Labels
🙏 Help 조언, 경험공유 등 도움이 필요

Comments

@joseph704
Copy link
Member

joseph704 commented Nov 11, 2022

[State](https://developer.apple.com/documentation/swiftui/state)

  • SwiftUI에서 관리하는 값을 일고 쓸 수 있는 프로퍼티 래퍼 타입

  • SwiftUI는 개발자가 state로써 선언한 프로퍼티의 Storage를 관리함

  • 프로퍼티의 값이 변경될 때, SwiftUI는 프로퍼티의 값에 의존되어 있는 View-Hierarchy의 일부분을 업데이트 시킴

  • View-Hierarchy에 저장된 특정 값에 대한 단일 진실소스(?)로 State를 사용하셈

  • 하나의 State 인스턴스는 값 그자체가 아님, 값을 읽고 쓰는 수단임

  • State의 기본값에 접근하기 위해선, 프로퍼티 이름으로 해당값을 참조하면 됨(프로퍼티 값을 wrapping한 값이 return됨)

    struct PlayButton: View {
        @State private var isPlaying: Bool = false
    
        var body: some View {
            Button(isPlaying ? "Pause" : "Play") {
                isPlaying.toggle()
            }
        }
    }
  • 만약 State 프로퍼티를 자식뷰에 전달하면, SwiftUI는 부모뷰에서 프로퍼티 값이 변경될때 마다 업데이트 할 것임, 하지만 자식은 프로퍼티 값을 변경할 수 없음

  • 자식뷰에서 저장된 값을 변경할 수 있도록 State 대신 Binding을 선언하면됨. State의 ProjectedValue(프로퍼티 앖에 달러$ 표시)를 엑세스 함으로써 State 값에 대한 바인딩을 얻을 수 있음

    struct PlayerView: View {
        var episode: Episode
        @State private var isPlaying: Bool = false
    
        var body: some View {
            VStack {
                Text(episode.title)
                    .foregroundStyle(isPlaying ? .primary : .secondary)
                PlayButton(isPlaying: $isPlaying) // Pass a binding.
            }
        }
    }
    
    struct PlayButton: View {
        @Binding var isPlaying: Bool
    
        var body: some View {
            Button(isPlaying ? "Pause" : "Play") {
                isPlaying.toggle()
            }
        }
    }
  • View를 인스턴스화는 곳에서 View의 State 프로퍼티를 초기화 하지 마셈. 왜냐하면 SwiftUI에서 제공하는 Storage Management와 충돌날 수 있기 때문.

    • 이 점을 방지하기 위해, 항상 State 프로퍼티를 private으로 설정하면됨. 그리고 해당 값에 대한 접근이 필요한 View-hierarchy에서 제일 상위 뷰에 위치하게끔 하면됨. 그런다음 읽기 전용 엑세스 또는 읽기-쓰기 엑세스에 대한 바인딩에 대한 접근이 필요한 모든 자식뷰들에게 State를 공유하면 됨.
  • 이로써 안전하게 모든 스레드로 부터 State 프로퍼티를 변형시킬 수 있음

[Publisher](https://developer.apple.com/documentation/combine/publisher)

  • 시간의 경과에 따라 일련의 값을 전달할수 있는 타입을 선언한 프로토콜

  • 하나의 Publisher는 하나 이상의 Subscriber 인스턴스에 요소들을 전달함

  • 그 Subscriber의 associated types(Input과 Failure)은 Publisher에 의해 선언된 associated types(Output과 Failure)와 매칭 되어야 함

  • Publisher는 Subscriber를 받아드리기 위해 [receive(subscriber:)](https://developer.apple.com/documentation/combine/publisher/receive(subscriber:))

  • 그 다음, Publisher는 Subscriber를 다음과 같은 메소드들로 부를 수 있음

    • [receive(subscription:)](https://developer.apple.com/documentation/combine/subscriber/receive(subscription:)): subscribe 요청을 승인하고 Subscription 인스턴스를 반환함, Subscriber는 Subscription을 사용해 Publisher로부터 요소들을 요구하고 Publishing을 취소하는 데 사용할 수 있음
    • [receive(_:)](https://developer.apple.com/documentation/combine/subscriber/receive(_:)): Publisher로부터 Subscriber에게 하나의 요소를 전달함
    • [receive(completion:)](https://developer.apple.com/documentation/combine/subscriber/receive(completion:)): Subscriber에게 Publishing이 정상적으로 또는 오류로 종료되었음을 알림

    모든 Pubsliher는 downstream Subscriber가 올바르게 작동하려면 이 규약들을 준수해야 함

  • Publisher의 Extension들은 정교한 이벤트-처리 체인들을 만들기 위해 다양한 operator들을 정의함 각 연사자는 Publisher 프로토콜을 채택한 타입을 반환함. 이러한 타입의 대부분은 Publisher enumeration로써 존재함. 예를들어 [map(_:)](https://developer.apple.com/documentation/combine/publisher/map(_:)-99evh) 연산자는 [Publishers.Map](https://developer.apple.com/documentation/combine/publishers/map) 인스턴스를 반환함

  • Publisher 프로토콜을 직접 구현하는 대신, combine 프레임워크에서 제공하는 여러 타입들 중 하나를 사용하여 자신만의 Publisher를 만들 수 있음!!

    • PassThroughSubject와 같은 Subject 프로토콜의 구체적인 서브클래스를 사용하여 [send(_:)](https://developer.apple.com/documentation/combine/subject/send(_:))
      메소드를 호출하여 주문형 값을 Publish하시오
    • Subject의 기본 값을 업데이트할 때마다 CurrentValueSubject를 사용하여 Publish하시오
    • @published annotation을 자신의 타입의 프로퍼티에 추가하시오. 그렇게 함으로써, 프로퍼티는 프로퍼티의 값이 변경될 때 마다 이벤트를 방출하는 하나의 Publisher를 얻음.
    class Weather {
        @Published var temperature: Double
        init(temperature: Double) {
            self.temperature = temperature
        }
    }
    
    let weather = Weather(temperature: 20)
    cancellable = weather.$temperature
        .sink() {
            print ("Temperature now: \($0)")
    }
    weather.temperature = 25

[Subscriber](https://developer.apple.com/documentation/combine/subscriber)

  • Publisher로 부터 입력을 받을 수 있는 타입을 선언한 프로토콜
  • Subscriber 인스턴스는 Publisher로 부터 요소들의 stream을 받음, 그들의 관계의 변화를 설명하는 life-cycle 이벤트들과 함께.
  • 주어진 Subscriber들의 associated type(Input과 Failure) 해당 Publisher의 Output과 Failure와 매칭 되어야 함
  • Publisher의 [subscribe(_:)](https://developer.apple.com/documentation/combine/publisher/subscribe(_:)-4u8kn) 메소드를 호출하여 Subscriber를 Publisher에게 연결함
  • [subscribe(_:)](https://developer.apple.com/documentation/combine/publisher/subscribe(_:)-4u8kn) 메소드 호출 후, Publisher은 Subscriber의 [receive(subscription:)](https://developer.apple.com/documentation/combine/subscriber/receive(subscription:)) 메소드를 호출함
  • 이것은 Subscriber에게 Publisher로 부터 요소를 요구하고 선택적으로 Subscription을 취소하는데 사용되는 Subscription 인스턴스를 제공함
  • Subsrciber이 초기화 요구를 한 후, Publisher는 새로 Publish된 요소들을 전달하기 위해 [receive(_:)](https://developer.apple.com/documentation/combine/subscriber/receive(_:)) 메소드를 비동기적으로 호출함
  • Publisher가 Publish를 중단하면 Publish가 정상적으로 완료되는지 또른 오류가 있는지 나타나기 위해,[Subscribers.Completion](https://developer.apple.com/documentation/combine/subscribers/completion) 타입의 파라미터를 가진 [receive(completion:)](https://developer.apple.com/documentation/combine/subscriber/receive(completion:)) 메소드를 사용함
  • Combine은 Publisher 타입의 Operator로 다음 Subscriber를 제공함
    • [sink(receiveCompletion:receiveValue:)](https://developer.apple.com/documentation/combine/publisher/sink(receivecompletion:receivevalue:)) 는 completion 신호를 수신하고 새 요소를 받을 때마다 임의의 클로저를 실행함
    • [assign(to:on:)](https://developer.apple.com/documentation/combine/publisher/assign(to:on:))는 각각의 새로 수신된 값을 주어진 인스턴스의 키경로로 식별된 프로퍼티에 씀

[ObservableObject](https://developer.apple.com/documentation/combine/observableobject)

  • 객체가 변경 되기 전에 방출하는 publisher가 있는 객체 유형

  • ObservableObject는 @published 프로퍼티가 변경되기 전에 변경된 값을 내보내는 objectWillChange라는 publisher를 합성합니다.

    class Contact: ObservableObject {
        @Published var name: String
        @Published var age: Int
    
        init(name: String, age: Int) {
            self.name = name
            self.age = age
        }
    
        func haveBirthday() -> Int {
            age += 1
            return age
        }
    }
    
    let john = Contact(name: "John Appleseed", age: 24)
    cancellable = john.objectWillChange
        .sink { _ in
            print("\(john.age) will change")
    }
    print(john.haveBirthday())
    // Prints "24 will change"
    // Prints "25"
  • objectWillChange(ObservableObjectPublisher타입)은 send()함수를 통해 변경된 사항이 있다고 알려줍니다.

    let objectWillChange = PassthroughSubject<ViewRouter, Never>()
    
       var currentPage: String = "page1" {
           didSet {
               objectWillChange.send(self)
           }
       }
    
       @Published var currentPage: String = "page1"

[List](https://developer.apple.com/documentation/swiftui/list)

  • dynamically-generated list

    List는 식별 가능한 데이터를 통해 dynamically-generated list로 동작할수 있다.

    식별 가능한 데이터는 두가지로 만들 수 있다.

    1. 각 요소(객체)를 고유하게 식별하는 프로퍼티를 포함한 데이터 타입에 포함. List 사용 시, 고유 식별 프로퍼티 경로 설정

      // Model
      struct Landmark: Codable {
          var id: Int  // 고유 식별 프로퍼티
          var name: String
      }
      
      // View
      struct LandmarkList: View {
          var body: some View {
              List(landmarks, id: \.id) // 고유 식별 프로퍼티 경로 설정 
                                         { landmark in
                  LandmarkRow(landmark: landmark) // landmarks배열의 각각 요소에 대한 LandmarkRow 뷰를 생성함
              }
          }
      }
    2. Identifiable 프로토콜을 채택한 데이터 타입

      // Model
      struct Landmark: Codable, Identifiable {
          var id: Int  // Identifiable를 준수하는 id 프로퍼티 
          var name: String
      }
      
      // View
      struct LandmarkList: View {
          var body: some View {
              List(landmarks) { landmark in // 고유 식별 프로퍼티 경로 지정할 필요 없음.
                  LandmarkRow(landmark: landmark) // landmarks배열의 각각 요소에 대한 LandmarkRow 뷰를 생성함
              }
          }
      }
    • Identifiable 프로토콜

      다음 구조체를 살펴보자

      struct Person: Equatable {
      	let name: String
      	let age: Int
      }
      
      let person1 = Person(name: "bigbang", age: 30)
      let person2 = Person(name: "bigbang", age: 30)
      
      person1 == person2 // true

      Equatable 프로토콜을 채택한 구조체는 아래 코드처럼 비교 할수 있음


      그리고 다음 예제를 살펴보자

      struct Person: Equatable {
      	let name: String
      	let age: Int
        let id: Int = 7777
      }
      
      let person3 = Person(name: "joseph", age: 28)
      let person4 = Person(name: "steve", age: 26)
      
      person3 == person4 // false

      name과 age가 다른 Person의 객체들은 == 연산자로 비교 시 다름


      person3.id == person4.id

      동일한 클래스의 객체임을 판단하려면 id 프로퍼티를 통해 비교해 보면 됨

      그러고 나서, Identifiable Protocol을 살펴 보자

      Identifiable Protocol

      public protocol Identifiable<ID> {
      
          /// A type representing the stable identity of the entity associated with
          /// an instance.
          associatedtype ID : Hashable
      
          /// The stable identity of the entity associated with this instance.
          var id: Self.ID { get }
      }

      출처: 애플공홈(https://developer.apple.com/documentation/swift/identifiable)

      출처: 애플공홈(https://developer.apple.com/documentation/swift/identifiable)

      Identifiable 프로토콜은 자신을 채택한 데이터 타입에게 안정적인 ID 개념을 제공

      예를 들어, 앱과 앱의 데이터베이스 스토리지 전체에서 안정적인 id 속성을 가진 데이터 타입을 정의할 수 있음

      사용자 이름과 같은 프로퍼티가 변경되더라도 id 속성을 사용하여 특정 데이터 타입을 식별할 수 있음.

      Identifiable은 다음과 같은 특징들을 가지고 있음

      1. UUID와 같이 항상 고유하게 보장됨
      2. 데이터베이스 레코드 키와 같이 환경별로 지속적으로 고유함
      3. 프로세스의 lifetime에 대해 고유함
      4. 객체의 수명 동안 고유함
      5. 현재 컬렉션 내에서 고유함
@joseph704 joseph704 added the 🙏 Help 조언, 경험공유 등 도움이 필요 label Nov 11, 2022
@joseph704 joseph704 added this to the SwiftUI 학습 milestone Nov 11, 2022
@joseph704 joseph704 self-assigned this Nov 11, 2022
@joseph704
Copy link
Member Author

joseph704 commented Nov 17, 2022

image

내용 변경 이력은 여기서 확인하시면 됩니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🙏 Help 조언, 경험공유 등 도움이 필요
Projects
None yet
Development

No branches or pull requests

1 participant