Skip to content

Commit 0b8977c

Browse files
committed
Added pressure
1 parent f10666f commit 0b8977c

File tree

1 file changed

+104
-30
lines changed

1 file changed

+104
-30
lines changed

Sources/Footprint/Footprint.swift

Lines changed: 104 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import Foundation
3232
/// caches being purged based on the users behavior and the memory footprint
3333
/// used by your app has a much lower upper bound and much smaller drops.
3434
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, visionOS 1.0, *)
35-
final public class Footprint : Sendable {
35+
final public class Footprint : @unchecked Sendable {
3636

3737
/// A structure that represents the different values required for easier memory
3838
/// handling throughout your apps lifetime.
@@ -71,10 +71,13 @@ final public class Footprint : Sendable {
7171
/// The state describing where your app sits within the scope of its memory limit.
7272
public let state: State
7373

74+
/// The state of memory pressure (aka. how close the app is to being Jetsamed/Jetisoned).
75+
public let pressure: State
76+
7477
/// The time at which this snapshot was taken in monotonic milliseconds of uptime.
7578
public let timestamp: UInt64
7679

77-
init() {
80+
init(memoryPressure: State = .normal) {
7881
var info = task_vm_info_data_t()
7982
var infoCount = TASK_VM_INFO_COUNT
8083

@@ -101,6 +104,7 @@ final public class Footprint : Sendable {
101104
usedRatio < 0.50 ? .warning :
102105
usedRatio < 0.75 ? .urgent :
103106
usedRatio < 0.90 ? .critical : .terminal
107+
pressure = memoryPressure
104108
timestamp = {
105109
let time = mach_absolute_time()
106110
var timebaseInfo = mach_timebase_info_data_t()
@@ -124,19 +128,31 @@ final public class Footprint : Sendable {
124128
/// it is best to call this API as soon as possible at startup.
125129
public static let shared = Footprint()
126130

127-
/// Notification name sent when the Footprint.Memory.state changes.
131+
/// Notification name sent when the Footprint.Memory.state and/or
132+
/// Footprint.Memory.pressure changes.
128133
///
129-
/// The notification userInfo dict will contain they `.oldMemoryStateKey`
130-
/// and `.newMemoryStateKey` keys.
131-
public static let stateDidChangeNotification: NSNotification.Name = NSNotification.Name("FootprintMemoryStateDidChangeNotification")
134+
/// The notification userInfo dict will contain they `.oldMemoryKey`,
135+
/// .newMemoryKey` and `.changesKey` keys.
136+
public static let memoryDidChangeNotification: NSNotification.Name = NSNotification.Name("FootprintMemoryDidChangeNotification")
132137

133138
/// Key for the previous value of the memory state in the the
134139
/// `.stateDidChangeNotification` userInfo object.
135-
public static let oldMemoryStateKey: String = "oldMemoryState"
140+
/// Type is `Footprint.Memory`.
141+
public static let oldMemoryKey: String = "oldMemory"
136142

137143
/// Key for the new value of the memory statein the the `.stateDidChangeNotification`
138-
/// userInfo object.
139-
public static let newMemoryStateKey: String = "newMemoryState"
144+
/// userInfo object. Type is `Footprint.Memory`.
145+
public static let newMemoryKey: String = "newMemory"
146+
147+
/// Key for the changes of the memory in the the `.stateDidChangeNotification`
148+
/// userInfo object. Type is `Set<ChangeType>`
149+
public static let changesKey: String = "changes"
150+
151+
/// Types of changes possible
152+
public enum ChangeType: Comparable {
153+
case state
154+
case pressure
155+
}
140156

141157
/// Returns a copy of the current memory structure.
142158
public var memory: Memory {
@@ -162,6 +178,13 @@ final public class Footprint : Sendable {
162178
return _memory.state
163179
}
164180

181+
/// The currently tracked memory pressure.
182+
public var pressure: Memory.State {
183+
_memoryLock.lock()
184+
defer { _memoryLock.unlock() }
185+
return _memory.pressure
186+
}
187+
165188
private init() {
166189
_memory = Memory()
167190

@@ -192,7 +215,7 @@ final public class Footprint : Sendable {
192215
}
193216

194217
private func heartbeat() {
195-
let memory = Memory()
218+
let memory = Memory(memoryPressure: currentPressureFromSource())
196219
storeAndSendObservers(for: memory)
197220
#if targetEnvironment(simulator)
198221
// In the simulator there are no memory terminations,
@@ -204,13 +227,34 @@ final public class Footprint : Sendable {
204227
#endif
205228
}
206229

207-
private func update(with memory: Memory) -> Memory? {
230+
private func currentPressureFromSource() -> Memory.State {
231+
guard let source = _memoryPressureSource else {
232+
return .normal
233+
}
234+
if source.data.contains(.critical) {
235+
return .critical
236+
}
237+
if source.data.contains(.warning) {
238+
return .warning
239+
}
240+
return .normal
241+
}
242+
243+
private func update(with memory: Memory) -> (Memory, Set<ChangeType>)? {
208244

209245
_memoryLock.lock()
210246
defer { _memoryLock.unlock() }
211247

212248
// Verify that state changed...
213-
guard _memory.state != memory.state else {
249+
var changeSet: Set<ChangeType> = []
250+
251+
if _memory.state != memory.state {
252+
changeSet.insert(.state)
253+
}
254+
if _memory.pressure != memory.pressure {
255+
changeSet.insert(.pressure)
256+
}
257+
guard !changeSet.isEmpty else {
214258
return nil
215259
}
216260

@@ -221,27 +265,30 @@ final public class Footprint : Sendable {
221265
return nil
222266
}
223267

224-
print("Footprint.state changed after \(memory.timestamp - _memory.timestamp)")
268+
print("Footprint changed after \(memory.timestamp - _memory.timestamp)")
225269
let oldMemory = _memory
226270
_memory = memory
227271

228-
return oldMemory
272+
return (oldMemory, changeSet)
229273
}
230274

231275
private func storeAndSendObservers(for memory: Memory) {
232276

233-
guard let oldMemory = update(with: memory) else {
277+
guard let (oldMemory, changeSet) = update(with: memory) else {
234278
return
235279
}
236280

237281
// send all observers outside of the lock on the main queue.
238282
// main queue is important since most of us will want to
239283
// make changes that might touch the UI.
284+
print("Footprint changes \(changeSet)")
240285
print("Footprint.state \(memory.state)")
241-
DispatchQueue.main.sync {
242-
NotificationCenter.default.post(name: Footprint.stateDidChangeNotification, object: nil, userInfo: [
243-
Footprint.newMemoryStateKey: memory.state,
244-
Footprint.oldMemoryStateKey: oldMemory.state
286+
print("Footprint.pressure \(memory.pressure)")
287+
DispatchQueue.main.async {
288+
NotificationCenter.default.post(name: Footprint.memoryDidChangeNotification, object: nil, userInfo: [
289+
Footprint.newMemoryKey: memory,
290+
Footprint.oldMemoryKey: oldMemory,
291+
Footprint.changesKey: changeSet
245292
])
246293
}
247294
}
@@ -250,7 +297,7 @@ final public class Footprint : Sendable {
250297
private var _timerSource: DispatchSourceTimer? = nil
251298
private let _heartbeatInterval = 500 // milliseconds
252299
private var _memoryLock: NSLock = NSLock()
253-
private var _memory: Memory
300+
private var _memory: Memory = Memory()
254301
private var _memoryPressureSource: DispatchSourceMemoryPressure? = nil
255302
}
256303

@@ -261,28 +308,55 @@ import SwiftUI
261308
extension View {
262309

263310
/// A SwiftUI extension providing a convenient way to observe changes in the memory
264-
/// state of the app through the `onFootprintMemoryStateDidChange` modifier.
311+
/// state of the app through the `onFootprintMemoryDidChange` modifier.
265312
///
266313
/// ## Overview
267314
///
268-
/// The `onFootprintMemoryStateDidChange` extension allows you to respond
269-
/// to changes in the app's memory state by providing a closure that is executed
270-
/// whenever the memory state transitions.
315+
/// The `onFootprintMemoryDidChange` extension allows you to respond
316+
/// to changes in the app's memory state and pressure by providing a closure that is executed
317+
/// whenever the memory state transitions. You can also use specific modifiers for
318+
/// state (`onFootprintMemoryStateDidChange`) or
319+
/// pressure (`onFootprintMemoryPressureDidChange`).
271320
///
272321
/// ### Example Usage
273322
///
274323
/// ```swift
275324
/// Text("Hello, World!")
276-
/// .onFootprintMemoryStateDidChange { newState, oldState in
325+
/// .onFootprintMemoryDidChange { newMemory, oldMemory, changeSet in
277326
/// print("Memory state changed from \(oldState) to \(newState)")
278-
/// // Perform actions based on the memory state change
327+
/// // Perform actions based on the memory change
279328
/// }
329+
@inlinable public func onFootprintMemoryDidChange(perform action: @escaping (_ state: Footprint.Memory, _ previousState: Footprint.Memory, _ changes: Set<Footprint.ChangeType>) -> Void) -> some View {
330+
_ = Footprint.shared // make sure it's running
331+
return onReceive(NotificationCenter.default.publisher(for: Footprint.memoryDidChangeNotification)) { note in
332+
if let changes = note.userInfo?[Footprint.changesKey] as? Set<Footprint.ChangeType>,
333+
let memory = note.userInfo?[Footprint.newMemoryKey] as? Footprint.Memory,
334+
let prevMemory = note.userInfo?[Footprint.oldMemoryKey] as? Footprint.Memory {
335+
action(memory, prevMemory, changes)
336+
}
337+
}
338+
}
339+
280340
@inlinable public func onFootprintMemoryStateDidChange(perform action: @escaping (_ state: Footprint.Memory.State, _ previousState: Footprint.Memory.State) -> Void) -> some View {
281341
_ = Footprint.shared // make sure it's running
282-
return onReceive(NotificationCenter.default.publisher(for: Footprint.stateDidChangeNotification)) { note in
283-
if let state = note.userInfo?[Footprint.newMemoryStateKey] as? Footprint.Memory.State,
284-
let prevState = note.userInfo?[Footprint.oldMemoryStateKey] as? Footprint.Memory.State {
285-
action(state, prevState)
342+
return onReceive(NotificationCenter.default.publisher(for: Footprint.memoryDidChangeNotification)) { note in
343+
if let changes = note.userInfo?[Footprint.changesKey] as? Set<Footprint.ChangeType>,
344+
changes.contains(.state),
345+
let memory = note.userInfo?[Footprint.newMemoryKey] as? Footprint.Memory,
346+
let prevMemory = note.userInfo?[Footprint.oldMemoryKey] as? Footprint.Memory {
347+
action(memory.state, prevMemory.state)
348+
}
349+
}
350+
}
351+
352+
@inlinable public func onFootprintMemoryPressureDidChange(perform action: @escaping (_ pressure: Footprint.Memory.State, _ previousPressure: Footprint.Memory.State) -> Void) -> some View {
353+
_ = Footprint.shared // make sure it's running
354+
return onReceive(NotificationCenter.default.publisher(for: Footprint.memoryDidChangeNotification)) { note in
355+
if let changes = note.userInfo?[Footprint.changesKey] as? Set<Footprint.ChangeType>,
356+
changes.contains(.pressure),
357+
let memory = note.userInfo?[Footprint.newMemoryKey] as? Footprint.Memory,
358+
let prevMemory = note.userInfo?[Footprint.oldMemoryKey] as? Footprint.Memory {
359+
action(memory.pressure, prevMemory.pressure)
286360
}
287361
}
288362
}

0 commit comments

Comments
 (0)