Skip to content

Instantly share code, notes, and snippets.

@mattt
Last active April 27, 2020 23:51
Show Gist options
  • Select an option

  • Save mattt/17c880d64c362b923e13c765f5b1c75a to your computer and use it in GitHub Desktop.

Select an option

Save mattt/17c880d64c362b923e13c765f5b1c75a to your computer and use it in GitHub Desktop.

Revisions

  1. mattt revised this gist Apr 17, 2020. 2 changed files with 9 additions and 3 deletions.
    9 changes: 6 additions & 3 deletions ContactTracingManager.swift
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,6 @@
    import ContactTracing


    @objc class ContactTracingManager: NSObject {
    static let shared = ContactTracingManager(queue: DispatchQueue(label: "com.nshipster.contact-tracing-manager"))

    @@ -93,9 +94,10 @@ import ContactTracing
    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 }
    fetchPositiveDiagnosisKeys { result in
    guard case let .success(positiveDiagnosisKeys) = result else {
    /* handle error */
    }

    session.addPositiveDiagnosisKeys(batching: dailyTracingKeys) { (error) in
    guard error != nil else { return /* handle error */ }
    @@ -116,3 +118,4 @@ import ContactTracing
    }
    }
    }

    3 changes: 3 additions & 0 deletions fetchPositiveDiagnosisKeys.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,3 @@
    func fetchPositiveDiagnosisKeys(completion: (Result<[CTDailyTracingKey], Error>) -> Void) {
    /* download from central database */
    }
  2. mattt created this gist Apr 11, 2020.
    23 changes: 23 additions & 0 deletions CTExposureDetectionSession+Extensions.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,23 @@
    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)
    }
    }
    }
    }
    }
    }
    118 changes: 118 additions & 0 deletions ContactTracingManager.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,118 @@
    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)
    }
    }
    }
    }
    }
    }
    14 changes: 14 additions & 0 deletions ContactTracingManagerDelegate.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,14 @@
    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])
    }