Posts

Post marked as solved
3 Replies
Ach, my obvious mistake on the first one. I had been using a separate custom log method, which did have a label. On the second, ahh yes I see. Compiled first time with the changes. thanks very much,
Post marked as solved
3 Replies
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())                                     }                                 }                             }                         }                     }                 }             )         }     } }
Post not yet marked as solved
2 Replies
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 : { &#9;// rotate-credentials api call closure &#9;defer { &#9;&#9;self.semaphore.signal() &#9;&#9;print("semaphore signal - apiCredential") &#9;} &#9;// 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
Post marked as solved
2 Replies
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 //... }
Post not yet marked as solved
7 Replies
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.
Post not yet marked as solved
7 Replies
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,
Post not yet marked as solved
7 Replies
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) -&gt; 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
Post not yet marked as solved
7 Replies
Yes, I don't want it in the parent as that would mean test code polluting the main code. With mocking I can have all the mocked classes in my test modules.thanks
Post marked as solved
9 Replies
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.
Post marked as solved
9 Replies
I'll take a look at this, and play around with the code.
Post marked as solved
9 Replies
Hi,thanks for your help so far.The question is why is stateUpdateHandler not being called? Even though, clearly, the connection is coming up, data is sent (and received), and the connection closes. I need to be able to have some logic take action if the state changes. This is what I'm trying to figure out.cheers,
Post marked as solved
9 Replies
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%
Post marked as solved
5 Replies
Yes, a tcp/tls stream.And, yes, Network, of course! I just found the WWDC18 presentation on Network.framework (audio missing minutes 15-17) https://developer.apple.com/videos/play/wwdc2018/715/, for anyone else stumbling here through withUnsafeBytes 🙂thanks both for the help. both very helpful. I'll mark OOPer's reply as the answer as it did solve my immedate question/problem. But Network is the way to go, for sure, to avoid such questions/problems in the first place 🙂
Post marked as solved
5 Replies
Ah yes, I didn't understand enough about 'more than simple' closures. So I re-read, and I think this is now better (in the sense of more correct, and safer). Still verbose to be clear.I think I was also mis-understanding that when a method signature (say, withUnsafeBytes) contains a closure that 'throws', it means that the closure may throw and not that it must throw. By which I mean that if the closure omits any 'throw' expression, then that's not a violation?I also now get the implicit one-liner return value from a closure, which I no longer have with all the pointer type assignments.// refactor 3 do { // explicit type for dataSent4 because the return type cannot be inferred from the closure let dataSent4: Int? = try data.withUnsafeBytes({ // we need to re-bind with a typed UInt8 buffer pointer, from UnsafeRawBufferPointer let unsafeUInt8BufferPtr = $0.bindMemory(to: UInt8.self) // now we need a simple UInt8 pointer for outputStream.write(), not a buffer pointer if let unsafeUInt8Ptr = unsafeUInt8BufferPtr.baseAddress { if let count = self.outputStream?.write(unsafeUInt8Ptr, maxLength: unsafeUInt8BufferPtr.count) { if count &gt; 0 { // data was sent return count } } } throw MessageError.sending }) print("dataSent4: \(String(describing: dataSent4))") } catch MessageError.sending { print("sending exception") } catch { print("unhandled exception") }thanks,