#ios #swift #performance #udp
#iOS #swift #Производительность #udp
Вопрос:
Я впервые пишу Swift и использую Xcode, поэтому, пожалуйста, потерпите меня.
Я пытаюсь написать приложение для iPhone, которое принимает значения из гироскопа / магнитометра устройства и отправляет их в UDP-пакетах на определенный IP-адрес и порт с определенной частотой (1-100 Гц). Таким образом, я могу привязать свой телефон к голове и использовать его как «бесплатную» альтернативу TrackIR.
Opentrack, программное обеспечение, которое я использую для получения UDP-пакетов, ожидает 48 байт. 6 8-байтовые удвоения в таком порядке: X, Y, Z, рыскание, шаг, крен. Поскольку я не могу измерить положение телефона относительно дисплея, первые 3 удвоения будут равны 0. В то время как рыскание, тангаж и крен будут определять положение телефона в градусах.
Вот урезанная версия моего кода на данный момент (полный файл здесь):
import UIKit
import CoreMotion
import Network
class ViewController: UIViewController {
var enabled = false
var rate: Int = 100
var motion = CMMotionManager()
var connection: NWConnection?
@IBOutlet weak var activeState: UILabel!
@IBOutlet weak var ipAddress: UITextField!
@IBOutlet weak var port: UITextField!
@IBOutlet weak var rateDisplay: UILabel!
@IBOutlet weak var rateSlider: UISlider!
func radToDegData(value: Double) -> Data {
return withUnsafeBytes(of: value*180/Double.pi) { Data($0) }
}
func startStream(hostUDP: NWEndpoint.Host, portUDP: NWEndpoint.Port) {
motion.deviceMotionUpdateInterval = 1 / Double(rate)
motion.startDeviceMotionUpdates(to: OperationQueue.current!){ (data, error) in
if let trueData = data{
let UDPmessage = self.radToDegData(value: 0) self.radToDegData(value: 0) self.radToDegData(value: 0) self.radToDegData(value: trueData.attitude.yaw) self.radToDegData(value: trueData.attitude.pitch) self.radToDegData(value: trueData.attitude.roll)
self.connection = NWConnection(host: hostUDP, port: portUDP, using: .udp)
self.sendUDP(UDPmessage)
self.connection?.start(queue: .global())
}
}
}
func stopStream() {
motion.stopDeviceMotionUpdates()
}
func sendUDP(_ content: Data) {
self.connection?.send(content: content, completion: NWConnection.SendCompletion.contentProcessed(({ (NWError) in
if (NWError == nil) {
//print("Data was sent to UDP")
} else {
//print("ERROR! Error when data (Type: Data) sending. NWError: n (NWError!)")
}
})))
}
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func enableSwitch(_ sender: Any) {
enabled.toggle()
if enabled {
startStream(hostUDP: .init(ipAddress.text!), portUDP: NWEndpoint.Port(rawValue: UInt16(port.text ?? "4242")!) ?? NWEndpoint.Port.any)
} else {
stopStream()
}
}
}
У меня 2 проблемы:
- Запустив это на физическом iPhone SE 2 с частотой 100 Гц, Xcode сообщает о 100-101% загрузке процессора. Я действительно не могу себе представить, что это приложение настолько требовательно даже к частоте 100 Гц. Еще одна странность заключается в том, что, хотя я ожидаю, что 100% использование будет означать, что скорость будет снижена, я все еще получаю ~ 100 пакетов в секунду в скрипте Python на моем ноутбуке, который я написал для тестирования.
- Независимо от скорости, отправляется только 512 пакетов. После этого на консоль выводится 2 сообщения об ошибках для каждой попытки:
2020-12-13 22:03:44.765054 0100 UDPHeadTrack[5571:3061403] [] nw_path_evaluator_create_flow_inner NECP_CLIENT_ACTION_ADD_FLOW 31FA4B2D-1E8A-4D16-A1A6-D023471B59C0 [28: No space left on device]
2020-12-13 22:03:44.765089 0100 UDPHeadTrack[5571:3061403] [connection] nw_endpoint_flow_setup_channel [C513 192.168.0.1:4242 in_progress channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, ipv6, dns)] failed to request add nexus flow
Поскольку это мой первый проект Swift, значительная его часть — это скопированные примеры / документация, которые, как я предполагаю, являются причиной возникновения этих проблем. Любая помощь будет оценена.
Ответ №1:
Вы создаете новый NWConnection
каждый раз, когда получаете обновление motion. Ваше приложение может иметь только ограниченное количество открытых сетевых подключений (в данном случае 512). После превышения квоты вы получаете сообщение об ошибке.
Простая реструктуризация — просто использовать существующий NWConnection
, если он существует:
func startStream(hostUDP: NWEndpoint.Host, portUDP: NWEndpoint.Port) {
motion.deviceMotionUpdateInterval = 1 / Double(rate)
motion.startDeviceMotionUpdates(to: OperationQueue.current!){ (data, error) in
guard let trueData = data else {
return
}
if self.connection == nil {
self.connection = NWConnection(host: hostUDP, port: portUDP, using: .udp)
self.connection?.start(queue: .global())
}
let udpMessage = self.radToDegData(value: 0) self.radToDegData(value: 0) self.radToDegData(value: 0) self.radToDegData(value: trueData.attitude.yaw) self.radToDegData(value: trueData.attitude.pitch) self.radToDegData(value: trueData.attitude.roll)
self.sendUDP(udpMessage)
}
}
func stopStream() {
motion.stopDeviceMotionUpdates()
self.connection?.cancel()
self.connection = nil
}
func sendUDP(_ content: Data) {
guard let connection = self.connection else {
return
}
connection.send(content: content, completion: NWConnection.SendCompletion.contentProcessed(({ (error) in
if let error = error {
print("ERROR! Error when data (Type: Data) sending. NWError: n (error)")
} else {
print("Data was sent to UDP")
}
})))
}
Комментарии:
1. Спасибо за объяснение. Теперь загрузка процессора составляет 6% при частоте 100 Гц. Есть ли способ избавиться от
completion
аргументаconnection.send()
функции? Или печать этой строки 100 раз в секунду не повлияет на производительность, когда она не находится в сеансе отладки?2. Вам не нужно ничего делать в обработчике завершения, если вы этого не хотите. Я бы не подумал, что отпечатки будут потреблять много ЦП, если не отлаживать
3. Столкнулся с небольшой проблемой. При запуске потока, затем остановке, затем изменении ip-адреса, затем повторном запуске он по-прежнему использует первый адрес. Хорошим решением было бы каждый раз создавать новое соединение без проверки
if self.connection == nil
. И добавитьself.connection?.cancel()
внизуstopStream()
функции?4. Я имею в виду вот так . Кажется, это работает, но я не уверен, правильно ли я закрываю соединение, т. Е. Если я получу ошибки после включения / выключения потока 512 раз.
5. В
stopStream
вы должны отменить соединение, а затем установить его наnil
. Таким образом, при запуске потока будет создано новое соединение.