I'm sending some data through UDP but the receiveMessage(completion:) doesn't get invoked.
With Network Framework i open the connection and when it's in the ready state i send a single datagram which is being sent successfully but i have problem receiving the incoming datagram.
I call receive on the same connection by the way.
class func connect()
{
connection = NWConnection(host: hostUDP, port: portUDP, using: .udp)
connection?.stateUpdateHandler =
{
(newState) in switch (newState)
{
case .ready:
//The connection is established and ready to send and recieve data.
print("ready")
self.sendPaket(self.sendingPacket)
self.receive()
print("i read this line")
case .setup:
//The connection has been initialized but not started
print("setup")
case .cancelled:
//The connection has been cancelled
print("cancelled")
case .preparing:
//The connection in the process of being established
print("Preparing")
default:
//The connection has disconnected or encountered an error
print("waiting or failed")
}
}
connection?.start(queue: .global())
}
class func sendPaket(_ packet:String)
{
let packet = dataWithHexString(hex: sendingPacket)
print("converted version to byte is :\(packet)")
connection?.send(content: packet, completion: NWConnection.SendCompletion.contentProcessed((
{
(NWError) in
if (NWError != nil)
{
print("error in sending packet : \(NWError!)")
}else
{
print("Packet Sent Successfully")
}
})))
}
class func receive()
{
print("Receive func got called")
connection?.receive(minimumIncompleteLength: 17, maximumLength: 100, completion:(
{
(data, context, isComplete, error) in
print(context!)
print("receiveMessage called")
if (isComplete)
{
print("receiving is complete!")
if (data != nil)
{
let backToString = String(decoding: data!, as: UTF8.self)
print("Received message: \(backToString)")
}else
{
print("data is nil")
}
}else
{
print("isn't complete")
}
if error != nil
{
print("error in receiving : \(error!)")
}
}))
}
What should i do?
Related
I've centralized API calls for my App in a class called APIService.
Calls look like the one below:
// GET: Attempts getconversations API call. Returns Array of Conversation objects or Error
func getConversations(searchString: String = "", completion: #escaping(Result<[Conversation], APIError>) -> Void) {
{...} //setting up URLRequest
let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200, let _ = data
else {
print("ERROR: ", error ?? "unknown error")
completion(.failure(.responseError))
return
}
do {
{...} //define custom decoding strategy
}
let result = try decoder.decode(ResponseMultipleElements<[Conversation]>.self, from: data!)
completion(.success(result.detailresponse.element))
}catch {
completion(.failure(.decodingError))
}
}
dataTask.resume()
}
I'm executing API calls from anywhere in the Application like so:
func searchConversations(searchString: String) {
self.apiService.getConversations(searchString: searchString, completion: {result in
switch result {
case .success(let conversations):
DispatchQueue.main.async {
{...} // do stuff
}
case .failure(let error):
print("An error occured \(error.localizedDescription)")
}
})
}
What I would like to achieve now is to execute func searchConversations for each character tapped by the user when entering searchString.
This would be easy enough by just calling func searchConversations based on a UIPressesEvent being fired. Like so:
override func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
guard let key = presses.first?.key else { return }
switch key.keyCode {
{...} // handle special cases
default:
super.pressesEnded(presses, with: event)
searchConversations(searchString: SearchText.text)
}
}
My problem is this now:
Whenever a new character is entered, I'd like to cancel the previous URLSession and kick-off a new one. How can I do that from inside the UIPressesEvent handler?
The basic idea is to make sure the API returns an object that can later be canceled, if needed, and then modifying the search routine to make sure to cancel any pending request, if any:
First, make your API call return the URLSessionTask object:
#discardableResult
func getConversations(searchString: String = "", completion: #escaping(Result<[Conversation], APIError>) -> Void) -> URLSessionTask {
...
let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in
...
}
dataTask.resume()
return dataTask
}
Have your search routine keep track of the last task, canceling it if needed:
private weak var previousTask: URLSessionTask?
func searchConversations(searchString: String) {
previousTask?.cancel()
previousTask = apiService.getConversations(searchString: searchString) { result in
...
}
}
We frequently add a tiny delay so that if the user is typing quickly we avoid lots of unnecessary network requests:
private weak var previousTask: URLSessionTask?
private weak var delayTimer: Timer?
func searchConversations(searchString: String) {
previousTask?.cancel()
delayTimer?.invalidate()
delayTimer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false) { [weak self] _ in
guard let self = self else { return }
self.previousTask = self.apiService.getConversations(searchString: searchString) {result in
...
}
}
}
The only other thing is that you probably want to change your network request error handler so that the “cancel” of a request isn’t handled like an error. From the URLSession perspective, cancelation is an error, but from our app’s perspective, cancelation is not an error condition, but rather an expected flow.
You can achieve this by using a timer,
1) Define a timer variable
var requestTimer: Timer?
2) Update searchConversations function
#objc func searchConversations() {
self.apiService.getConversations(searchString: SearchText.text, completion: {result in
switch result {
case .success(let conversations):
DispatchQueue.main.async {
{...} // do stuff
}
case .failure(let error):
print("An error occured \(error.localizedDescription)")
}
})
}
3) Update pressesEnded
override func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
guard let key = presses.first?.key else { return }
switch key.keyCode {
{...} // handle special cases
default:
super.pressesEnded(presses, with: event)
self.requestTimer?.invalidate()
self.requestTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(searchConversations), userInfo: nil, repeats: false)
}
}
I wanna have UDP connection with a wifi module as a server and an iOS as a client and send/receive data (obviously).First i wanted to use Sockets but then i realized Apple has introduced Network Framework; So i used NWConnection which is a class from Network Framework for my purpose and i was successful at sending the data to the device but unable to receive the response (which i am sure exists as i monitor devices I/O packets via serial port monitor).Here is a test version of the code where i use netcat as server to test the connection:
ViewController Class
import UIKit
import Network
class ViewController: UIViewController
{
var network: UDPNetwork!
override func viewDidLoad()
{
super.viewDidLoad()
}
#IBAction func button(_ sender: Any)
{
self.network = UDPNetwork(host: "192.168.200.4", port:"4210")!
self.network.connect()
}
}
UDPNetwork Class
import Foundation
import Network
class UDPNetwork {
var hostUDP: NWEndpoint.Host
var portUDP: NWEndpoint.Port
private var connection: NWConnection?
private var queue = DispatchQueue(label: "NetworkQuue", qos: .utility)
init?(host: String, port: String) {
guard !host.isEmpty, let portUDP = NWEndpoint.Port(port) else {
return nil
}
self.hostUDP = NWEndpoint.Host(host)
self.portUDP = portUDP
}
func connect()
{
connection = NWConnection(host: hostUDP, port: portUDP, using: .udp)
connection?.stateUpdateHandler =
{
(newState) in switch (newState)
{
case .ready:
//The connection is established and ready to send and recieve data.
print("ready")
self.sendPaket("Hello")
self.receive()
case .setup:
//The connection has been initialized but not started
print("setup")
case .cancelled:
//The connection has been cancelled
print("cancelled")
case .preparing:
//The connection in the process of being established
print("Preparing")
default:
//The connection has disconnected or encountered an error
print("waiting or failed")
}
}
connection?.start(queue: self.queue)
}
func sendPaket(_ packet:String)
{
let packetData = packet.data(using: .utf8)
self.connection?.send(content: packetData, completion: NWConnection.SendCompletion.contentProcessed(({ (error) in
if let err = error {
print("Sending error \(err)")
} else {
print("Sent successfully")
}
})))
}
func receive()
{
self.connection?.receiveMessage(completion:
{
(data, context, isComplete, error) in
print("Got it")
if let err = error {
print("Recieve error: \(err)")
}
if let rcvData = data,
let str = String(data:rcvData, encoding: .utf8) {
print("Received: \(str)")
}
self.receive()
})
}
}
Apple Documentation says:
receiveMessage(completion:)
Schedules a single receive completion handler for a complete message
completion :
A receive completion is invoked exactly once for a call to receive.
My Question is:
How can we call to receive ?
Assuming the receiveMessage(completion:) method is the Call to receive and also after receiving complete message calls the completion itself, What could be the problem if it doesn't get invoked?
Related code and more detail about my use case can be found here:
Swift: Receiving UDP packets from server on serial port monitor but not in ios app
With these assumptions :
We call to receive on the same connection that we send data to
UDP is used method for connection
Server is tested for swapping ports correctly, meaning it responds on the same IP,Port that data is sent to
My Setup:
13" MacBookPro Early 2015 with MacOS Catalina 10.15
Xcode Version 11.0 (11A420a)
Swift 5.1
target iOS 12+
And this is the result from Wire Shark:
I am working on NFCTagReaderSession the newly available in iOS 13, i struck to connect the session tag and send apdu commands for communication.
when i call the connect property it looks long time like 15 secs to connect by the time when it is connected(beeps sound) it shows an error message
NFCError Code=201 "Session timeout".
Every time tagReaderSession:didInvalidateWithError is calling while connecting the card and i could not send apdu commands.
the code which i tried to connect and send apudu command
var nfcSession: NFCTagReaderSession?
nfcSession = NFCTagReaderSession(pollingOption: [.iso14443], delegate: self, queue: DispatchQueue.main)
nfcSession?.alertMessage = "Hold your iPhone near an NFC."
nfcSession?.begin()
// MARK: - NFCTagReaderSessionDelegate
public func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {
print("tagReaderSessionDidBecomeActive")
}
public func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
print( "tagReaderSession:didInvalidateWithError - \(error)" )
}
public func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
let tag = tags.first!
var nfcTag7816: NFCISO7816Tag
switch tags.first! {
case let .iso7816(tag):
nfcTag7816 = tag
#unknown default :
session.invalidate(errorMessage: "Tag not valid.")
return
}
session.connect(to: tags.first!) { (error: Error?) in
if error != nil {
session.invalidate(errorMessage: "Connection error. Please try again.")
return
}
else {
let myAPDU = NFCISO7816APDU(instructionClass:0, instructionCode:0xB0, p1Parameter:0, p2Parameter:0, data: Data(), expectedResponseLength:16)
nfcTag7816.sendCommand(apdu: myAPDU) { (response: Data, sw1: UInt8, sw2: UInt8, error: Error?)
in
guard error != nil && !(sw1 == 0x90 && sw2 == 0) else {
session.invalidate(errorMessage: "Applicationfailure")
return
}
}
}
}
}
Error found when connected:
tagReaderSession:didInvalidateWithError - Error Domain=NFCError Code=201 "Session timeout" UserInfo={NSLocalizedDescription=Session timeout}
Please suggest me the exact reason of happening this one, and so that i can change in code according to the solutions
I am buidling an app using VOIP service. Now i integrated my app with CallKit in order to handle incoming call request.
When the app stays in foreground or background they are working fine when answered call. But the problem is that when the screen is locked and i tried to answer the call but unfortunately I can't hear audio for both side even i unlocked the screen.
How to solve this issue?
This is how incoming call reports:
func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)?) {
// 1.
print("This is UUID === ", uuid)
configureAudioSession()
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
update.hasVideo = hasVideo
provider.reportNewIncomingCall(with: uuid, update: update) { error in
if error == nil {
// 3.
self.configureAudioSession()
let call = CallKitCallInit(uuid: uuid, handle: handle)
self.callKitManager.add(call: call)
lastCallUUID = uuid
print("UUID === ", uuid)
} else {
}
// 4.
completion?(error as NSError?)
}
}
This is how i set audio
func configureAudioSession() {
let session = AVAudioSession.sharedInstance()
do{
try session.setCategory(AVAudioSessionCategoryPlayAndRecord)
} catch {
print("========== Error in setting category \(error.localizedDescription)")
}
do {
try session.setMode(AVAudioSessionModeVoiceChat)
} catch {
print("========= Error in setting mode \(error.localizedDescription)")
}
do {
try session.setPreferredSampleRate(44100.0)
} catch {
print("======== Error setting rate \(error.localizedDescription)")
}
do {
try session.setPreferredIOBufferDuration(0.005)
} catch {
print("======== Error IOBufferDuration \(error.localizedDescription)")
}
do {
try session.setActive(true)
} catch {
print("========== Error starting session \(error.localizedDescription)")
}
}
When i answered the call when screen is locked i could see that the error that it throwed in configureAudioSession() function.
Why it not able to set audio when the screen is locked?
I did by adding these lines of code
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
// 1.
guard let call = callKitManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
// 2.
configureAudioSession()
// 3.
call.answer()
// 4.
action.fulfill()
}
This is configureAudioSession
func configureAudioSession() {
let session = AVAudioSession.sharedInstance()
do{
try session.setCategory(AVAudioSession.Category.playAndRecord,
mode: AVAudioSession.Mode.voiceChat,
options: [])
} catch {
print("========== Error in setting category \(error.localizedDescription)")
}
do {
try session.setPreferredSampleRate(44100.0)
} catch {
print("======== Error setting rate \(error.localizedDescription)")
}
do {
try session.setPreferredIOBufferDuration(0.005)
} catch {
print("======== Error IOBufferDuration \(error.localizedDescription)")
}
do {
try session.setActive(true)
} catch {
print("========== Error starting session \(error.localizedDescription)")
}
}
You can try using AVAudioSessionModeVoiceChat instead of AVAudioSessionModeMoviePlayback mode while configuring audio session.
I'm using PromiseKit and Moya to send a long polling request in the method bellow:
func pullMessages() {
let service = ChatServices()
let request = service.pullMessages()
self.request = request
request.promise.then { [weak self] status -> Void in
// If chat successfully established with the agent
// navigate to chat screen or else try again. And if failure then show error message
guard let `self` = self else { return }
switch status {
case .waiting:
self.pullMessages()
case .messages(let messages):
for message: String in messages {
self.addMessage(text: message, sender: self.agentSender())
}
self.pullMessages()
case .chatEnded(let reason):
if reason == .agent {
self.endChat(with: "Agent ended chat session")
}
case .failure:
self.endChat(with: "Session lost")
}
}.catch { error in
// Show error
Log.warning(error.localizedDescription)
self.endChat(with: "Session lost")
}
}
And I'm using self.request to cancel the request once I'm leaving the view in viewWillDisapear.
But after I leave the view, and a new message arrives. the "cancelled" request returns the message. Any ideas why this is happening?