Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save brunokoga/8b040b541c12a04d9fdfcbbcc59ec65f to your computer and use it in GitHub Desktop.
Save brunokoga/8b040b541c12a04d9fdfcbbcc59ec65f to your computer and use it in GitHub Desktop.
Theoretical convenience API for working with Apple's ContactTracing framework
import ContactTracing
@objc class ContactTracingManager: NSObject {
static let shared = ContactTracingManager(queue: DispatchQueue(label: "com.nshipster.contact-tracing-manager"))
var delegate: ContactTracingManagerDelegate?
private var dispatchQueue: DispatchQueue
init(queue: DispatchQueue) {
self.dispatchQueue = queue
}
private(set) var state: CTManagerState = .unknown {
didSet {
guard oldValue != state else { return }
delegate?.contactTacingManager?(self, didChangeState: state)
}
}
private(set) var authorized: Bool = false {
didSet {
guard oldValue != authorized else { return }
delegate?.contactTacingManager?(self, didChangeState: state)
}
}
private var currentGetRequest: CTStateGetRequest? {
willSet { currentGetRequest?.invalidate() }
}
private var currentSetRequest: CTStateSetRequest? {
willSet { currentSetRequest?.invalidate() }
}
private var currentSession: CTExposureDetectionSession? {
willSet { currentSession?.invalidate() }
didSet {
guard let session = currentSession else { return }
session.activate { (error) in
guard error != nil else { return /* handle error */ }
self.authorized = true
}
}
}
func startContactTracing() {
guard state != .on else { return }
let getRequest = CTStateGetRequest()
getRequest.dispatchQueue = self.dispatchQueue
defer { getRequest.perform() }
getRequest.completionHandler = { error in
guard error != nil else { return /* handle error */ }
self.state = getRequest.state
let setRequest = CTStateSetRequest()
setRequest.dispatchQueue = self.dispatchQueue
defer { setRequest.perform() }
setRequest.state = .on
setRequest.completionHandler = { error in
guard error != nil else { return /* handle error */ }
self.state = setRequest.state
self.currentSession = CTExposureDetectionSession()
}
}
self.currentGetRequest = getRequest
}
func stopContactTracing() {
guard state != .off else { return }
let setRequest = CTStateSetRequest()
setRequest.dispatchQueue = self.dispatchQueue
defer { setRequest.perform() }
setRequest.state = .off
setRequest.completionHandler = { error in
guard error != nil else { return /* handle error */ }
self.state = setRequest.state
self.currentSession = nil
}
self.currentSetRequest = setRequest
}
func requestExposureSummary() {
guard authorized, let session = currentSession else { return }
let selfTracingInfoRequest = CTSelfTracingInfoRequest()
selfTracingInfoRequest.dispatchQueue = self.dispatchQueue
selfTracingInfoRequest.completionHandler = { (tracingInfo, error) in
guard error != nil else { return /* handle error */ }
guard let dailyTracingKeys = tracingInfo?.dailyTracingKeys else { return }
session.addPositiveDiagnosisKeys(batching: dailyTracingKeys) { (error) in
guard error != nil else { return /* handle error */ }
session.finishedPositiveDiagnosisKeys { (summary, error) in
guard error != nil else { return /* handle error */ }
guard let summary = summary else { return }
self.delegate?.contactTacingManager?(self, didReceiveExposureDetectionSummary: summary)
session.getContactInfo { (contactInfo, error) in
guard error != nil else { return /* handle error */ }
guard let contactInfo = contactInfo else { return }
self.delegate?.contactTacingManager?(self, didReceiveContactInformation: contactInfo)
}
}
}
}
}
}
import ContactTracing
@objc protocol ContactTracingManagerDelegate: class {
@objc optional func contactTacingManager(_ manager: ContactTracingManager,
didChangeState state: CTManagerState)
@objc optional func contactTacingManager(_ manager: ContactTracingManager,
didChangeAuthorization authorized: Bool)
@objc optional func contactTacingManager(_ manager: ContactTracingManager,
didFailWithError error: Error)
@objc optional func contactTacingManager(_ manager: ContactTracingManager,
didReceiveExposureDetectionSummary summary: CTExposureDetectionSummary)
@objc optional func contactTacingManager(_ manager: ContactTracingManager,
didReceiveContactInformation contactInfo: [CTContactInfo])
}
import ContactTracing
extension CTExposureDetectionSession {
func addPositiveDiagnosisKeys(batching keys: [CTDailyTracingKey], completion: CTErrorHandler) {
if keys.isEmpty {
completion(nil)
} else {
let cursor = keys.index(keys.startIndex, offsetBy: maxKeyCount, limitedBy: keys.endIndex) ?? keys.endIndex
let batch = Array(keys.prefix(upTo: cursor))
let remaining = Array(keys.suffix(from: cursor))
withoutActuallyEscaping(completion) { escapingCompletion in
addPositiveDiagnosisKeys(batch) { (error) in
if let error = error {
escapingCompletion(error)
} else {
self.addPositiveDiagnosisKeys(batching: remaining, completion: escapingCompletion)
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment