diff --git a/Sources/NetShears/Colors.swift b/Sources/NetShears/Colors.swift index f64f94b..162f01f 100644 --- a/Sources/NetShears/Colors.swift +++ b/Sources/NetShears/Colors.swift @@ -9,25 +9,25 @@ import UIKit struct Colors { struct UI{ - static public let wordsInEvidence = UIColor(hexString: "#dadfe1") - static public let wordFocus = UIColor(hexString: "#f7ca18") + static let wordsInEvidence = UIColor(hexString: "#dadfe1") + static let wordFocus = UIColor(hexString: "#f7ca18") } struct Gray{ - static public let darkestGray = UIColor(hexString: "#666666") - static public let darkerGray = UIColor(hexString: "#888888") - static public let darkGray = UIColor(hexString: "#999999") - static public let midGray = UIColor(hexString: "#BBBBBB") - static public let lightGray = UIColor(hexString: "#CCCCCC") - static public let lighestGray = UIColor(hexString: "#E7E7E7") + static let darkestGray = UIColor(hexString: "#666666") + static let darkerGray = UIColor(hexString: "#888888") + static let darkGray = UIColor(hexString: "#999999") + static let midGray = UIColor(hexString: "#BBBBBB") + static let lightGray = UIColor(hexString: "#CCCCCC") + static let lighestGray = UIColor(hexString: "#E7E7E7") } struct HTTPCode{ - static public let Success = UIColor(hexString: "#297E4C") //2xx - static public let Redirect = UIColor(hexString: "#3D4140") //3xx - static public let ClientError = UIColor(hexString: "#D97853") //4xx - static public let ServerError = UIColor(hexString: "#D32C58") //5xx - static public let Generic = UIColor(hexString: "#999999") //Others + static let Success = UIColor(hexString: "#297E4C") //2xx + static let Redirect = UIColor(hexString: "#3D4140") //3xx + static let ClientError = UIColor(hexString: "#D97853") //4xx + static let ServerError = UIColor(hexString: "#D32C58") //5xx + static let Generic = UIColor(hexString: "#999999") //Others } } diff --git a/Sources/NetShears/Extension/URLRequest+Extension.swift b/Sources/NetShears/Extension/URLRequest+Extension.swift index 6c48572..7598359 100644 --- a/Sources/NetShears/Extension/URLRequest+Extension.swift +++ b/Sources/NetShears/Extension/URLRequest+Extension.swift @@ -8,7 +8,7 @@ import Foundation extension URLRequest { - public func getHttpBodyStreamData() -> Data? { + func getHttpBodyStreamData() -> Data? { guard let httpBodyStream = self.httpBodyStream else { return nil } diff --git a/Sources/NetShears/Models/RequestModel.swift b/Sources/NetShears/Models/RequestModel.swift index b93fea6..0facbe2 100644 --- a/Sources/NetShears/Models/RequestModel.swift +++ b/Sources/NetShears/Models/RequestModel.swift @@ -7,7 +7,7 @@ import Foundation import UIKit -final class NetShearsRequestModel: Codable { +public final class NetShearsRequestModel: Codable { let id: String let url: String let host: String? diff --git a/Sources/NetShears/NetShears.swift b/Sources/NetShears/NetShears.swift index 9de4e93..ce70b40 100644 --- a/Sources/NetShears/NetShears.swift +++ b/Sources/NetShears/NetShears.swift @@ -11,10 +11,18 @@ public final class NetShears: NSObject { public static let shared = NetShears() let networkRequestInterceptor = NetworkRequestInterceptor() + lazy var config: NetworkInterceptorConfig = { var savedModifiers = [RequestEvaluatorModifier]().retrieveFromDisk() return NetworkInterceptorConfig(modifiers: savedModifiers) }() + + lazy var requestObserver: RequestObserverProtocol = { + RequestObserver(options: [ + RequestStorage.shared, + RequestBroadcast.shared + ]) + }() public func startRecording(){ @@ -56,6 +64,6 @@ public final class NetShears: NSObject { HPACKHeadersRequest: [String: String]?, HPACKHeadersResponse: [String: String]?){ let request = NetShearsRequestModel(url: url, host: host, requestObject: requestObject, responseObject: responseObject, success: success, statusCode: statusCode, duration: duration, HPACKHeadersRequest: HPACKHeadersRequest, HPACKHeadersResponse: HPACKHeadersResponse) - Storage.shared.saveRequest(request: request) + requestObserver.newRequestArrived(request) } } diff --git a/Sources/NetShears/NetworkInterceptor.swift b/Sources/NetShears/NetworkInterceptor.swift index 46560a6..4dcc288 100644 --- a/Sources/NetShears/NetworkInterceptor.swift +++ b/Sources/NetShears/NetworkInterceptor.swift @@ -8,20 +8,20 @@ import Foundation -@objc public class NetworkInterceptor: NSObject { +@objc class NetworkInterceptor: NSObject { - @objc public static let shared = NetworkInterceptor() + @objc static let shared = NetworkInterceptor() let networkRequestInterceptor = NetworkRequestInterceptor() - public func startRecording(){ + func startRecording(){ self.networkRequestInterceptor.startRecording() } - public func stopRecording(){ + func stopRecording(){ self.networkRequestInterceptor.stopRecording() } - public func shouldRequestModify(urlRequest: URLRequest) -> Bool { + func shouldRequestModify(urlRequest: URLRequest) -> Bool { for modifer in NetShears.shared.config.modifiers { if modifer.isActionAllowed(urlRequest: urlRequest) { return true @@ -29,6 +29,5 @@ import Foundation } return false } - } diff --git a/Sources/NetShears/NetworkInterceptorConfig.swift b/Sources/NetShears/NetworkInterceptorConfig.swift index 33d23f5..01f492c 100644 --- a/Sources/NetShears/NetworkInterceptorConfig.swift +++ b/Sources/NetShears/NetworkInterceptorConfig.swift @@ -11,7 +11,7 @@ public struct RedirectedRequestModel: Codable, Equatable { public let originalUrl: String public let redirectUrl: String - public init (originalUrl: String, redirectUrl: String) { + public init(originalUrl: String, redirectUrl: String) { self.originalUrl = originalUrl self.redirectUrl = redirectUrl } @@ -21,13 +21,13 @@ public struct HeaderModifyModel: Codable, Equatable { public let key: String public let value: String - public init (key: String, value: String) { + public init(key: String, value: String) { self.key = key self.value = value } } -public final class NetworkInterceptorConfig { +final class NetworkInterceptorConfig { var modifiers: [RequestEvaluatorModifier] = [] { didSet { modifiers.store() diff --git a/Sources/NetShears/NetworkRequestInterceptor.swift b/Sources/NetShears/NetworkRequestInterceptor.swift index 19a1cc4..3c0ba07 100644 --- a/Sources/NetShears/NetworkRequestInterceptor.swift +++ b/Sources/NetShears/NetworkRequestInterceptor.swift @@ -7,25 +7,25 @@ import Foundation -@objc public class NetworkRequestInterceptor: NSObject{ - +@objc class NetworkRequestInterceptor: NSObject{ + func swizzleProtocolClasses(){ let instance = URLSessionConfiguration.default let uRLSessionConfigurationClass: AnyClass = object_getClass(instance)! - + let method1: Method = class_getInstanceMethod(uRLSessionConfigurationClass, #selector(getter: uRLSessionConfigurationClass.protocolClasses))! let method2: Method = class_getInstanceMethod(URLSessionConfiguration.self, #selector(URLSessionConfiguration.fakeProcotolClasses))! - + method_exchangeImplementations(method1, method2) } - public func startRecording() { + func startRecording() { URLProtocol.registerClass(NetworkInterceptorUrlProtocol.self) URLProtocol.registerClass(NetworkLoggerUrlProtocol.self) swizzleProtocolClasses() } - public func stopRecording() { + func stopRecording() { URLProtocol.unregisterClass(NetworkInterceptorUrlProtocol.self) URLProtocol.unregisterClass(NetworkLoggerUrlProtocol.self) swizzleProtocolClasses() diff --git a/Sources/NetShears/Observable/RequestBroadcast.swift b/Sources/NetShears/Observable/RequestBroadcast.swift new file mode 100644 index 0000000..81b42a4 --- /dev/null +++ b/Sources/NetShears/Observable/RequestBroadcast.swift @@ -0,0 +1,38 @@ +// +// RequestBroadcast.swift +// +// +// Created by Ali Moazenzadeh on 11/17/21. +// + +import Foundation + +public protocol RequestBroadcastDelegate: AnyObject { + func newRequestArrived(_ request: NetShearsRequestModel) +} + +public final class RequestBroadcast: RequestObserverProtocol { + static public let shared = RequestBroadcast() + + var delegate = ThreadSafe(nil) + + private init() {} + + public func setDelegate(_ newDelegate: RequestBroadcastDelegate) { + delegate.atomically { delegate in + delegate = newDelegate + } + } + + public func removeDelegate() { + delegate.atomically { delegate in + delegate = nil + } + } + + func newRequestArrived(_ request: NetShearsRequestModel) { + delegate.atomically { delegate in + delegate?.newRequestArrived(request) + } + } +} diff --git a/Sources/NetShears/Observable/RequestObservable.swift b/Sources/NetShears/Observable/RequestObservable.swift new file mode 100644 index 0000000..0aa15f7 --- /dev/null +++ b/Sources/NetShears/Observable/RequestObservable.swift @@ -0,0 +1,26 @@ +// +// RequestObservable.swift +// +// +// Created by Ali Moazenzadeh on 11/17/21. +// + +import Foundation + +protocol RequestObserverProtocol { + func newRequestArrived(_ request: NetShearsRequestModel) +} + +final class RequestObserver: RequestObserverProtocol { + let options: [RequestObserverProtocol] + + init(options: [RequestObserverProtocol]) { + self.options = options + } + + func newRequestArrived(_ request: NetShearsRequestModel) { + options.forEach { + $0.newRequestArrived(request) + } + } +} diff --git a/Sources/NetShears/Observable/RequestStorage.swift b/Sources/NetShears/Observable/RequestStorage.swift new file mode 100644 index 0000000..015030c --- /dev/null +++ b/Sources/NetShears/Observable/RequestStorage.swift @@ -0,0 +1,18 @@ +// +// RequestStorage.swift +// +// +// Created by Ali Moazenzadeh on 11/17/21. +// + +import Foundation + +final class RequestStorage: RequestObserverProtocol { + static let shared = RequestStorage() + + private init() {} + + func newRequestArrived(_ request: NetShearsRequestModel) { + Storage.shared.saveRequest(request: request) + } +} diff --git a/Sources/NetShears/Protocols/PersistHelper.swift b/Sources/NetShears/Protocols/PersistHelper.swift index 1485d24..57182d6 100644 --- a/Sources/NetShears/Protocols/PersistHelper.swift +++ b/Sources/NetShears/Protocols/PersistHelper.swift @@ -7,7 +7,7 @@ import Foundation -public class PersistHelper { +class PersistHelper { fileprivate init() { } diff --git a/Sources/NetShears/RequestModifier/RequestEvaluatorModifierEndpoint.swift b/Sources/NetShears/RequestModifier/RequestEvaluatorModifierEndpoint.swift index 3cf4db2..f6616c4 100644 --- a/Sources/NetShears/RequestModifier/RequestEvaluatorModifierEndpoint.swift +++ b/Sources/NetShears/RequestModifier/RequestEvaluatorModifierEndpoint.swift @@ -10,7 +10,7 @@ import Foundation public struct RequestEvaluatorModifierEndpoint: RequestEvaluatorModifier, Equatable, Codable { public var redirectedRequest: RedirectedRequestModel - + public static var storeFileName: String { "Modifier.txt" } @@ -20,7 +20,7 @@ public struct RequestEvaluatorModifierEndpoint: RequestEvaluatorModifier, Equata } public func modify(request: inout URLRequest) { - + if isRequestRedirectable(urlRequest: request) { request.modifyURLRequestEndpoint(redirectUrl: redirectedRequest) } @@ -34,7 +34,7 @@ public struct RequestEvaluatorModifierEndpoint: RequestEvaluatorModifier, Equata guard let urlString = urlRequest.url?.absoluteString else { return false } - + if urlString.contains(redirectedRequest.originalUrl) { return true } diff --git a/Sources/NetShears/RequestModifier/RequestEvaluatorModifierHeader.swift b/Sources/NetShears/RequestModifier/RequestEvaluatorModifierHeader.swift index 38575ba..a5f3239 100644 --- a/Sources/NetShears/RequestModifier/RequestEvaluatorModifierHeader.swift +++ b/Sources/NetShears/RequestModifier/RequestEvaluatorModifierHeader.swift @@ -10,7 +10,7 @@ import Foundation public struct RequestEvaluatorModifierHeader: RequestEvaluatorModifier, Equatable, Codable { public var header: HeaderModifyModel - + public static var storeFileName: String { "Header.txt" } diff --git a/Sources/NetShears/Storage.swift b/Sources/NetShears/Storage.swift index 4b243b8..7b12e0e 100644 --- a/Sources/NetShears/Storage.swift +++ b/Sources/NetShears/Storage.swift @@ -10,7 +10,7 @@ import Foundation final class Storage: NSObject { - public static let shared: Storage = Storage() + static let shared: Storage = Storage() var requests: [NetShearsRequestModel] = [] @@ -21,9 +21,9 @@ final class Storage: NSObject { if let index = requests.firstIndex(where: { (req) -> Bool in return request?.id == req.id ? true : false - }){ + }) { requests[index] = request! - }else{ + } else { requests.insert(request!, at: 0) } NotificationCenter.default.post(name: NSNotification.Name.NewRequestNotification, object: nil) diff --git a/Sources/NetShears/URLProtocol/NetworkInterceptorUrlProtocol.swift b/Sources/NetShears/URLProtocol/NetworkInterceptorUrlProtocol.swift index ba40ac8..4628fa2 100644 --- a/Sources/NetShears/URLProtocol/NetworkInterceptorUrlProtocol.swift +++ b/Sources/NetShears/URLProtocol/NetworkInterceptorUrlProtocol.swift @@ -7,9 +7,9 @@ import Foundation -public class NetworkInterceptorUrlProtocol: URLProtocol { +class NetworkInterceptorUrlProtocol: URLProtocol { static var blacklistedHosts = [String]() - + struct Constants { static let RequestHandledKey = "NetworkInterceptorUrlProtocol" } @@ -25,9 +25,9 @@ public class NetworkInterceptorUrlProtocol: URLProtocol { } } - override public class func canInit(with request: URLRequest) -> Bool { + override class func canInit(with request: URLRequest) -> Bool { guard NetworkInterceptor.shared.shouldRequestModify(urlRequest: request) else { return false } - + if NetworkInterceptorUrlProtocol.property(forKey: Constants.RequestHandledKey, in: request) != nil { return false } @@ -40,7 +40,7 @@ public class NetworkInterceptorUrlProtocol: URLProtocol { return mutableRequest.copy() as! URLRequest } - override public func startLoading() { + override func startLoading() { var newRequest = request for modifier in NetShears.shared.config.modifiers where modifier.isActionAllowed(urlRequest: request) { modifier.modify(request: &newRequest) @@ -51,7 +51,7 @@ public class NetworkInterceptorUrlProtocol: URLProtocol { sessionTask?.resume() } - override public func stopLoading() { + override func stopLoading() { sessionTask?.cancel() session?.invalidateAndCancel() } @@ -63,17 +63,17 @@ public class NetworkInterceptorUrlProtocol: URLProtocol { } extension NetworkInterceptorUrlProtocol: URLSessionDataDelegate { - public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { client?.urlProtocol(self, didLoad: data) } - public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { let policy = URLCache.StoragePolicy(rawValue: request.cachePolicy.rawValue) ?? .notAllowed client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: policy) completionHandler(.allow) } - public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { if let error = error { client?.urlProtocol(self, didFailWithError: error) } else { @@ -81,17 +81,17 @@ extension NetworkInterceptorUrlProtocol: URLSessionDataDelegate { } } - public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { + func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { client?.urlProtocol(self, wasRedirectedTo: request, redirectResponse: response) completionHandler(request) } - public func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) { + func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) { guard let error = error else { return } client?.urlProtocol(self, didFailWithError: error) } - public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { let protectionSpace = challenge.protectionSpace let sender = challenge.sender @@ -105,7 +105,7 @@ extension NetworkInterceptorUrlProtocol: URLSessionDataDelegate { } } - public func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { + func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { client?.urlProtocolDidFinishLoading(self) } } diff --git a/Sources/NetShears/URLProtocol/NetworkLoggerUrlProtocol.swift b/Sources/NetShears/URLProtocol/NetworkLoggerUrlProtocol.swift index 8c352e8..f374f5d 100644 --- a/Sources/NetShears/URLProtocol/NetworkLoggerUrlProtocol.swift +++ b/Sources/NetShears/URLProtocol/NetworkLoggerUrlProtocol.swift @@ -7,8 +7,8 @@ import Foundation -public class NetworkLoggerUrlProtocol: URLProtocol { - +class NetworkLoggerUrlProtocol: URLProtocol { + struct Constants { static let RequestHandledKey = "NetworkLoggerUrlProtocol" } @@ -16,6 +16,12 @@ public class NetworkLoggerUrlProtocol: URLProtocol { var session: URLSession? var sessionTask: URLSessionDataTask? var currentRequest: NetShearsRequestModel? + lazy var requestObserver: RequestObserverProtocol = { + RequestObserver(options: [ + RequestStorage.shared, + RequestBroadcast.shared + ]) + }() override init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) { super.init(request: request, cachedResponse: cachedResponse, client: client) @@ -25,36 +31,40 @@ public class NetworkLoggerUrlProtocol: URLProtocol { } } - override public class func canInit(with request: URLRequest) -> Bool { - + override class func canInit(with request: URLRequest) -> Bool { + if NetworkLoggerUrlProtocol.property(forKey: Constants.RequestHandledKey, in: request) != nil { return false } return true } - override public class func canonicalRequest(for request: URLRequest) -> URLRequest { + override class func canonicalRequest(for request: URLRequest) -> URLRequest { return request } - override public func startLoading() { + override func startLoading() { let newRequest = ((request as NSURLRequest).mutableCopy() as? NSMutableURLRequest)! NetworkLoggerUrlProtocol.setProperty(true, forKey: Constants.RequestHandledKey, in: newRequest) sessionTask = session?.dataTask(with: newRequest as URLRequest) sessionTask?.resume() currentRequest = NetShearsRequestModel(request: newRequest, session: session) - Storage.shared.saveRequest(request: currentRequest) + if let request = currentRequest { + requestObserver.newRequestArrived(request) + } } - override public func stopLoading() { + override func stopLoading() { sessionTask?.cancel() currentRequest?.httpBody = body(from: request) if let startDate = currentRequest?.date{ currentRequest?.duration = fabs(startDate.timeIntervalSinceNow) * 1000 //Find elapsed time and convert to milliseconds } - - Storage.shared.saveRequest(request: currentRequest) + + if let request = currentRequest { + requestObserver.newRequestArrived(request) + } session?.invalidateAndCancel() } @@ -62,7 +72,7 @@ public class NetworkLoggerUrlProtocol: URLProtocol { /// The receiver will have either an HTTP body or an HTTP body stream only one may be set for a request. /// A HTTP body stream is preserved when copying an NSURLRequest object, /// but is lost when a request is archived using the NSCoding protocol. -// return request.httpBody ?? request.httpBodyStream?.readfully() + // return request.httpBody ?? request.httpBodyStream?.readfully() return nil } @@ -74,7 +84,7 @@ public class NetworkLoggerUrlProtocol: URLProtocol { } extension NetworkLoggerUrlProtocol: URLSessionDataDelegate { - public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { client?.urlProtocol(self, didLoad: data) if currentRequest?.dataResponse == nil{ currentRequest?.dataResponse = data @@ -84,14 +94,14 @@ extension NetworkLoggerUrlProtocol: URLSessionDataDelegate { } } - public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { let policy = URLCache.StoragePolicy(rawValue: request.cachePolicy.rawValue) ?? .notAllowed client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: policy) currentRequest?.initResponse(response: response) completionHandler(.allow) } - public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { if let error = error { currentRequest?.errorClientDescription = error.localizedDescription client?.urlProtocol(self, didFailWithError: error) @@ -100,18 +110,18 @@ extension NetworkLoggerUrlProtocol: URLSessionDataDelegate { } } - public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { + func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { client?.urlProtocol(self, wasRedirectedTo: request, redirectResponse: response) completionHandler(request) } - public func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) { + func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) { guard let error = error else { return } currentRequest?.errorClientDescription = error.localizedDescription client?.urlProtocol(self, didFailWithError: error) } - public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { let protectionSpace = challenge.protectionSpace let sender = challenge.sender @@ -125,7 +135,7 @@ extension NetworkLoggerUrlProtocol: URLSessionDataDelegate { } } - public func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { + func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { client?.urlProtocolDidFinishLoading(self) } } diff --git a/Sources/NetShears/Utils/ThreadSafe.swift b/Sources/NetShears/Utils/ThreadSafe.swift new file mode 100644 index 0000000..12c0853 --- /dev/null +++ b/Sources/NetShears/Utils/ThreadSafe.swift @@ -0,0 +1,26 @@ +// +// ThreadSafe.swift +// +// +// Created by Ali Moazenzadeh on 11/17/21. +// + +import Foundation + +final class ThreadSafe { + private var _value: A + private let queue = DispatchQueue(label: "ThreadSafe") + init(_ value: A) { + self._value = value + } + + var value: A { + return queue.sync { _value } + } + + func atomically(_ transform: (inout A) -> Void) { + queue.sync { + transform(&self._value) + } + } +} diff --git a/Tests/NetShearsTests/XCTestManifests.swift b/Tests/NetShearsTests/XCTestManifests.swift index 3c11082..a00fb9a 100644 --- a/Tests/NetShearsTests/XCTestManifests.swift +++ b/Tests/NetShearsTests/XCTestManifests.swift @@ -1,7 +1,7 @@ import XCTest #if !canImport(ObjectiveC) -public func allTests() -> [XCTestCaseEntry] { +func allTests() -> [XCTestCaseEntry] { return [ testCase(NetShearsTests.allTests), ]