onevcat / ObservationBP

Proof of concept for back-porting Observation framework to earlier iOS versions
119 stars 15 forks source link

ObservationView不修改body的改进尝试 #1

Open winddpan opened 1 year ago

winddpan commented 1 year ago

通过_makeView可以不修改body的实现达成 inspired

struct ContentView: View {
  private var person = Person(name: "Tom", age: 12)

  var body: some View {
    VStack {
      Text(person.name)
      Text("\(person.age)")
      HStack {
        Button("+") { person.age += 1 }
        Button("-") { person.age -= 1 }
      }
      .padding()
    }
  }

  @ViewBuilder
  private var observationView: ObservationView<Body> {
    ObservationView {
      body
    }
  }

  static func _makeView(view: SwiftUI._GraphValue<Self>, inputs: SwiftUI._ViewInputs)
    -> SwiftUI._ViewOutputs
  {
    return ObservationView<Body>._makeView(view: view[\.observationView], inputs: inputs)
  }

  static func _makeViewList(view: SwiftUI._GraphValue<Self>, inputs: SwiftUI._ViewListInputs)
    -> SwiftUI._ViewListOutputs
  {
    return ObservationView<Body>._makeViewList(view: view[\.observationView], inputs: inputs)
  }

  static func _viewListCount(inputs: SwiftUI._ViewListCountInputs) -> Int? {
    return ObservationView<Body>._viewListCount(inputs: inputs)
  }
}

包装一个协议

protocol ObservationBPView: View {}

extension ObservationBPView {
  @ViewBuilder
  private var observationView: ObservationView<Body> {
    ObservationView {
      body
    }
  }

 static func _makeView(view: SwiftUI._GraphValue<Self>, inputs: SwiftUI._ViewInputs)
    -> SwiftUI._ViewOutputs
  {
    return ObservationView<Body>._makeView(view: view[\.observationView], inputs: inputs)
  }

 static func _makeViewList(view: SwiftUI._GraphValue<Self>, inputs: SwiftUI._ViewListInputs)
    -> SwiftUI._ViewListOutputs
  {
    return ObservationView<Body>._makeViewList(view: view[\.observationView], inputs: inputs)
  }

 static func _viewListCount(inputs: SwiftUI._ViewListCountInputs) -> Int? {
    return ObservationView<Body>._viewListCount(inputs: inputs)
  }
}

struct ContentView: ObservationBPView {
  ...
}

包装一个Macro

@ObservationBPView
struct ContentView: View {
  ...

  // Macro expends below
  @ViewBuilder
  private var observationView: ObservationView<Body> {
    ObservationView {
      body
    }
  }

  static func _makeView(view: SwiftUI._GraphValue<Self>, inputs: SwiftUI._ViewInputs)
    -> SwiftUI._ViewOutputs
  {
    return ObservationView<Body>._makeView(view: view[\.observationView], inputs: inputs)
  }

  static func _makeViewList(view: SwiftUI._GraphValue<Self>, inputs: SwiftUI._ViewListInputs)
    -> SwiftUI._ViewListOutputs
  {
    return ObservationView<Body>._makeViewList(view: view[\.observationView], inputs: inputs)
  }

  static func _viewListCount(inputs: SwiftUI._ViewListCountInputs) -> Int? {
    return ObservationView<Body>._viewListCount(inputs: inputs)
  }
}
winddpan commented 1 year ago

Day2

发现只要View中有StateObject就报错: Accessing StateObject's object without being installed on a View. This will create a new instance each time.

The only dynamic property a VersionedView / VersionedViewModifier can only contain is a Binding

一种取巧的尝试

@Observable final class Person {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

class Ref: ObservableObject {
    let randomColor = Color(
        red: .random(in: 0 ... 1),
        green: .random(in: 0 ... 1),
        blue: .random(in: 0 ... 1)
    )
}

struct ContentView: View {
    private var person = Person(name: "Tom", age: 12)
    @StateObject private var ref = Ref()
    @State private var randomColor = Color(
        red: .random(in: 0 ... 1),
        green: .random(in: 0 ... 1),
        blue: .random(in: 0 ... 1)
    )

    var body: some View {
        VStack {
            Text(person.name)
            Text("\(person.age)")

            HStack {
                Button("+") { person.age += 1 }
                Button("-") { person.age -= 1 }
                Button("name") { person.name += "@" }
            }
        }
        .padding()
        .background(randomColor)
        .foregroundColor(ref.randomColor)
    }
}

extension ContentView {
    private struct Impl: View {
        private var person = Person(name: "Tom", age: 12)
        @StateObject private var ref = Ref()
        @State private var randomColor = Color(
            red: .random(in: 0 ... 1),
            green: .random(in: 0 ... 1),
            blue: .random(in: 0 ... 1)
        )

        var body: some View {
            ObservationView {
                VStack {
                    Text(person.name)
                    Text("\(person.age)")

                    HStack {
                        Button("+") { person.age += 1 }
                        Button("-") { person.age -= 1 }
                        Button("name") { person.name += "@" }
                    }
                }
                .padding()
                .background(randomColor)
                .foregroundColor(ref.randomColor)
            }
        }
    }

    @ViewBuilder
    @MainActor
    private var observationBody: Impl {
        var mutatingSelf = self
        let ptr = withUnsafePointer(to: &mutatingSelf) { UnsafeRawPointer($0) }
        ptr.load(as: Impl.self)
    }

    static func _makeView(view: SwiftUI._GraphValue<Self>, inputs: SwiftUI._ViewInputs)
        -> SwiftUI._ViewOutputs {
        Impl._makeView(view: view[\.observationBody], inputs: inputs)
    }

    static func _makeViewList(view: SwiftUI._GraphValue<Self>, inputs: SwiftUI._ViewListInputs)
        -> SwiftUI._ViewListOutputs {
        Impl._makeViewList(view: view[\.observationBody], inputs: inputs)
    }

    static func _viewListCount(inputs: SwiftUI._ViewListCountInputs) -> Int? {
        Impl._viewListCount(inputs: inputs)
    }
}

但失去了原body断点的能力(实际调用了Impl的body),增加了一点包体积。😂😂😂 我决定在一个小项目上实验一下。

Rex-xingjl commented 1 year ago

进展咋样了 老哥

winddpan commented 1 year ago

进展咋样了 老哥

https://github.com/winddpan/ObservationBP/blob/Observing-DynamicProperty/Demo/ObservationBPSwiftUIDemo/ObservationBPSwiftUIDemo/DevView.swift

Rex-xingjl commented 1 year ago

进展咋样了 老哥

  • _makeView方式无法做到局部刷新,已放弃。
  • ObservationView方式对延时加载的闭包无法处理,需要在延时调用的地方再包一层ObservationView,是一种负担。
  • 目前在尝试手动控制ObservationTracking._AccessList开闭,API上加个@Observing即可,运行看起来没有什么问题(玩具阶段)

https://github.com/winddpan/ObservationBP/blob/Observing-DynamicProperty/Demo/ObservationBPSwiftUIDemo/ObservationBPSwiftUIDemo/DevView.swift

我也在尝试ObservationView这种形式 @Observing 这种方案不错 我也尝试一下

Rex-xingjl commented 12 months ago

进展咋样了 老哥

  • _makeView方式无法做到局部刷新,已放弃。
  • ObservationView方式对延时加载的闭包无法处理,需要在延时调用的地方再包一层ObservationView,是一种负担。
  • 目前在尝试手动控制ObservationTracking._AccessList开闭,API上加个@Observing即可,运行看起来没有什么问题(玩具阶段)

https://github.com/winddpan/ObservationBP/blob/Observing-DynamicProperty/Demo/ObservationBPSwiftUIDemo/ObservationBPSwiftUIDemo/DevView.swift

老哥 集成到项目里时 我遇到了一个问题:怎么把package结构移除掉呢 ObservationBP这个我打了个xcframework 但是Macro和Swift5.9的package 解决不掉了... 有下面这个报错 : External macro implementation type 'ObservationBPMacrosMacros.ObservableMacro' could not be found for macro 'Observable()'

winddpan commented 11 months ago

进展咋样了 老哥

  • _makeView方式无法做到局部刷新,已放弃。
  • ObservationView方式对延时加载的闭包无法处理,需要在延时调用的地方再包一层ObservationView,是一种负担。
  • 目前在尝试手动控制ObservationTracking._AccessList开闭,API上加个@Observing即可,运行看起来没有什么问题(玩具阶段)

https://github.com/winddpan/ObservationBP/blob/Observing-DynamicProperty/Demo/ObservationBPSwiftUIDemo/ObservationBPSwiftUIDemo/DevView.swift

老哥 集成到项目里时 我遇到了一个问题:怎么把package结构移除掉呢 ObservationBP这个我打了个xcframework 但是Macro和Swift5.9的package 解决不掉了... 有下面这个报错 : External macro implementation type 'ObservationBPMacrosMacros.ObservableMacro' could not be found for macro 'Observable()'

Macro的包官方只支持用SPM集成,貌似有Cocoapods的方式但是没有研究。 一周前的版本还有一些问题,今天我提交了一个新的版本,解决了实例保存、局部刷新部分情况失效、内存泄漏等问题。如果可以的话请帮我测试一下。

Rex-xingjl commented 11 months ago

Cocoapods的形式,我试了一下 没有成功,因为没法解决宏替换的问题。

  1. ObservationBP用到了swift5.9的Macro,而Macro必须由Package来包装。
  2. Package这种形式和LocalPods似乎不兼容。

最近在排期内,闲了再来进行这个继续研究。

beforeold commented 11 months ago

进展咋样了 老哥

  • _makeView方式无法做到局部刷新,已放弃。
  • ObservationView方式对延时加载的闭包无法处理,需要在延时调用的地方再包一层ObservationView,是一种负担。
  • 目前在尝试手动控制ObservationTracking._AccessList开闭,API上加个@Observing即可,运行看起来没有什么问题(玩具阶段)

https://github.com/winddpan/ObservationBP/blob/Observing-DynamicProperty/Demo/ObservationBPSwiftUIDemo/ObservationBPSwiftUIDemo/DevView.swift

很好很强大,我 fork 下来发现无法在 @Observable 修饰的对象中新增计算属性,比如在 Person 中添加 var myName: String { name } 会报错:

'accessor' macro cannot be attached to getter

winddpan commented 11 months ago

进展咋样了 老哥

  • _makeView方式无法做到局部刷新,已放弃。
  • ObservationView方式对延时加载的闭包无法处理,需要在延时调用的地方再包一层ObservationView,是一种负担。
  • 目前在尝试手动控制ObservationTracking._AccessList开闭,API上加个@Observing即可,运行看起来没有什么问题(玩具阶段)

https://github.com/winddpan/ObservationBP/blob/Observing-DynamicProperty/Demo/ObservationBPSwiftUIDemo/ObservationBPSwiftUIDemo/DevView.swift

很好很强大,我 fork 下来发现无法在 @observable 修饰的对象中新增计算属性,比如在 Person 中添加 var myName: String { name } 会报错:

'accessor' macro cannot be attached to getter

已同步apple swift lib 最新版本