Post

Replies

Boosts

Views

Activity

Reply to Networking in iOS app v Mac command line tool
Hi,Thanks. The problem I'm having is the different behaviour when I run the above code (yours or mine) in an iOS app v an OSX command line tool.So now, using your suggested code, I get different results between iOS and OSX. But with both I see no state change being reported. The examples all look the same, and I've looked at other networking frameworks. Is this something weird about running from Xcode?I am using this test snippet:let conn = Connection() conn.startConnection() sleep(10) for index in 1...3 { conn.send(message: "test message \(index)") }And this is the code:import Foundation import Network @available(OSX 10.14, *) class Connection: ConnectionDelegate { var connection: NWConnection? weak var delegate: ConnectionDelegate? func startConnection() { connection = NWConnection(host: "127.0.0.1", port: NWEndpoint.Port("80")!, using: .tcp) connection?.stateUpdateHandler = { [weak self] newState in guard let strongSelf = self else { return } switch newState { case .setup: print("Connection setup") case .preparing: print("Connection preparing") case .ready: print("Connection Established") strongSelf.delegate?.connectionDidUpdate(strongSelf, true) case .waiting(let error): print("Waiting with \(error)") case .failed(let error): print("Failed with \(error)") default: break } } connection?.start(queue: .main) print("connection start") } func connectionDidUpdate(_ conn: Connection, _ state: Bool) { print("Did update state to ready: \(state), \(conn)") } func send(message: String) { print("sending '\(message)' while connection state is \(connection?.state)") connection?.send(content: Data(message.utf8), completion: NWConnection.SendCompletion.idempotent) } } protocol ConnectionDelegate: AnyObject { func connectionDidUpdate(_ conn: Connection, _ ready: Bool) }Console output when run as an iOS app:connection start sending 'test message 1' while connection state is Optional(Network.NWConnection.State.setup) sending 'test message 2' while connection state is Optional(Network.NWConnection.State.setup) sending 'test message 3' while connection state is Optional(Network.NWConnection.State.setup)Running nc in Terminal:% nc -l 80 test message 1test message 2test message 3%Console output when run as an OSX command-line-tool:connection start sending 'test message 1' while connection state is Optional(Network.NWConnection.State.setup) sending 'test message 2' while connection state is Optional(Network.NWConnection.State.setup) sending 'test message 3' while connection state is Optional(Network.NWConnection.State.setup) Program ended with exit code: 0Running nc in Terminal:% nc -l 80 test message 1%
Mar ’20
Reply to Networking in iOS app v Mac command line tool
ahh, yes I see. Yes, I get the correct results when using dispatchMain(). For anyone else, I found a good explanation of dispatchMain() here. It makes most sense if you follow the thread discussion all the way through.I also realised (as a result of the above) that I was mis-declaring 'conn = Connection()' inside methods in the iOS app. Which obviously meant conn disappeared as soon as the method finished. And that was why it was intermittently working (especially with the 'sleep 10' which was actually meant for the cli version). So I was confusing myself... 😁Using the above, I've now got the correct results when importing the framework into both an iOS app and the command line tool. Which is exactly what I need.many thanks for your help/patience.
Mar ’20
Reply to Allow var to hold parent or sub-class type
So, I had a thought. Setting my var via a convenience initialiser. And it seems to work fine.Modified my mock class:public class MockNetworkHandler: NetworkHandler { // this var should let me switch on/off the network for unit tests var websiteActive: Bool public override init() { self.websiteActive = true // default super.init() } public convenience init(networkActive: Bool = true) { self.init() websiteActive = networkActive } override func websiteStatus(_ fullURL: String, _ completion: @escaping (Int) -> Void) { if websiteActive { completion(801) // 801 just to differentiate from a real request in this example } else{ completion(901) // 901 just to differentiate from a real request in this example } } }In use:var mockClient = AppClient(mockNetHandler: MockNetworkHandler(networkActive: true)) mockClient.netHandler?.websiteStatus("https://apple.com", statusCompletion) mockClient = AppClient(mockNetHandler: MockNetworkHandler(networkActive: false)) mockClient.netHandler?.websiteStatus("https://apple.com", statusCompletion)Output:status 801 status 901
Apr ’20
Reply to Allow var to hold parent or sub-class type
I am trying to work out the best way to unit test classes that depend on networking. I do not want any unit test code/scaffolding in the main code-base. The main code is a Swift package, with unit tests alongside.So, here is an example of two unit tests I have which test the outcome of device.processQueue(). I must test this for both the network being active and inactive. func testDeviceProcessQueueConnReady() { let device = CKDevice() device.conn = ConnectionMock(networkResponse: .ready) device.submitToQueue(payload: "some data") // create some data to send device.startConnection() device.processQueue() let sendQueueLength = device.sendQueue.count XCTAssertEqual(0, sendQueueLength) // payload should have been sent } func testDeviceProcessQueueConnNotReady() { let device = CKDevice() device.conn = ConnectionMock(networkResponse: .preparing) device.submitToQueue(payload: "some data") // create some data to send device.startConnection() device.processQueue() let sendQueueLength = device.sendQueue.count XCTAssertEqual(1, sendQueueLength) // payload should still be queued }And I create the mock class in my unit test modules:public class ConnectionMock: Connection { var networkState = NWConnection.State.ready init() { super.init(host: "127.0.0.1", port: "8000") } convenience init(networkResponse: NWConnection.State) { self.init() self.networkState = networkResponse } public override func startConnection() { self.delegate?.connectionDidUpdate(self, (NWConnection.State.ready == networkState)) } public override func sendMessage(queueID: String, messageString: String) { // immediate response for unitests, rather than async in normal code flow self.delegate?.messageProcessed(queueID: queueID, result: (NWConnection.State.ready == networkState)) } }So, without putting any code in the main code body (deployable app/package) I can simulate network conditions per unit test.cheers,
Apr ’20
Reply to Allow var to hold parent or sub-class type
So the choices are1. after assigning the mock handler to mockClient.netHandler, the value can be changed by force-casting it back to MockNetworkHandler. (your first above)2. to set the value before assigning it to mockClient.netHandler, at which point netHandler is effectively cast to the NetworkHandler type. (additional initialisers, your second above)All makes sense now.thanks.
Apr ’20
Reply to Best approach to optionally show sign-up view, with SwiftUI?
Ahh, cool. Thanks.For anyone else, here's this method with a navigation view.struct ContentView: View { @EnvironmentObject var userStatus: UserStatus var body: some View { ZStack { NavigationView { NavigationLink(destination: Text("Second View")) { Text("Hello, World!") } .navigationBarTitle("Navigation") } if !userStatus.hasAccount { Text("test: has no account") .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity) .background(Color.red) .edgesIgnoringSafeArea(.all) } } } } class UserStatus: ObservableObject { @Published var hasAccount: Bool = true //... }And with a tab view (which I'm using):struct ContentView: View { @EnvironmentObject var userStatus: UserStatus var body: some View { ZStack { TabView { Text("First View") .tabItem { Image(systemName: "1.circle") Text("First") }.tag(0) } if !userStatus.hasAccount { Text("test: has no account") .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity) .background(Color.red) .edgesIgnoringSafeArea(.all) } } } } class UserStatus: ObservableObject { @Published var hasAccount: Bool = false //... }
Jun ’20
Reply to Behaviour of dispatch group tasks with completions?
So I implemented the control using a semaphore, and it seems to be working fine. Though not load tested. Semaphores seem easier to understand than dispatch groups, more explicit. But I'm not 100% which is idiomatic for Swift. In the closure of the api_call to rotate-credentials I signal the semaphore like this : { 	// rotate-credentials api call closure 	defer { 		self.semaphore.signal() 		print("semaphore signal - apiCredential") 	} 	// main closure code } The debug output gives the logical flow: ident loaded: 7CC20A7BEAF242C8B853BAE960402EC7 secret loaded: A52189B0CA8D42ABAF22047439057B44 semaphore wait over - getnodestatus Req http://127.0.0.1:8000/vendor/node/status, Resp 418 💥 credential trigger 💥 Triggered credential rotation for 7CC20A7BEAF242C8B853BAE960402EC7 with secret 0356FC2123804D4DB232379BD88CA06B, signed with A52189B0CA8D42ABAF22047439057B44. Req http://127.0.0.1:8000/vendor/node/credential, Resp 201 KeychainManager: Successfully deleted data KeychainManager: Item added successfully Primary credential now: 0356FC2123804D4DB232379BD88CA06B semaphore signal - apiCredential semaphore wait over - getnodestatus Req http://127.0.0.1:8000/vendor/node/status, Resp 204 semaphore wait over - getnodestatus semaphore signal - getnodestatus Req http://127.0.0.1:8000/vendor/node/status, Resp 204 semaphore signal - getnodestatus semaphore wait over - getnodestatus Req http://127.0.0.1:8000/vendor/node/status, Resp 204 semaphore signal - getnodestatus
Jun ’20
Reply to Compile error, doing calculations in View body
For anyone happening upon this, here's the working version. In my case I'm drawing a collection of buttons. struct collectionView: View {     let totalElements = 8     let elementWidth: CGFloat = 80.0     var body: some View {         GeometryReader&lt;AnyView&gt; { geometry in             let elementsPerRow = Int(geometry.size.width / self.elementWidth)             let rowCount = Int( (CGFloat(self.totalElements) / CGFloat(elementsPerRow)).rounded(.up) )             return AnyView(                 VStack(alignment: .leading) {                     ForEach(0..<rowCount) { rowIndex in                         HStack {                             ForEach(0..<elementsPerRow) { elementIndex in                                 if ((rowIndex * elementsPerRow) + elementIndex < self.totalElements) {                                     Button(action: {                                         print("\((rowIndex * elementsPerRow) + elementIndex)")                                     }) {                                         Text("\( (rowIndex * elementsPerRow) + elementIndex)")                                         .frame(maxWidth: self.elementWidth, maxHeight: self.elementWidth)                                         .background(Color.purple)                                         .foregroundColor(Color.white)                                         .clipShape(Circle())                                     }                                 }                             }                         }                     }                 }             )         }     } }
Jul ’20