-
-
Save zwaldowski/49f61292757f86d7d036a529f2d04f0c to your computer and use it in GitHub Desktop.
| // | |
| // Activity.swift | |
| // | |
| // Created by Zachary Waldowski on 8/21/16. | |
| // Copyright © 2016 Zachary Waldowski. Licensed under MIT. | |
| // | |
| import os.activity | |
| private final class LegacyActivityContext { | |
| let dsoHandle: UnsafeRawPointer? | |
| let description: UnsafePointer<CChar> | |
| let flags: os_activity_flag_t | |
| init(dsoHandle: UnsafeRawPointer?, description: UnsafePointer<CChar>, flags: os_activity_flag_t) { | |
| self.dsoHandle = dsoHandle | |
| self.description = description | |
| self.flags = flags | |
| } | |
| } | |
| @available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) | |
| @_silgen_name("_os_activity_create") private func _os_activity_create(_ dso: UnsafeRawPointer?, _ description: UnsafePointer<Int8>, _ parent : Unmanaged<AnyObject>?, _ flags: os_activity_flag_t) -> AnyObject! | |
| @available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) | |
| @_silgen_name("os_activity_apply") private func _os_activity_apply(_ storage: AnyObject, _ block: @convention(block) () -> ()) | |
| @_silgen_name("_os_activity_initiate") private func __os_activity_initiate(_ dso: UnsafeRawPointer?, _ description: UnsafePointer<Int8>, _ flags: os_activity_flag_t, _ block: @convention(block) () -> ()) | |
| @available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) | |
| @_silgen_name("os_activity_scope_enter") private func _os_activity_scope_enter(_ storage: AnyObject, _ state: UnsafeMutablePointer<os_activity_scope_state_s>) | |
| @_silgen_name("_os_activity_start") private func __os_activity_start(_ dso: UnsafeRawPointer?, _ description: UnsafePointer<Int8>, _ flags: os_activity_flag_t) -> UInt64 | |
| @available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) | |
| @_silgen_name("os_activity_scope_leave") private func _os_activity_scope_leave(_ state: UnsafeMutablePointer<os_activity_scope_state_s>) | |
| @_silgen_name("os_activity_end") private func __os_activity_end(_ state: UInt64) | |
| @available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) | |
| private let OS_ACTIVITY_NONE = unsafeBitCast(dlsym(UnsafeMutableRawPointer(bitPattern: -2), "_os_activity_none"), to: Unmanaged<AnyObject>.self) | |
| @available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) | |
| private let OS_ACTIVITY_CURRENT = unsafeBitCast(dlsym(UnsafeMutableRawPointer(bitPattern: -2), "_os_activity_current"), to: Unmanaged<AnyObject>.self) | |
| public struct Activity { | |
| /// Support flags for OSActivity. | |
| public struct Options: OptionSet { | |
| public let rawValue: UInt32 | |
| public init(rawValue: UInt32) { | |
| self.rawValue = rawValue | |
| } | |
| /// Detach a newly created activity from a parent activity, if any. | |
| /// | |
| /// If passed in conjunction with a parent activity, the activity will | |
| /// only note what activity "created" the new one, but will make the | |
| /// new activity a top level activity. This allows seeing what | |
| /// activity triggered work without actually relating the activities. | |
| public static let detached = Options(rawValue: OS_ACTIVITY_FLAG_DETACHED.rawValue) | |
| /// Will only create a new activity if none present. | |
| /// | |
| /// If an activity ID is already present, a new activity will be | |
| /// returned with the same underlying activity ID. | |
| @available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) | |
| public static let ifNonePresent = Options(rawValue: OS_ACTIVITY_FLAG_IF_NONE_PRESENT.rawValue) | |
| } | |
| private let opaque: AnyObject | |
| /// Creates an activity. | |
| public init(_ description: StaticString, dso: UnsafeRawPointer? = #dsohandle, options: Options = []) { | |
| self.opaque = description.withUTF8Buffer { (buf: UnsafeBufferPointer<UInt8>) -> AnyObject in | |
| let str = unsafeBitCast(buf.baseAddress!, to: UnsafePointer<Int8>.self) | |
| let flags = os_activity_flag_t(rawValue: options.rawValue) | |
| if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *), OS_ACTIVITY_OBJECT_API != 0 { | |
| return _os_activity_create(dso, str, OS_ACTIVITY_CURRENT, flags) | |
| } else { | |
| return LegacyActivityContext(dsoHandle: dso, description: str, flags: flags) | |
| } | |
| } | |
| } | |
| private func active(execute body: @convention(block) () -> ()) { | |
| if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *), OS_ACTIVITY_OBJECT_API != 0 { | |
| _os_activity_apply(opaque, body) | |
| } else { | |
| let context = opaque as! LegacyActivityContext | |
| __os_activity_initiate(context.dsoHandle, context.description, context.flags, body) | |
| } | |
| } | |
| /// Executes a function body within the context of the activity. | |
| public func active<Return>(execute body: () throws -> Return) rethrows -> Return { | |
| func impl(execute work: () throws -> Return, recover: (Error) throws -> Return) rethrows -> Return { | |
| var result: Return? | |
| var error: Error? | |
| active { | |
| do { | |
| result = try work() | |
| } catch let e { | |
| error = e | |
| } | |
| } | |
| if let e = error { | |
| return try recover(e) | |
| } else { | |
| return result! | |
| } | |
| } | |
| return try impl(execute: body, recover: { throw $0 }) | |
| } | |
| /// Opaque structure created by `Activity.enter()` and restored using | |
| /// `leave()`. | |
| public struct Scope { | |
| fileprivate var state = os_activity_scope_state_s() | |
| fileprivate init() {} | |
| /// Pops activity state to `self`. | |
| public mutating func leave() { | |
| if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *), OS_ACTIVITY_OBJECT_API != 0 { | |
| _os_activity_scope_leave(&state) | |
| } else { | |
| UnsafeRawPointer(bitPattern: Int(state.opaque.0)).map(Unmanaged<AnyObject>.fromOpaque)?.release() | |
| __os_activity_end(state.opaque.1) | |
| } | |
| } | |
| } | |
| /// Changes the current execution context to the activity. | |
| /// | |
| /// An activity can be created and applied to the current scope by doing: | |
| /// | |
| /// var scope = OSActivity("my new activity").enter() | |
| /// defer { scope.leave() } | |
| /// ... do some work ... | |
| /// | |
| public func enter() -> Scope { | |
| var scope = Scope() | |
| if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *), OS_ACTIVITY_OBJECT_API != 0 { | |
| _os_activity_scope_enter(opaque, &scope.state) | |
| } else { | |
| let context = opaque as! LegacyActivityContext | |
| scope.state.opaque.0 = numericCast(Int(bitPattern: Unmanaged.passRetained(context).toOpaque())) | |
| scope.state.opaque.1 = __os_activity_start(context.dsoHandle, context.description, context.flags) | |
| } | |
| return scope | |
| } | |
| /// Creates an activity. | |
| @available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) | |
| public init(_ description: StaticString, dso: UnsafeRawPointer? = #dsohandle, parent: Activity, options: Options = []) { | |
| self.opaque = description.withUTF8Buffer { (buf: UnsafeBufferPointer<UInt8>) -> AnyObject in | |
| let str = unsafeBitCast(buf.baseAddress!, to: UnsafePointer<Int8>.self) | |
| let flags = os_activity_flag_t(rawValue: options.rawValue) | |
| return _os_activity_create(dso, str, Unmanaged.passRetained(parent.opaque), flags) | |
| } | |
| } | |
| private init(_ opaque: AnyObject) { | |
| self.opaque = opaque | |
| } | |
| /// An activity with no traits; as a parent, it is equivalent to a | |
| /// detached activity. | |
| @available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) | |
| public static var none: Activity { | |
| return Activity(OS_ACTIVITY_NONE.takeUnretainedValue()) | |
| } | |
| /// The running activity. | |
| /// | |
| /// As a parent, the new activity is linked to the current activity, if one | |
| /// is present. If no activity is present, it behaves the same as `.none`. | |
| @available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) | |
| public static var current: Activity { | |
| return Activity(OS_ACTIVITY_CURRENT.takeUnretainedValue()) | |
| } | |
| /// Label an activity auto-generated by UI with a name that is useful for | |
| /// debugging macro-level user actions. | |
| /// | |
| /// This function should be called early within the scope of an `IBAction`, | |
| /// before any sub-activities are created. The name provided will be shown | |
| /// in tools in addition to the system-provided name. This API should only | |
| /// be called once, and only on an activity created by the system. These | |
| /// actions help determine workflow of the user in order to reproduce | |
| /// problems that occur. | |
| /// | |
| /// For example, a control press and/or menu item selection can be labeled: | |
| /// | |
| /// OSActivity.labelUserAction("New mail message") | |
| /// OSActivity.labelUserAction("Empty trash") | |
| /// | |
| /// Where the underlying name will be "gesture:" or "menuSelect:". | |
| @available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) | |
| public static func labelUserAction(_ description: StaticString, dso: UnsafeRawPointer? = #dsohandle) { | |
| description.withUTF8Buffer { (buf: UnsafeBufferPointer<UInt8>) in | |
| let str = unsafeBitCast(buf.baseAddress!, to: UnsafePointer<Int8>.self) | |
| _os_activity_label_useraction(UnsafeMutableRawPointer(mutating: dso), str) | |
| } | |
| } | |
| } |
@kdawgwilk try this:
let str = buf.baseAddress!.withMemoryRebound(to: Int8.self, capacity: 8, { $0 })
Update for Xcode 9.4 / Swift 4.1:
import Foundation
import os.activity
// Bridging Obj-C variabled defined as c-macroses. See `activity.h` header.
private let OS_ACTIVITY_NONE = unsafeBitCast(dlsym(UnsafeMutableRawPointer(bitPattern: -2), "_os_activity_none"), to: OS_os_activity.self)
private let OS_ACTIVITY_CURRENT = unsafeBitCast(dlsym(UnsafeMutableRawPointer(bitPattern: -2), "_os_activity_current"), to: OS_os_activity.self)
public struct Activity {
private let activity: OS_os_activity!
public init(_ description: StaticString, dso: UnsafeRawPointer? = #dsohandle, options: Options = []) {
activity = description.withUTF8Buffer {
if let dso = UnsafeMutableRawPointer(mutating: dso), let address = $0.baseAddress {
let str = UnsafeRawPointer(address).assumingMemoryBound(to: Int8.self)
return _os_activity_create(dso, str, OS_ACTIVITY_CURRENT, os_activity_flag_t(rawValue: options.rawValue))
} else {
return nil
}
}
}
public init(_ description: StaticString, dso: UnsafeRawPointer? = #dsohandle, parent: Activity, options: Options = []) {
let parentActivity: OS_os_activity = parent.activity != nil ? parent.activity : OS_ACTIVITY_CURRENT
activity = description.withUTF8Buffer {
if let dso = UnsafeMutableRawPointer(mutating: dso), let address = $0.baseAddress {
let str = UnsafeRawPointer(address).assumingMemoryBound(to: Int8.self)
return _os_activity_create(dso, str, parentActivity, os_activity_flag_t(rawValue: options.rawValue))
} else {
return nil
}
}
}
private init(_ activity: OS_os_activity) {
self.activity = activity
}
}
extension Activity {
public static var none: Activity {
return Activity(OS_ACTIVITY_NONE)
}
public static var current: Activity {
return Activity(OS_ACTIVITY_CURRENT)
}
public static func initiate(_ description: StaticString, dso: UnsafeRawPointer? = #dsohandle, options: Options = [],
execute body: @convention(block) () -> ()) {
description.withUTF8Buffer {
if let dso = UnsafeMutableRawPointer(mutating: dso), let address = $0.baseAddress {
let str = UnsafeRawPointer(address).assumingMemoryBound(to: Int8.self)
_os_activity_initiate(dso, str, os_activity_flag_t(rawValue: options.rawValue), body)
}
}
}
public func apply(execute body: @convention(block) () -> ()) {
if activity != nil {
os_activity_apply(activity, body)
}
}
public func enter() -> Scope {
var scope = Scope()
if activity != nil {
os_activity_scope_enter(activity, &scope.state)
}
return scope
}
/**
* Label an activity that is auto-generated by AppKit/UIKit with a name that is
* useful for debugging macro-level user actions. The API should be called
* early within the scope of the IBAction and before any sub-activities are
* created.
* This API can only be called once and only on the activity created by AppKit/UIKit.
*/
public static func labelUserAction(_ description: StaticString, dso: UnsafeRawPointer? = #dsohandle) {
description.withUTF8Buffer {
if let dso = UnsafeMutableRawPointer(mutating: dso), let address = $0.baseAddress {
let str = UnsafeRawPointer(address).assumingMemoryBound(to: Int8.self)
_os_activity_label_useraction(dso, str)
}
}
}
}
extension Activity {
public struct Options: OptionSet {
public let rawValue: UInt32
public init(rawValue: UInt32) {
self.rawValue = rawValue
}
public static let `default` = Options(rawValue: OS_ACTIVITY_FLAG_DEFAULT.rawValue)
public static let detached = Options(rawValue: OS_ACTIVITY_FLAG_DETACHED.rawValue)
public static let ifNonePresent = Options(rawValue: OS_ACTIVITY_FLAG_IF_NONE_PRESENT.rawValue)
}
public struct Scope {
fileprivate var state = os_activity_scope_state_s()
public mutating func leave() {
os_activity_scope_leave(&state)
}
}
}
@vgorloff What is OS_os_activity?
@zwaldowski (all), Do you occasionally get blocked by the error 'String' is not convertible to 'StaticString'?
EDIT: This seems to be the Swift compiler behavior, when a Return type is not specified when wrapping a function expecting a return. Makes sense.
Fix:
func applyMainExclusionPath() -> CGRect {
// would be invoked from within Activity
return Activity("apply main exclusion path").active { () -> CGRect inI haven't been able to try this out yet, but it looks interesting.
Compared to the native API, the process of entering and leaving scopes is a bit verbose, and when someone forgets to leave it, I imagine it may cause problems.
To avoid the deferred leave boilerplate, would it be possible to define the scope as a class instead and leave the scope on deinit?
public class Scope {
fileprivate var state = os_activity_scope_state_s()
deinit {
os_activity_scope_leave(&state)
}
}To avoid the deferred leave boilerplate, would it be possible to define the scope as a class instead and leave the scope on deinit?
public class Scope { fileprivate var state = os_activity_scope_state_s() deinit { os_activity_scope_leave(&state) } }
This approach doesn't work on Swift 5.0. Unfortunately, we still have to dance around the defer { scope.leave() }.
Re: class Scope, Swift ARC deinitialization isn't defined to happen at the end of scope like in C++. It's free to be deallocated (and thus end scope) as soon as you last use it.

Whoa wait, the system frameworks actually include os_log APIs for Swift? I thought they didn't have that, like they don't have os_activity. That's interesting.