美文网首页
cs193p_2021笔记[5]_Property Wrappe

cs193p_2021笔记[5]_Property Wrappe

作者: walkerwzy | 来源:发表于2021-11-08 01:08 被阅读0次

cs193p_2021_笔记_1
cs193p_2021_笔记_2
cs193p_2021_笔记_3_Animation_Transition
cs193p_2021_笔记_4_Color_Image_Gesture
cs193p_2021_笔记_5_Property Wrapper
cs193p_2021_笔记_6_Persistence
cs193p_2021_笔记_7_Document Architecture
cs193p_2021_笔记_8


Property Wrappers

C#中的Attributes,python中的Decorators, Java的Annonations,类似的设计模式。

  • A property wrapper is actually a struct.
  • 这个特殊的struct封装了一些模板行为应用到它们wrap的vars上:
    1. Making a var live in the heap (@State)
    2. Making a var publish its changes (@Published)
    3. Causing a View to redraw when a published change is detected (@ObservedObject)

即能够分配到堆上,能够通知状态变化和能重绘等,可以理解为语法糖

@Published var emojiArt: EmojiArt = EmojiArt()

// ... is really just this struct ...
struct Published {
    var wrappedValue: EmojiArt
    var projectedValue: Publisher<EmojiArt, Never>  // i.e. $
}

// `projected value`的类型取决于wrapper自己,比如本例就是一个`Publisher`

// 我理解为一个属性和一个广播器

// ... and Swift (approximately) makes these vars available to you ...
var _emojiArt: Published = Published(wrappedValue: EmojiArt()) 
var emojiArt: EmojiArt {
     get { _emojiArt.wrappedValue }
     set { _emojiArt.wrappedValue = newValue }
 }

把get,set直接通过$emojiArt(即projectedValue)来使用

当一个Published值发生变化:

  • It publishes the change through its projectedValue ($emojiArt) which is a Publisher.
  • It also invokes objectWillChange.send() in its enclosing ObservableObject.

下面列的几种Property wrapper,我们主要关心最核心的两个概念,wrappedValueprojectedValue是什么就行了:

@State

这是第二次提到了,在Property Observers一节里预告过,基本上点@的,大都为Property Wrapper的内容。

  • The wrappedValue is: anything (but almost certainly a value type).
  • What it does:
    • stores the wrappedValue in the heap;
    • when it changes, invalidates the View.
  • Projected value (i.e. $): a Binding (to that value in the heap).
@State private var foo: Int
init() {
    _foo = .init(initiaValue: 5)
}

注意_$的区别。

@StateObject & @ObservedObject

  • The wrappedValue is: anything that implements the ObservableObject protocol (ViewModels).
  • What it does:
    • invalidates the View when wrappedValue does objectWillChange.send().
  • Projected value (i.e. $): a Binding (to the vars of the wrappedValue (a ViewModel)).

@StateObject V.S. @State

  • 一个类型是ObservableObjects, 一个是value type

@StateObject V.S. @ObservedObject

  • @StateObject is a "source of truth",也就是说可以直接赋值:@StateObject var foo = SomeObservableObject()
  • 能用在View, APP, Scene等场景
  • 如果用在View里,生命周期与View一致
@main
struct EmojiArtApp: App {
    // stateObject, source of truth
    // defined in the app
    @StateObject var paletteStore = PaletteStore(named: "default")

    var body: some Scene {
    DocumentGroup(newDocument: { EmojiArtDocument() }) { config in
        EmojiArtDocumentView(document: config.document)
            .environmentObject(paletteStore)  // passed by environment
        }
    }
}

@Binding

  • The wrappedValue is: a value that is bound to something else.
  • What it does:
    • gets/sets the value of the wrappedValue from some other source.
    • when the bound-to value changes, it invalidates the View.
    • Form表单典型应用场景,有UI变化的控件
    • 手势过程中的State, 或drag时是否targted
    • 模态窗口的状态
    • 分割view后共享状态
    • 总之,数据源只有一个(source of the truth)的场景,就不需要用两个@State而用@Binding,
  • Projected value (i.e. $): a Binding (self; i.e. the Binding itself)
struct MyView: View {
      @State var myString = “Hello”               // 1
      var body: View {
          OtherView(sharedText: $myString)        // 2
      }
  }
  struct OtherView: View {
      @Binding var sharedText: string             // 3
      var body: View {
          Text(sharedText)                        // 4
          TextField("shared", text: $sharedText)  // 5 _myString.projectValue.projectValue
      }
}
  1. _myString是实际变量,包含一个wrappedValue,一个projectedValue
  2. myString就是_myString.wrappedValue
  3. $myString_myString.projectedValue
    • 是一个Binding<String>,传值和接值用的就是它
    • 所以传$myString的地方也可以用_myString.projectedValue代替,学习阶段的话
  4. 要把projectedValue层层传递下去,并不是用同一个projectedValue,而是设计成了Binding<T>
    • 参考上面代码块的第5条

其它

  • 也可以绑定一个常量:OtherView(sharedText: .constant(“Howdy”))
  • computed binding: Binding(get:, set:).

比如你的view是一个小组件,里面有一个Binding var user: User,那么在preview里面怎么传入这个User呢?用常量:

static var preview: some View {
    myView(user: .constant(User(...)))
}

@EnvironmenetObject

  • The wrappedValue is: ObservableObject obtained via .environmentObject() sent to the View.
  • What it does: invalidates the View when wrappedValue does objectWillChange.send().
  • Projected value (i.e. $): a Binding (to the vars of the wrappedValue (a ViewModel)).

@ObservedObject用法稍有点不同,有单独的赋值接口:

let myView = MyView().environmentObject(theViewModel)
// 而@ObservedObject是一个普通的属性
let myView = MyView(viewModel: theViewModel)

// Inside the View ...
@EnvironmentObject var viewModel: ViewModelClass 
// ... vs ...
@ObservedObject var viewModel: ViewModelClass
  • visible to all views in your body (except modallay presented ones)
  • 多用于多个view共享ViewModel的时候

@Environment

  • @EnvironmentObject完全不是同一个东西
  • 这是Property Wrapper不只有两个变量(warped..., projected...)的的一个应用
  • 通过keyPath来使用:@Environment(\.colorScheme) var colorScheme
  • wrappedValue的类型是通过keyPath声明时设置的
view.environment(\.colorScheme, .dark)

so:

  • The wrappedValue is: the value of some var in EnvironmentValues.
  • What it does: gets/sets a value of some var in EnvironmentValues.
  • Projected value (i.e. $): none.
// someView pop 一个 modal 的 myView,传递 environment
someView.sheet(isPresented: myCondition){
    myView(...init...)
    .enviroment(\.colorScheme, colorScheme) 
}

除了深色模式,还有一个典型的应用场景就是编辑模式\.editMode,比如点了编辑按钮后。

EditButton是一个封装了UI和行为的控件,它只做一件事,就是更改\.editmode这个环境变量(的isEditing)

@Publisher

It is an object that emits values and possibly a failure object if it fails while doing so.

Publisher<Output, Failure>
  • Failure需要实现Error,如果没有,可以传Never

订阅

一种简单用法,sink:

cancellable = myPublisher.sink(
    receiveCompletion:{resultin...}, //result is a Completion<Failure> enum
        receiveValue: { thingThePublisherPublishes in . . . }
  )

返回一个Cancellable,可以随时.cancel(),只要你持有这个cancellable,就能随时用这个sink

View有自己的订阅方式:

.onReceive(publisher) { thingThePublisherPublishes in
    // do whatever you want with thingThePublisherPublishes 
}
  1. .onReceive will automatically invalidate your View (causing a redraw).
  2. 既然参数是publisher,所以是一个binding的变量,即带$使用:
.onReceive($aBindData) { bind_data in 
    // my code
}

publisher来源:

  1. $ in front of vars marked @Published
    • 还记得$就是取的projectedValue吗?
    • 一般的projectedValue是一个Binding,Published的是是个Publisher
  2. URLSession’s dataTaskPublisher (publishes the Data obtained from a URL)
  3. Timer’s publish(every:) (periodically publishes the current date and time as a Date)
  4. NotificationCenter’s publisher(for:) (publishes notifications when system events happen)

如果你有一个ObservedObject(Document),它里面有一个@Publisher(background),那么注意以下两者的区别:

  • document.$background: 是一个publisher
  • $document.background: 是一个binding

.onReceive只能接收Publisher的推送,而事实上,onChange(一般用于接收ObservedObject或State)同样也能接收Publisher。

相关文章

网友评论

      本文标题:cs193p_2021笔记[5]_Property Wrappe

      本文链接:https://www.haomeiwen.com/subject/jebdzltx.html