@@ -30,15 +30,51 @@ public struct Flag<Value>: Decorated, Identifiable where Value: FlagValue {
3030
3131 // MARK: - Properties
3232
33+ // FlagContainers may have many flags, so to reduce code bloat
34+ // it's important that each Flag have as few stored properties
35+ // (with nontrivial copy behavior) as possible. We therefore use
36+ // a single `Allocation` for all of Flag's stored properties.
37+ var allocation : Allocation
38+
3339 /// All `Flag`s are `Identifiable`
34- public var id = UUID ( )
40+ public var id : UUID {
41+ get {
42+ allocation. id
43+ }
44+ set {
45+ if isKnownUniquelyReferenced ( & allocation) == false {
46+ allocation = allocation. copy ( )
47+ }
48+ allocation. id = newValue
49+ }
50+ }
3551
3652 /// A collection of information about this `Flag`, such as its display name and description.
37- public var info : FlagInfo
53+ public var info : FlagInfo {
54+ get {
55+ allocation. info
56+ }
57+ set {
58+ if isKnownUniquelyReferenced ( & allocation) == false {
59+ allocation = allocation. copy ( )
60+ }
61+ allocation. info = newValue
62+ }
63+ }
3864
3965 /// The default value for this `Flag` for when no sources are available, or if no
4066 /// sources have a value specified for this flag.
41- public var defaultValue : Value
67+ public var defaultValue : Value {
68+ get {
69+ allocation. defaultValue
70+ }
71+ set {
72+ if isKnownUniquelyReferenced ( & allocation) == false {
73+ allocation = allocation. copy ( )
74+ }
75+ allocation. defaultValue = newValue
76+ }
77+ }
4278
4379 /// The `Flag` value. This is a calculated property based on the `FlagPole`s sources.
4480 public var wrappedValue : Value {
@@ -48,7 +84,7 @@ public struct Flag<Value>: Decorated, Identifiable where Value: FlagValue {
4884 /// The string-based Key for this `Flag`, as calculated during `init`. This key is
4985 /// sent to the `FlagValueSource`s.
5086 public var key : String {
51- return self . decorator . key!
87+ return self . allocation . key!
5288 }
5389
5490 /// A reference to the `Flag` itself is available as a projected value, in case you need
@@ -77,12 +113,12 @@ public struct Flag<Value>: Decorated, Identifiable where Value: FlagValue {
77113 /// You can also specify `.hidden` to hide this flag from Vexillographer.
78114 ///
79115 public init ( name: String ? = nil , codingKeyStrategy: CodingKeyStrategy = . default, default initialValue: Value , description: FlagInfo ) {
80- self . codingKeyStrategy = codingKeyStrategy
81- self . defaultValue = initialValue
82-
83- var info = description
84- info . name = name
85- self . info = info
116+ self . init (
117+ wrappedValue : initialValue,
118+ name : name ,
119+ codingKeyStrategy : codingKeyStrategy ,
120+ description : description
121+ )
86122 }
87123
88124 /// Initialises a new `Flag` with the supplied info.
@@ -101,46 +137,49 @@ public struct Flag<Value>: Decorated, Identifiable where Value: FlagValue {
101137 /// You can also specify `.hidden` to hide this flag from Vexillographer.
102138 ///
103139 public init ( wrappedValue: Value , name: String ? = nil , codingKeyStrategy: CodingKeyStrategy = . default, description: FlagInfo ) {
104- self . codingKeyStrategy = codingKeyStrategy
105- self . defaultValue = wrappedValue
106-
107140 var info = description
108141 info. name = name
109- self . info = info
142+ self . allocation = Allocation (
143+ info: info,
144+ defaultValue: wrappedValue,
145+ codingKeyStrategy: codingKeyStrategy
146+ )
110147 }
111148
112149
113150 // MARK: - Decorated Conformance
114151
115- internal var decorator = Decorator ( )
116- internal let codingKeyStrategy : CodingKeyStrategy
117-
118152 /// Decorates the receiver with the given lookup info.
119153 ///
120154 /// `self.key` is calculated during this step based on the supplied parameters. `lookup` is used by `self.wrappedValue`
121155 /// to find out the current flag value from the source hierarchy.
122156 ///
123- internal func decorate ( lookup: Lookup , label: String , codingPath: [ String ] , config: VexilConfiguration ) {
124- self . decorator. lookup = lookup
125-
126- var action = self . codingKeyStrategy. codingKey ( label: label)
157+ internal func decorate (
158+ lookup: Lookup ,
159+ label: String ,
160+ codingPath: [ String ] ,
161+ config: VexilConfiguration
162+ ) {
163+ self . allocation. lookup = lookup
164+
165+ var action = self . allocation. codingKeyStrategy. codingKey ( label: label)
127166 if action == . default {
128167 action = config. codingPathStrategy. codingKey ( label: label)
129168 }
130169
131170 switch action {
132171
133172 case . append( let string) :
134- self . decorator . key = ( codingPath + [ string] )
173+ self . allocation . key = ( codingPath + [ string] )
135174 . joined ( separator: config. separator)
136175
137176 case . absolute( let string) :
138- self . decorator . key = string
177+ self . allocation . key = string
139178
140179 // these two options should really never happen, but just in case, use what we've got
141180 case . default, . skip:
142181 assertionFailure ( " Invalid `CodingKeyAction` found when attempting to create key name for Flag \( self ) " )
143- self . decorator . key = ( codingPath + [ label] )
182+ self . allocation . key = ( codingPath + [ label] )
144183 . joined ( separator: config. separator)
145184
146185 }
@@ -150,7 +189,7 @@ public struct Flag<Value>: Decorated, Identifiable where Value: FlagValue {
150189 // MARK: - Lookup Support
151190
152191 func value ( in source: FlagValueSource ? ) -> LookupResult < Value > ? {
153- guard let lookup = self . decorator . lookup, let key = self . decorator . key else {
192+ guard let lookup = self . allocation . lookup, let key = self . allocation . key else {
154193 return LookupResult ( source: nil , value: self . defaultValue)
155194 }
156195 let value : LookupResult < Value > ? = lookup. lookup ( key: key, in: source)
@@ -192,6 +231,52 @@ extension Flag: CustomDebugStringConvertible {
192231}
193232
194233
234+ // MARK: - Property Storage
235+
236+ extension Flag {
237+
238+ final class Allocation {
239+ var id : UUID
240+ var info : FlagInfo
241+ var defaultValue : Value
242+
243+ // these are computed lazily during `decorate`
244+ var key : String ?
245+ weak var lookup : Lookup ?
246+
247+ var codingKeyStrategy : CodingKeyStrategy
248+
249+ init (
250+ id: UUID = UUID ( ) ,
251+ info: FlagInfo ,
252+ defaultValue: Value ,
253+ key: String ? = nil ,
254+ lookup: Lookup ? = nil ,
255+ codingKeyStrategy: CodingKeyStrategy
256+ ) {
257+ self . id = id
258+ self . info = info
259+ self . defaultValue = defaultValue
260+ self . key = key
261+ self . lookup = lookup
262+ self . codingKeyStrategy = codingKeyStrategy
263+ }
264+
265+ func copy( ) -> Allocation {
266+ Allocation (
267+ id: id,
268+ info: info,
269+ defaultValue: defaultValue,
270+ key: key,
271+ lookup: lookup,
272+ codingKeyStrategy: codingKeyStrategy
273+ )
274+ }
275+ }
276+
277+ }
278+
279+
195280// MARK: - Real Time Flag Publishing
196281
197282#if !os(Linux)
@@ -206,7 +291,7 @@ public extension Flag where Value: FlagValue & Equatable {
206291 /// remove duplicates.
207292 ///
208293 var publisher : AnyPublisher < Value , Never > {
209- decorator . lookup!. publisher ( key: self . key)
294+ allocation . lookup!. publisher ( key: self . key)
210295 . removeDuplicates ( )
211296 . eraseToAnyPublisher ( )
212297 }
@@ -224,7 +309,7 @@ public extension Flag {
224309 /// remove duplicates.
225310 ///
226311 var publisher : AnyPublisher < Value , Never > {
227- decorator . lookup!. publisher ( key: self . key)
312+ allocation . lookup!. publisher ( key: self . key)
228313 }
229314
230315}
0 commit comments