Как лучше всего использовать структуры Codable с вызовами PATCH?
Думаю, мне нужно знать, какие свойства моей структуры были обновлены, а затем передавать только эти значения в качестве частичных обновлений для вызова патча.
Нужно ли мне использовать наблюдателей DidSet для всех свойств, чтобы я мог отметить, какой из них изменился, или есть лучший способ?
Спасибо
ОБНОВЛЕНИЕ: пожалуйста, проверьте комментарии к вопросу
@Sweeper Да, все правильно. Я спрашиваю, есть ли лучший подход к быстрой обработке вызовов PATCH? Codable обычно используются для передачи всех данных для вызовов POST. Что делать, если вам приходится иметь дело с частичными обновлениями?
Я придумал один из способов — использовать подобную обертку свойства, чтобы связать флаг didSet
с интересующими вас свойствами:
@propertyWrapper
public struct DidSetFlag<T: Codable>: Codable {
public var wrappedValue: T {
didSet {
didSet = true
}
}
public var didSet = false
public init(wrappedValue: T) {
self.wrappedValue = wrappedValue
}
public init(from decoder: any Decoder) throws {
wrappedValue = try T(from: decoder)
}
public func encode(to encoder: any Encoder) throws {
try wrappedValue.encode(to: encoder)
}
}
Затем «захватите» KeyedEncodingContainer.encode
, чтобы, если didSet
ложно, ничего не кодировалось:
extension KeyedEncodingContainer {
mutating func encode<T: Codable>(
_ value: DidSetFlag<T>,
forKey key: KeyedEncodingContainer<K>.Key
) throws {
if value.didSet {
try encode(value.wrappedValue, forKey: key)
}
}
}
Синтезированная реализация Codable
будет разрешаться в эту реализацию в расширении, если только тип Codable
не находится в другом модуле.
Пример использования:
struct Foo: Codable {
@DidSetFlag var foo = 0
@DidSetFlag var bar = 0
mutating func resetFlags() {
_foo.didSet = false
_bar.didSet = false
}
}
var foo = Foo()
let encoder = JSONEncoder()
print(String(data: try encoder.encode(foo), encoding:. utf8)!)
foo.foo = 1
print(String(data: try encoder.encode(foo), encoding:. utf8)!)
foo.bar = 2
print(String(data: try encoder.encode(foo), encoding:. utf8)!)
/*
output:
{}
{"foo":1}
{"bar":2,"foo":1}
*/
В качестве бонуса, вот реализация макроса, позволяющая автоматически добавлять @DidSetFlag
к каждому свойству, а также генерировать метод resetFlags
.
// declaration
@attached(memberAttribute)
@attached(member, names: named(resetFlags))
public macro EncodePropertiesWhenSet() = #externalMacro(module: "Your Module Here...", type: "EncodePropertiesWhenSet")
// implementation
enum EncodePropertiesWhenSet: MemberAttributeMacro, MemberMacro {
static func expansion(of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext) throws -> [DeclSyntax] {
let hasMutating = !declaration.is(ClassDeclSyntax.self)
let function = try FunctionDeclSyntax("\(raw: hasMutating ? "mutating " : "")func resetFlags()") {
for member in declaration.memberBlock.members {
if let name = name(for: member.decl) {
"_\(raw: name).didSet = false"
}
}
}
return [DeclSyntax(function)]
}
static func expansion(of node: AttributeSyntax, attachedTo declaration: some DeclGroupSyntax, providingAttributesFor member: some DeclSyntaxProtocol, in context: some MacroExpansionContext) throws -> [AttributeSyntax] {
if name(for: member) != nil {
["@DidSetFlag"]
} else {
[]
}
}
static func name(for decl: some DeclSyntaxProtocol) -> String? {
// filter out computed properties and 'let's
guard let varDecl = decl.as(VariableDeclSyntax.self),
varDecl.bindingSpecifier.text == "var",
varDecl.bindings.count == 1,
let binding = varDecl.bindings.first,
binding.accessorBlock == nil,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier.text
else { return nil }
return identifier
}
}
@main
struct MyMacroPlugin: CompilerPlugin {
let providingMacros: [Macro.Type] = [
EncodePropertiesWhenSet.self
]
}
// usage:
@EncodePropertiesWhenSet
struct Foo: Codable {
var foo = 0
var bar = 0
«Нужно ли мне использовать наблюдателей DidSet для всех свойств, чтобы я мог отметить, какое из них изменилось?» Это звучит правильно, если вы просто хотите знать, какие свойства изменились. Это будет не так утомительно, если вы напишете несколько макросов, которые сделают это за вас. Если я правильно понимаю, вы хотите, чтобы реализация
Encodable
кодировала только те свойства, которые изменились с... когда? Я предполагаю, что должно быть что-то вроде методаreset
, который сбрасывает все флаги?