diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index ec073f240..b70495a7a 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -155,8 +155,6 @@ 513146B3235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513146B1235A81A400387FDC /* AddWebFeedIntentHandler.swift */; }; 51314704235C41FC00387FDC /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 51314707235C41FC00387FDC /* Intents.intentdefinition */; }; 51314705235C41FC00387FDC /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 51314707235C41FC00387FDC /* Intents.intentdefinition */; }; - 513228FB233037630033D4ED /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513228F2233037620033D4ED /* Reachability.swift */; }; - 513228FC233037630033D4ED /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513228F2233037620033D4ED /* Reachability.swift */; }; 51333D1624685D2E00EB5C91 /* AddRedditFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51333D1524685D2E00EB5C91 /* AddRedditFeedWindowController.swift */; }; 51333D1724685D2E00EB5C91 /* AddRedditFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51333D1524685D2E00EB5C91 /* AddRedditFeedWindowController.swift */; }; 51333D3B2468615D00EB5C91 /* AddRedditFeedSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51333D392468615D00EB5C91 /* AddRedditFeedSheet.xib */; }; @@ -584,8 +582,6 @@ 51E4993424A867E700B667CB /* UserInfoKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9805237DCAC90028BCAA /* UserInfoKey.swift */; }; 51E4993524A867E800B667CB /* AppNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */; }; 51E4993624A867E800B667CB /* UserInfoKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9805237DCAC90028BCAA /* UserInfoKey.swift */; }; - 51E4993724A8680E00B667CB /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513228F2233037620033D4ED /* Reachability.swift */; }; - 51E4993824A8680E00B667CB /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513228F2233037620033D4ED /* Reachability.swift */; }; 51E4993A24A8708800B667CB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4993924A8708800B667CB /* AppDelegate.swift */; }; 51E4993C24A8709900B667CB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4993B24A8709900B667CB /* AppDelegate.swift */; }; 51E4993D24A870F800B667CB /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; }; @@ -725,7 +721,6 @@ 65ED3FC4235DEF6C0081F399 /* OPMLExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8444C8F11FED81840051386C /* OPMLExporter.swift */; }; 65ED3FC5235DEF6C0081F399 /* MainWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A975D1ED9EB72007D329B /* MainWindowController.swift */; }; 65ED3FC6235DEF6C0081F399 /* UnreadFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5391FC2308B00998D64 /* UnreadFeed.swift */; }; - 65ED3FC7235DEF6C0081F399 /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513228F2233037620033D4ED /* Reachability.swift */; }; 65ED3FC8235DEF6C0081F399 /* SidebarCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29211FC9251E007B49E3 /* SidebarCellLayout.swift */; }; 65ED3FC9235DEF6C0081F399 /* SmartFeedPasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EB92031649C00BC20B7 /* SmartFeedPasteboardWriter.swift */; }; 65ED3FCA235DEF6C0081F399 /* SmartFeedsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC88171FE59CBF00644329 /* SmartFeedsController.swift */; }; @@ -1526,7 +1521,6 @@ 513146B1235A81A400387FDC /* AddWebFeedIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedIntentHandler.swift; sourceTree = ""; }; 51314706235C41FC00387FDC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = ""; }; 51314714235C420900387FDC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Intents.strings; sourceTree = ""; }; - 513228F2233037620033D4ED /* Reachability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reachability.swift; sourceTree = ""; }; 51333D1524685D2E00EB5C91 /* AddRedditFeedWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRedditFeedWindowController.swift; sourceTree = ""; }; 51333D3A2468615D00EB5C91 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/AddRedditFeedSheet.xib; sourceTree = SOURCE_ROOT; }; 51392D1A24AC19A000BE0D35 /* SidebarExpandedContainers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarExpandedContainers.swift; sourceTree = ""; }; @@ -2387,14 +2381,6 @@ path = IntentsExtension; sourceTree = ""; }; - 513228F1233037620033D4ED /* Network */ = { - isa = PBXGroup; - children = ( - 513228F2233037620033D4ED /* Reachability.swift */, - ); - path = Network; - sourceTree = ""; - }; 513C5CE7232571C2003D4054 /* ShareExtension */ = { isa = PBXGroup; children = ( @@ -3309,7 +3295,6 @@ 512E08DD22687FA000BDCFDD /* Tree */, 849A97561ED9EB0D007D329B /* Extensions */, 510C43F5243D0325009F70C3 /* ExtensionPoints */, - 513228F1233037620033D4ED /* Network */, 511D43CE231FA51100FB1562 /* Resources */, ); path = Shared; @@ -4554,7 +4539,6 @@ 5177476724B3BE3400EB0F74 /* SettingsAboutModel.swift in Sources */, 65C2E40124B05D8A000AFDF6 /* FeedsSettingsModel.swift in Sources */, 51E4990A24A808C500B667CB /* FeaturedImageDownloader.swift in Sources */, - 51E4993824A8680E00B667CB /* Reachability.swift in Sources */, 51E4993224A8676400B667CB /* FetchRequestQueue.swift in Sources */, 51E4991724A8090400B667CB /* ArticleUtilities.swift in Sources */, 51E4991B24A8091000B667CB /* IconImage.swift in Sources */, @@ -4672,7 +4656,6 @@ 1727B39924C1368D00A4DBDC /* LayoutPreferencesView.swift in Sources */, 51A8FFEE24CA0CF400F41F1D /* WIthLatestFrom.swift in Sources */, 51E4993F24A8713B00B667CB /* ArticleStatusSyncTimer.swift in Sources */, - 51E4993724A8680E00B667CB /* Reachability.swift in Sources */, 51B80F4424BE58BF00C6C32D /* SharingServiceDelegate.swift in Sources */, 51B54AB624B5B33C0014348B /* WebViewController.swift in Sources */, 51E4994B24A8734C00B667CB /* SendToMicroBlogCommand.swift in Sources */, @@ -4833,7 +4816,6 @@ 65ED3FC4235DEF6C0081F399 /* OPMLExporter.swift in Sources */, 65ED3FC5235DEF6C0081F399 /* MainWindowController.swift in Sources */, 65ED3FC6235DEF6C0081F399 /* UnreadFeed.swift in Sources */, - 65ED3FC7235DEF6C0081F399 /* Reachability.swift in Sources */, 65ED3FC8235DEF6C0081F399 /* SidebarCellLayout.swift in Sources */, 65ED3FC9235DEF6C0081F399 /* SmartFeedPasteboardWriter.swift in Sources */, 515A5149243E64BA0089E588 /* ExtensionPointEnableWindowController.swift in Sources */, @@ -5156,7 +5138,6 @@ 515A517C243E90260089E588 /* ExtensionPointManager.swift in Sources */, 51627A6723861DA3007B3B4B /* MasterFeedViewController+Drag.swift in Sources */, 51FFF0C4235EE8E5002762AA /* VibrantButton.swift in Sources */, - 513228FC233037630033D4ED /* Reachability.swift in Sources */, 51C45259226508D300C03939 /* AppDefaults.swift in Sources */, 51CE1C0B23622007005548FC /* RefreshProgressView.swift in Sources */, 511D4419231FC02D00FB1562 /* KeyboardManager.swift in Sources */, @@ -5190,7 +5171,6 @@ 8444C8F21FED81840051386C /* OPMLExporter.swift in Sources */, 849A975E1ED9EB72007D329B /* MainWindowController.swift in Sources */, 84F2D53A1FC2308B00998D64 /* UnreadFeed.swift in Sources */, - 513228FB233037630033D4ED /* Reachability.swift in Sources */, 845A29221FC9251E007B49E3 /* SidebarCellLayout.swift in Sources */, 510C418224E5D1AE008226FD /* ExtensionFeedAddRequest.swift in Sources */, 516AE9DF2372269A007DEEAA /* IconImage.swift in Sources */, diff --git a/NetNewsWire.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/NetNewsWire.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 85f11c5d3..3f21dbd8a 100644 --- a/NetNewsWire.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/NetNewsWire.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -87,8 +87,8 @@ "repositoryURL": "https://github.com/Ranchero-Software/RSWeb.git", "state": { "branch": null, - "revision": "043eb0098aa70ba45277cc459cbd450d13b72ff2", - "version": "1.0.0-beta5" + "revision": "c0bf7a93255edc0e4b53c11206209739d2af2935", + "version": "1.0.0-beta6" } }, { diff --git a/Shared/Extensions/CacheCleaner.swift b/Shared/Extensions/CacheCleaner.swift index 0bb4ce191..fa00603e7 100644 --- a/Shared/Extensions/CacheCleaner.swift +++ b/Shared/Extensions/CacheCleaner.swift @@ -8,6 +8,7 @@ import Foundation import os.log +import RSWeb struct CacheCleaner { diff --git a/Shared/Network/Reachability.swift b/Shared/Network/Reachability.swift deleted file mode 100644 index 6c8ffa2e4..000000000 --- a/Shared/Network/Reachability.swift +++ /dev/null @@ -1,406 +0,0 @@ -/* -Copyright (c) 2014, Ashley Mills -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation -and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. -*/ - -import SystemConfiguration -import Foundation - -public enum ReachabilityError: Error { - case failedToCreateWithAddress(sockaddr, Int32) - case failedToCreateWithHostname(String, Int32) - case unableToSetCallback(Int32) - case unableToSetDispatchQueue(Int32) - case unableToGetFlags(Int32) -} - -@available(*, unavailable, renamed: "Notification.Name.reachabilityChanged") -public let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification") - -public extension Notification.Name { - static let reachabilityChanged = Notification.Name("reachabilityChanged") -} - -public class Reachability { - - public typealias NetworkReachable = (Reachability) -> () - public typealias NetworkUnreachable = (Reachability) -> () - - @available(*, unavailable, renamed: "Connection") - public enum NetworkStatus: CustomStringConvertible { - case notReachable, reachableViaWiFi, reachableViaWWAN - public var description: String { - switch self { - case .reachableViaWWAN: return "Cellular" - case .reachableViaWiFi: return "WiFi" - case .notReachable: return "No Connection" - } - } - } - - public enum Connection: CustomStringConvertible { - @available(*, deprecated, renamed: "unavailable") - case none - case unavailable, wifi, cellular - public var description: String { - switch self { - case .cellular: return "Cellular" - case .wifi: return "WiFi" - case .unavailable: return "No Connection" - case .none: return "unavailable" - } - } - } - - public var whenReachable: NetworkReachable? - public var whenUnreachable: NetworkUnreachable? - - @available(*, deprecated, renamed: "allowsCellularConnection") - public let reachableOnWWAN: Bool = true - - /// Set to `false` to force Reachability.connection to .none when on cellular connection (default value `true`) - public var allowsCellularConnection: Bool - - // The notification center on which "reachability changed" events are being posted - public var notificationCenter: NotificationCenter = NotificationCenter.default - - @available(*, deprecated, renamed: "connection.description") - public var currentReachabilityString: String { - return "\(connection)" - } - - @available(*, unavailable, renamed: "connection") - public var currentReachabilityStatus: Connection { - return connection - } - - public var connection: Connection { - if flags == nil { - try? setReachabilityFlags() - } - - switch flags?.connection { - case .unavailable?, nil: return .unavailable - case .none?: return .unavailable - case .cellular?: return allowsCellularConnection ? .cellular : .unavailable - case .wifi?: return .wifi - } - } - - fileprivate var isRunningOnDevice: Bool = { - #if targetEnvironment(simulator) - return false - #else - return true - #endif - }() - - fileprivate(set) var notifierRunning = false - fileprivate let reachabilityRef: SCNetworkReachability - fileprivate let reachabilitySerialQueue: DispatchQueue - fileprivate let notificationQueue: DispatchQueue? - fileprivate(set) var flags: SCNetworkReachabilityFlags? { - didSet { - guard flags != oldValue else { return } - notifyReachabilityChanged() - } - } - - required public init(reachabilityRef: SCNetworkReachability, - queueQoS: DispatchQoS = .default, - targetQueue: DispatchQueue? = nil, - notificationQueue: DispatchQueue? = .main) { - self.allowsCellularConnection = true - self.reachabilityRef = reachabilityRef - self.reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability", qos: queueQoS, target: targetQueue) - self.notificationQueue = notificationQueue - } - - public convenience init(hostname: String, - queueQoS: DispatchQoS = .default, - targetQueue: DispatchQueue? = nil, - notificationQueue: DispatchQueue? = .main) throws { - guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { - throw ReachabilityError.failedToCreateWithHostname(hostname, SCError()) - } - self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue, notificationQueue: notificationQueue) - } - - public convenience init(queueQoS: DispatchQoS = .default, - targetQueue: DispatchQueue? = nil, - notificationQueue: DispatchQueue? = .main) throws { - var zeroAddress = sockaddr() - zeroAddress.sa_len = UInt8(MemoryLayout.size) - zeroAddress.sa_family = sa_family_t(AF_INET) - - guard let ref = SCNetworkReachabilityCreateWithAddress(nil, &zeroAddress) else { - throw ReachabilityError.failedToCreateWithAddress(zeroAddress, SCError()) - } - - self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue, notificationQueue: notificationQueue) - } - - deinit { - stopNotifier() - } -} - -public extension Reachability { - - // MARK: - *** Notifier methods *** - func startNotifier() throws { - guard !notifierRunning else { return } - - let callback: SCNetworkReachabilityCallBack = { (reachability, flags, info) in - guard let info = info else { return } - - // `weakifiedReachability` is guaranteed to exist by virtue of our - // retain/release callbacks which we provided to the `SCNetworkReachabilityContext`. - let weakifiedReachability = Unmanaged.fromOpaque(info).takeUnretainedValue() - - // The weak `reachability` _may_ no longer exist if the `Reachability` - // object has since been deallocated but a callback was already in flight. - weakifiedReachability.reachability?.flags = flags - } - - let weakifiedReachability = ReachabilityWeakifier(reachability: self) - let opaqueWeakifiedReachability = Unmanaged.passUnretained(weakifiedReachability).toOpaque() - - var context = SCNetworkReachabilityContext( - version: 0, - info: UnsafeMutableRawPointer(opaqueWeakifiedReachability), - retain: { (info: UnsafeRawPointer) -> UnsafeRawPointer in - let unmanagedWeakifiedReachability = Unmanaged.fromOpaque(info) - _ = unmanagedWeakifiedReachability.retain() - return UnsafeRawPointer(unmanagedWeakifiedReachability.toOpaque()) - }, - release: { (info: UnsafeRawPointer) -> Void in - let unmanagedWeakifiedReachability = Unmanaged.fromOpaque(info) - unmanagedWeakifiedReachability.release() - }, - copyDescription: { (info: UnsafeRawPointer) -> Unmanaged in - let unmanagedWeakifiedReachability = Unmanaged.fromOpaque(info) - let weakifiedReachability = unmanagedWeakifiedReachability.takeUnretainedValue() - let description = weakifiedReachability.reachability?.description ?? "nil" - return Unmanaged.passRetained(description as CFString) - } - ) - - if !SCNetworkReachabilitySetCallback(reachabilityRef, callback, &context) { - stopNotifier() - throw ReachabilityError.unableToSetCallback(SCError()) - } - - if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef, reachabilitySerialQueue) { - stopNotifier() - throw ReachabilityError.unableToSetDispatchQueue(SCError()) - } - - // Perform an initial check - try setReachabilityFlags() - - notifierRunning = true - } - - func stopNotifier() { - defer { notifierRunning = false } - - SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil) - SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil) - } - - // MARK: - *** Connection test methods *** - @available(*, deprecated, message: "Please use `connection != .none`") - var isReachable: Bool { - return connection != .unavailable - } - - @available(*, deprecated, message: "Please use `connection == .cellular`") - var isReachableViaWWAN: Bool { - // Check we're not on the simulator, we're REACHABLE and check we're on WWAN - return connection == .cellular - } - - @available(*, deprecated, message: "Please use `connection == .wifi`") - var isReachableViaWiFi: Bool { - return connection == .wifi - } - - var description: String { - return flags?.description ?? "unavailable flags" - } -} - -fileprivate extension Reachability { - - func setReachabilityFlags() throws { - try reachabilitySerialQueue.sync { [unowned self] in - var flags = SCNetworkReachabilityFlags() - if !SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags) { - self.stopNotifier() - throw ReachabilityError.unableToGetFlags(SCError()) - } - - self.flags = flags - } - } - - - func notifyReachabilityChanged() { - let notify = { [weak self] in - guard let self = self else { return } - self.connection != .unavailable ? self.whenReachable?(self) : self.whenUnreachable?(self) - self.notificationCenter.post(name: .reachabilityChanged, object: self) - } - - // notify on the configured `notificationQueue`, or the caller's (i.e. `reachabilitySerialQueue`) - notificationQueue?.async(execute: notify) ?? notify() - } -} - -extension SCNetworkReachabilityFlags { - - typealias Connection = Reachability.Connection - - var connection: Connection { - guard isReachableFlagSet else { return .unavailable } - - // If we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi - #if targetEnvironment(simulator) - return .wifi - #else - var connection = Connection.unavailable - - if !isConnectionRequiredFlagSet { - connection = .wifi - } - - if isConnectionOnTrafficOrDemandFlagSet { - if !isInterventionRequiredFlagSet { - connection = .wifi - } - } - - if isOnWWANFlagSet { - connection = .cellular - } - - return connection - #endif - } - - var isOnWWANFlagSet: Bool { - #if os(iOS) - return contains(.isWWAN) - #else - return false - #endif - } - var isReachableFlagSet: Bool { - return contains(.reachable) - } - var isConnectionRequiredFlagSet: Bool { - return contains(.connectionRequired) - } - var isInterventionRequiredFlagSet: Bool { - return contains(.interventionRequired) - } - var isConnectionOnTrafficFlagSet: Bool { - return contains(.connectionOnTraffic) - } - var isConnectionOnDemandFlagSet: Bool { - return contains(.connectionOnDemand) - } - var isConnectionOnTrafficOrDemandFlagSet: Bool { - return !intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty - } - var isTransientConnectionFlagSet: Bool { - return contains(.transientConnection) - } - var isLocalAddressFlagSet: Bool { - return contains(.isLocalAddress) - } - var isDirectFlagSet: Bool { - return contains(.isDirect) - } - var isConnectionRequiredAndTransientFlagSet: Bool { - return intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection] - } - - var description: String { - let W = isOnWWANFlagSet ? "W" : "-" - let R = isReachableFlagSet ? "R" : "-" - let c = isConnectionRequiredFlagSet ? "c" : "-" - let t = isTransientConnectionFlagSet ? "t" : "-" - let i = isInterventionRequiredFlagSet ? "i" : "-" - let C = isConnectionOnTrafficFlagSet ? "C" : "-" - let D = isConnectionOnDemandFlagSet ? "D" : "-" - let l = isLocalAddressFlagSet ? "l" : "-" - let d = isDirectFlagSet ? "d" : "-" - - return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)" - } -} - -/** - `ReachabilityWeakifier` weakly wraps the `Reachability` class - in order to break retain cycles when interacting with CoreFoundation. - - CoreFoundation callbacks expect a pair of retain/release whenever an - opaque `info` parameter is provided. These callbacks exist to guard - against memory management race conditions when invoking the callbacks. - - #### Race Condition - - If we passed `SCNetworkReachabilitySetCallback` a direct reference to our - `Reachability` class without also providing corresponding retain/release - callbacks, then a race condition can lead to crashes when: - - `Reachability` is deallocated on thread X - - A `SCNetworkReachability` callback(s) is already in flight on thread Y - - #### Retain Cycle - - If we pass `Reachability` to CoreFoundtion while also providing retain/ - release callbacks, we would create a retain cycle once CoreFoundation - retains our `Reachability` class. This fixes the crashes and his how - CoreFoundation expects the API to be used, but doesn't play nicely with - Swift/ARC. This cycle would only be broken after manually calling - `stopNotifier()` — `deinit` would never be called. - - #### ReachabilityWeakifier - - By providing both retain/release callbacks and wrapping `Reachability` in - a weak wrapper, we: - - interact correctly with CoreFoundation, thereby avoiding a crash. - See "Memory Management Programming Guide for Core Foundation". - - don't alter the public API of `Reachability.swift` in any way - - still allow for automatic stopping of the notifier on `deinit`. - */ -private class ReachabilityWeakifier { - weak var reachability: Reachability? - init(reachability: Reachability) { - self.reachability = reachability - } -}