Gebührenfreier Bridging- und Pointer-Zugriff in Swift
-
21-12-2019 - |
Frage
Ich portiere eine App von Objective-C nach Swift und muss die folgende Methode verwenden:
CFStreamCreatePairWithSocketToHost(alloc: CFAllocator!, host: CFString!, port: UInt32, \
readStream: CMutablePointer<Unmanaged<CFReadStream>?>, \
writeStream: CMutablePointer<Unmanaged<CFWriteStream>?>)
Die alte Logik sieht so aus (über die sich mehrere Websites einig zu sein scheinen):
CFReadStreamRef readStream = NULL;
CFWriteStreamRef writeStream = NULL;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)(host), port, \
&readStream, &writeStream);
NSInputStream inputStream = (__bridge_transfer NSInputStream *)readStream;
NSOutputStream outputStream = (__bridge_transfer NSOutputStream *)writeStream;
Was dank der gebührenfreien Überbrückung problemlos funktioniert.Allerdings existiert ARC im „Swift-Space“ nicht und das Typsystem hat sich geändert.
Wie verwandle ich meine Streams in Instanzen von
CMutablePointer<Unmanaged<CFReadStream>?>, and
CMutablePointer<Unmanaged<CFWriteStream>?>
Und wandeln Sie sie dann wieder in um NSStream
Unterklassen nach dem CFStreamCreatePairWithSocketToHost
Anruf?
Lösung
Ich habe es zum Laufen gebracht, hier ist mein Code:Stellen Sie sicher, dass Sie irgendwo eine Referenz der Verbindungsklasse aufbewahren :-)
class Connection : NSObject, NSStreamDelegate {
let serverAddress: CFString = "127.0.0.1"
let serverPort: UInt32 = 8443
private var inputStream: NSInputStream!
private var outputStream: NSOutputStream!
func connect() {
println("connecting...")
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(nil, self.serverAddress, self.serverPort, &readStream, &writeStream)
// Documentation suggests readStream and writeStream can be assumed to
// be non-nil. If you believe otherwise, you can test if either is nil
// and implement whatever error-handling you wish.
self.inputStream = readStream!.takeRetainedValue()
self.outputStream = writeStream!.takeRetainedValue()
self.inputStream.delegate = self
self.outputStream.delegate = self
self.inputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
self.outputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
self.inputStream.open()
self.outputStream.open()
}
func stream(stream: NSStream, handleEvent eventCode: NSStreamEvent) {
println("stream event")
}
}
Andere Tipps
Ich konnte die Beispiele, die andere in diesem Thread bereitgestellt haben, nicht zum Laufen bringen.Sicher, sie haben kompiliert, aber sie sind abgestürzt, sobald die Verbindung geöffnet war.
Allerdings ist mir in den WWDC 2014-Diskussionen (und den Versionshinweisen zu iOS 8) aufgefallen, dass es eine neue Methode zum Initialisieren eines NSStreams zum Erstellen eines gebundenen Paars von Ein-/Aus-Streams gibt.
Siehe unten:
var inputStream: NSInputStream?
var outputStream: NSOutputStream?
NSStream.getStreamsToHostWithName("localhost", port: 1234, inputStream: &inputStream, outputStream: &outputStream)
Dadurch entfällt die Notwendigkeit des umständlichen CFStreamCreatePairWithSocketToHost-Aufrufs sowie der Bedarf an nicht verwalteten Ressourcen.
Ich habe herausgefunden, wie es geht.Ein paar wichtige Hinweise:
CMutablePointers
wird automatisch erstellt, wenn Sie den &-Operator verwenden.- Sie können bei der
T
in einem (nUnmanaged<T>
mit.getUnretainedValue()
UndgetRetainedValue()
(Scheint.getUnretainedValue()
ist analog zu__bridge_transfer
) - Optionale Optionen werden automatisch initialisiert
nil
. - Wenn ein optionales ist
nil
es wird in a übersetztfalse
Zustand.
Bisher habe ich (ungetestet):
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host, port, \
&readStream, &writeStream)
if (readStream && writeStream) {
inputStream = readStream!.takeUnretainedValue();
outputStream = writeStream!.takeUnretainedValue();
}
Ich verwende die Funktion getStreamsToHostWithName der NSStream-Klasse.Es ist einfacher und einfacher als CFStreamCreatePairWithSocketToHost
func initNetworkCommunication() {
print("connecting...")
let serverAddress = "gzoa.vps.infomaniak.com"
let serverPort = 1234
NSStream.getStreamsToHostWithName(serverAddress, port: serverPort, inputStream: &inputStream, outputStream: &outputStream)
self.inputStream!.delegate = self
self.outputStream!.delegate = self
self.inputStream!.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
self.outputStream!.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
self.inputStream!.open()
self.outputStream!.open()
}
Swift 3-Version des CF- und NS-Codes.Bei mir funktioniert beides.
CF:
class Connection: NSObject, StreamDelegate {
private var inputStream: InputStream!
private var outputStream: OutputStream!
var connected = false
func connect(host: String, port: UInt32) {
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(nil, host as CFString, port, &readStream, &writeStream)
self.inputStream = readStream!.takeRetainedValue()
self.outputStream = writeStream!.takeRetainedValue()
if let inputStream = inputStream, let outputStream = outputStream {
inputStream.delegate = self
outputStream.delegate = self
inputStream.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
outputStream.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
inputStream.open()
outputStream.open()
connected = true
}
}
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
print("stream event, \(eventCode)")
}
}
NS:
class NSConnection: NSObject, StreamDelegate {
private var inputStream: InputStream?
private var outputStream: OutputStream?
var connected = false
func connect(host: String, port: Int) {
Stream.getStreamsToHost(withName: host, port: port, inputStream: &inputStream, outputStream: &outputStream)
if let inputStream = inputStream, let outputStream = outputStream {
inputStream.delegate = self
outputStream.delegate = self
inputStream.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
outputStream.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
inputStream.open()
outputStream.open()
}
}
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
print("stream event, \(eventCode)")
}
}