How to remove a published service NetService - ios

I am using the latest Swift documentation to write NetService, which am able to publish and search. But I am unable to remove service from the publisher. Not able to understand the role of RunLoop here. However, if I kill the app then service is getting removed.
This is the code am using to publish the service.
class ServicePublisher : NSObject {
var nsNetService : NetService!
init?(domain : String, type : String, name : String, port : Int32){
nsNetService = NetService(domain: domain, type: type, name: name, port: port)
if nsNetService == nil{
return nil
}
}
func publish() {
nsNetService.publish(options:NetService.Options.listenForConnections)
}
func setDelegate(delegate : NetServiceDelegate) {
nsNetService.delegate = delegate
}
func stop(){
nsNetService.stop()
}
}

nsNetService.stop() should stop the service and get it removed from the DNS SD. NetServiceBrowser is also able to detect the removal of service. My mistake was that I did not attached the UIButton with the function which was supposed to call stop() api and hence no service removal.

Related

How to create NWConnection

Premise
I'm using Apple's Network framework to create a peer-to-peer app. What I'm currently trying to do is:
Device A establishes a connection with Device B and sends data.
Device B saves the NWConnection information of Device A.
Device B establishes a connection with Device A again using the saved MWConnection information and sends data.
I modelled off Apple's demo, which is essentially this source code here. Unlike Apple's demo, which establishes a single two-way connection and maintains that connection at all times, my app connects to multiple devices that can drop in and out of the connections at any time. This is why I want to be able to a) distinguish a specific device and b) initiate a new connection.
Problem
The problem I'm having is being able to reconstruct the NWConnection object using the information provided when a connection has been established. There are two ways of instantiating this object.
init(host: NWEndpoint.Host, port: NWEndpoint.Port, using: NWParameters)
init(to: NWEndpoint, using: NWParameters)
My attempts have been trying to gather the endpoint information like the host and the port while the connection with the desired device has been established and instantiate NWEndpoint to be used in NWConnection. But, I haven't been able to re-establish the connection thus far.
Following is the portion of the object that is used to initiate a connection. My full code is modified, but Apple's counterpart can be seen here.
class PeerConnection {
weak var delegate: PeerConnectionDelegate?
weak var statusDelegate: StatusDelegate?
var connection: NWConnection?
let initiatedConnection: Bool
init(endpoint: NWEndpoint, interface: NWInterface?, passcode: String, delegate: PeerConnectionDelegate) {
self.delegate = delegate
self.initiatedConnection = true
let connection = NWConnection(to: endpoint, using: NWParameters(passcode: passcode))
self.connection = connection
startConnection()
}
init(connection: NWConnection, delegate: PeerConnectionDelegate) {
self.delegate = delegate
self.connection = connection
self.initiatedConnection = false
startConnection()
}
// Handle starting the peer-to-peer connection for both inbound and outbound connections.
func startConnection() {
guard let connection = connection else {
return
}
connection.stateUpdateHandler = { newState in
switch newState {
case .ready:
print("\(connection) established")
// When the connection is ready, start receiving messages.
self.receiveNextMessage()
// Notify your delegate that the connection is ready.
if let delegate = self.statusDelegate {
delegate.showConnectionStatus(.connectionSuccess("Connection Success"))
}
case .failed(let error):
print("\(connection) failed with \(error)")
// Cancel the connection upon a failure.
connection.cancel()
// Notify your delegate that the connection failed.
if let delegate = self.statusDelegate {
delegate.showConnectionStatus(.connectionFail("Connection Fail"))
}
default:
break
}
}
// Start the connection establishment.
connection.start(queue: .main)
}
}
This is how I receive data when another device sends it.
func receiveNextMessage() {
guard let connection = connection else {
return
}
/// Has to call itself again within the closure because the maximum message is once.
connection.receiveMessage { (content, context, isComplete, error) in
// Extract your message type from the received context.
if let message = context?.protocolMetadata(definition: GameProtocol.definition) as? NWProtocolFramer.Message {
self.delegate?.receivedMessage(content: content, message: message, connection: connection)
}
if error == nil {
// Continue to receive more messages until you receive and error.
self.receiveNextMessage()
}
}
}
Finally, following is how I attempt to reconstruct MWConnection:
func receivedMessage(content: Data?, message: NWProtocolFramer.Message, connection: NWConnection) {
switch(connection.endpoint) {
case .hostPort(let host, let port):
/// first attempt
let endpoint = NWEndpoint.hostPort(host: host, port: port)
let newConnection = PeerConnection(endpoint: endpoint, interface: nil, passcode: passcode, delegate: self)
/// second attempt
let connection = NWConnection(host: host, port: port, using: NWParameters(passcode: passcode))
let newConnection = PeerConnection(connection: connection, delegate: self)
default:
break
}
}
NWConnection is instantiated inside initializer of PeerConnection.

Is is possible to open SwiftNIO based server socket within XCTest test app?

I have an XCTest which works with UI components. I tried to open a server socket within the xctext function using SwiftNIO.
I took the echo server example from here. and I simplified, removed the args with hardcoded values for the sake of a dirty test.
import XCTest
import NIOCore
import NIOPosix
private final class EchoHandler: ChannelInboundHandler {
public typealias InboundIn = ByteBuffer
public typealias OutboundOut = ByteBuffer
public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
// As we are not really interested getting notified on success or failure we just pass nil as promise to
// reduce allocations.
context.write(data, promise: nil)
}
// Flush it out. This can make use of gathering writes if multiple buffers are pending
public func channelReadComplete(context: ChannelHandlerContext) {
context.flush()
}
public func errorCaught(context: ChannelHandlerContext, error: Error) {
print("error: ", error)
// As we are not really interested getting notified on success or failure we just pass nil as promise to
// reduce allocations.
context.close(promise: nil)
}
}
class MyXCTests: XCTestCase {
var app: XCUIApplication!
override func setUpWithError() throws {
continueAfterFailure = false
app = XCUIApplication()
// Catch system alerts such as "allow connecting to Wi-fi network"
addUIInterruptionMonitor(withDescription: "System Dialog") { (alert) -> Bool in
alert.buttons["Join"].tap()
return true
}
}
override func tearDownWithError() throws {
}
func testXYZ() throws {
app.launch()
let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
let bootstrap = ServerBootstrap(group: group)
// Specify backlog and enable SO_REUSEADDR for the server itself
.serverChannelOption(ChannelOptions.backlog, value: 256)
.serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
// Set the handlers that are appled to the accepted Channels
.childChannelInitializer { channel in
// Ensure we don't read faster than we can write by adding the BackPressureHandler into the pipeline.
channel.pipeline.addHandler(BackPressureHandler()).flatMap { v in
channel.pipeline.addHandler(EchoHandler())
}
}
// Enable SO_REUSEADDR for the accepted Channels
.childChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.childChannelOption(ChannelOptions.maxMessagesPerRead, value: 16)
.childChannelOption(ChannelOptions.recvAllocator, value: AdaptiveRecvByteBufferAllocator())
defer {
try! group.syncShutdownGracefully()
}
let channel = try { () -> Channel in
return try bootstrap.bind(host: "0.0.0.0", port: 1234).wait()
}()
print("============= Server started and listening on \(channel.localAddress!)")
// then some XCTest code which works here was cut from this snippet
}
The test runs correctly, it also prints
============= Server started and listening on [IPv4]0.0.0.0/0.0.0.0:1234
But in reality EchoClient from here doesn't work
swift run NIOEchoClient localhost 1234    1785
[0/0] Build complete!
Please enter line to send to the server
dfsdfd
Swift/ErrorType.swift:200: Fatal error: Error raised at top level: NIOPosix.NIOConnectionError(host: "localhost", port: 1234, dnsAError: nil, dnsAAAAError: nil, connectionErrors: [NIOPosix.SingleConnectionFailure(target: [IPv6]localhost/::1:1234, error: connection reset (error set): Connection refused (errno: 61)), NIOPosix.SingleConnectionFailure(target: [IPv4]localhost/127.0.0.1:1234, error: connection reset (error set): Connection refused (errno: 61))])
[1] 28213 trace trap swift run NIOEchoClient localhost 1234
The listening socket also unavailable with
sudo lsof -PiTCP -sTCP:LISTEN
I was also trying UITestEntitlements to set com.apple.security.app-sandbox to false.
Is there a way to allow server sockets from XCTest?
Originally I was trying to embed a Swift-GRPC endpoint, to allow more finer grained control from a HW in the loop controller. The intent is to start an XCTest using command line xcodebuild, which in turn is starting a long running test, but instead of the test code written in Swift, I would expose the events when to tap some buttons, right outside of the test process through a grpc endpoint.
Since the grpc endpoint didn't worked, I reduced the problem to the one above.
Anybody have a hint, how to pass through this issue, or have a hint why it will never be possible to open server socket within an XCTest app, don't hesitate to reply here.
Yes, that is possible, you can find many examples of this in the AsyncHTTPClient and SwiftNIO test suites.
The reason that yours doesn't work is because you shut down the MultiThreadedEventLoopGroup right after binding the socket. So essentially you're starting everything up and then you shut it down again.
Also, for unit tests, I'd recommend binding to 127.0.0.1 only because you probably don't want connections from elsewhere. Another good idea is to use an ephemeral port, ie. have the system pick a free, random port automatically. You can achieve this by specifying port 0. After you bind the server Channel you can then interrogate the server channel by using serverChannel.localAddress?.port! about the port it picked.
Here's a full example with a client and a server in a test case.
import XCTest
import NIO
final class ExampleTests: XCTestCase {
// We keep the `EventLoopGroup` where all the I/O runs alive during the test
private var group: EventLoopGroup!
// Same for the server channel.
private var serverChannel: Channel!
private var serverAddress: SocketAddress {
return self.serverChannel.localAddress!
}
// We set up the server in `setUp`...
override func setUp() {
self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
XCTAssertNoThrow(self.serverChannel = try ServerBootstrap(group: self.group)
.childChannelInitializer { channel in
channel.pipeline.addHandler(UppercasingHandler())
}
.bind(host: "127.0.0.1", port: 0) // port 0 means: pick a free port.
.wait())
}
// ... and tear it down in `tearDown`.
override func tearDown() {
XCTAssertNoThrow(try self.serverChannel?.close().wait())
XCTAssertNoThrow(try self.group?.syncShutdownGracefully())
}
func testExample() throws {
// Here we just bootstrap a little client that sends "Hello world!\n"
let clientChannel = try ClientBootstrap(group: self.group)
.channelInitializer { channel in
channel.pipeline.addHandler(PrintEverythingHandler())
}
.connect(to: self.serverAddress)
.wait()
XCTAssertNoThrow(try clientChannel
.writeAndFlush(ByteBuffer(string: "Hello world!\n"))
.wait())
XCTAssertNoThrow(try clientChannel.closeFuture.wait())
}
}
// Just a handler that uses the C `toupper` function which uppercases characters.
final class UppercasingHandler: ChannelInboundHandler {
typealias InboundIn = ByteBuffer
typealias OutboundOut = ByteBuffer
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
let inBuffer = self.unwrapInboundIn(data)
var outBuffer = context.channel.allocator.buffer(capacity: inBuffer.readableBytes)
// Here we just upper case each byte using the C stdlib's `toupper` function.
outBuffer.writeBytes(inBuffer.readableBytesView.map { UInt8(toupper(CInt($0))) })
context.writeAndFlush(self.wrapOutboundOut(outBuffer),
promise: nil)
}
// We want to close the connection on any error really.
func errorCaught(context: ChannelHandlerContext, error: Error) {
print("server: unexpected error \(error), closing")
context.close(promise: nil)
}
}
// This handler just prints everything using the `write` system call. And closes the connection on a newline.
final class PrintEverythingHandler: ChannelInboundHandler {
typealias InboundIn = ByteBuffer
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
let inBuffer = self.unwrapInboundIn(data)
guard inBuffer.readableBytes > 0 else {
return
}
// We're using Unsafe* stuff here because we're using the `write` system call, which is a C function.
_ = inBuffer.withUnsafeReadableBytes { ptr in
write(STDOUT_FILENO, ptr.baseAddress!, ptr.count)
}
// If we see a newline, then let's actually close the connection...
if inBuffer.readableBytesView.contains(UInt8(ascii: "\n")) {
print("found newline, closing...")
context.close(promise: nil)
}
}
func errorCaught(context: ChannelHandlerContext, error: Error) {
print("client: unexpected error \(error), closing")
context.close(promise: nil)
}
}

GRPC-Swift Send HTTPProtocolVersion from Client

I'm trying GRPC-Swift for Client-Server application.
I'm using GRPC-Swift for both Client and Server
Client is an iPhone application, which I tried with iPhone Simulator.
I followed this link for Client-side streaming RPC.
When I send message to Server from Client, I got the following error message in the console from Server,
error io.grpc.server_channel_call : unable to determine http version
From the Server in the
HTTPProtocolSwitcher.swift
inside the function func channelRead(context: ChannelHandlerContext, data: NIOAny), it is checking for HTTPProtocolVersion, and it is missing.
How to send the HTTPVersion from the Client code?
Update:
Client Code
import GRPC
import NIO
class HTTPClient {
private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
private var channel: ClientConnection?
private var client: ChatGuide_ChatGuideClient?
private var clientCall: ClientStreamingCall<ChatGuide_TextMessage, ChatGuide_TextMessage>?
func connect(host: String, port: Int) throws {
let channel = ClientConnection.secure(group: self.group)
.connect(host: host, port: port)
self.channel = channel
self.client = ChatGuide_ChatGuideClient(channel: channel)
}
func disconnect() {
do {
self.clientCall?.sendEnd(promise: nil)
_ = try self.clientCall?.status.wait()
try self.group.syncShutdownGracefully()
} catch let error {
print("\(type(of: self)): Could not shutdown gracefully -", error.localizedDescription)
}
}
func initiateClient() {
let timeAmount = TimeAmount.minutes(1)
let timeLimit = TimeLimit.timeout(timeAmount)
let options = CallOptions(timeLimit: timeLimit)
let call = self.client?.chat(callOptions: options)
call?.response.whenSuccess { (message) in
print("\(type(of: self)): Message from server -", message.text)
}
call?.response.whenFailure { (error) in
print("\(type(of: self)): Response error -", error.localizedDescription)
}
self.clientCall = call
}
func send(text: String) {
if self.clientCall == nil {
self.initiateClient()
}
let message = ChatGuide_TextMessage.with {
$0.text = text
}
self.clientCall?.sendMessage(message, promise: nil)
}
}
Hey Vignesh,
I am currently learning gRPC-Swift myself, so I hope I will be of service and not muck things further.
However, it looks to me that you are not configuring the HTTP/1.x layer in order to transfer Protobuf packets, if you take a look at the HTTP1ToGRPCServerCodec.swift file Here
I think you will have a much clearer idea of how to adjust your code, I am sorry I can't provide more details, however not being too sure myself without further testing and reviewing the codebase.
Best regards and keep me posted if indeed i was helpful,
cheers
From the Server I have initiated insecure Server as,
let server = Server.insecure(group: self.group)
From the Client I have initiated secure ClientConnection as,
let channel = ClientConnection.secure(group: self.group)
And I got this clarification from here
So I made the ClientConnection also insecure as,
let channel = ClientConnection.insecure(group: self.group)
And after this it is working now.

Checking for internet connection or wifi connection with reachabilitySwift

I'm building an iOS app, at some point i needed to check the app's acces to internet, so i used the ReachabilitySwift library.
After some tests, it seems to me that the library only checks if the device has the wifi connected and not having an actual internet connection provided from my router.
lets say i disconnected my router from internet, if my iOS device is connected via wifi to the router, the reachability tells me that we have internet connection where in actuallity my router has no internet.
I tried the reachability with a particular host but still having the same result
var reachability = Reachability.init(hostname: "google.com");
Is the reachability library supposed to give feedback when the wifi connection is lost or the actual internet connection is lost ?
Thank you in advance.
I have had similar issues with Reachability where I was making web service calls to VPN protected network.
var reachability = Reachability.init(hostname: "google.com");
didnt work for me as it returned true when there is Wifi connection.
I used Alamofire response when a dummy call is made to the server
func networkIsAvailable(available: (Bool, String?) -> Void) {
var message : String = ""
DispatchQueue.main.async {
HUD.show(.progress)
Alamofire.request(Constants.baseUrl, method: .get, parameters: ["foo": "bar"])
.responseJSON(completionHandler: { (response) in
let error = response.result.error as? NSError
if error?.localizedDescription == networkAlert {
message = networkAlert
available(false, message)
} else if error?.code == cannotConnectServerCode || error?.localizedDescription == cannotConnectServerMessage {
message = cannotConnectServerMessage
available(false, anotherNetworkAlert)
} else {
available(true, nil)
}
})
}
}
At WWDC, Apple has said many many times that if you need to simply see if WiFi or cell is up - use Reachable. If you need to see if you can connect to - then don’t use Reachable; instead simply connect and handle the error.
Reachable doesn’t actually check to see if it can connect to that IP based resource and if you are going to get a result back. The only way to do that is to actually try.
Look for networking sessions with Quinn on Apple’s WWDC site to see the same advice.
In the Reachablility.h you can find a declaration:
typedef enum : NSInteger {
NotReachable = 0,
ReachableViaWiFi,
ReachableViaWWAN
} NetworkStatus;
Then, inside Reachability class you can find the method, which returns type:
- (NetworkStatus)currentReachabilityStatus;
So, use it. In your swift code you can do it like this:
if reachability.currentReachabilityStatus == .ReachableViaWiFi {
// Do things...
}

ios how to implement SocketIO in iOS

I have a Server Address: http://someaddress.com/
by using SocketIO iOS library to connect to the above address with the correct protocol and namespace.
Socket Namespace: “/random”
Socket Event for random number: “capture”
This server uses SocketIO to send the randomly generated numbers every 4 seconds over the namespace “/random” with the event “capture”. How to access above address using SocketIO library.
Thanks in advance.
enum Socket: String{
case serverURL = "YOUR_SERVER_URL"
case namespace = "NAMESPACE NAME"
case eventName = "EVENT NAME"
}
/// Represents actual socket object with server url and namespace.
var socket: SocketIOClient = SocketIOClient(socketURL: NSURL(string: Socket.serverURL.rawValue)! as URL, config: [.nsp(Socket.namespace.rawValue)])
override init() {
super.init()
}
/**
This function used to establish connection with server.
- Parameter: nil.
- Returns: nil.
*/
func establishConnection() {
socket.connect()
}
/**
This function used to fetch next number from server.
- Parameter: nil.
- Returns: nil.
*/
func nextNumberFromServer(){
socket.on(Socket.eventName.rawValue) {data, ack in
if let number = data[0] as? NSNumber {
print(number)
}
}
}
/**
This function used to close connection with server.
- Parameter: nil.
- Returns: nil.
*/
func closeConnection() {
socket.disconnect()
}

Resources