4

I get this error when I try to change variables in the closure:

A C function pointer cannot be formed from a closure that captures context

Is there a work around or is it possible to still change the variables within the closure?

My Code:

let callback: @convention(c) (readStream: CFWriteStream!, event: CFStreamEventType, data: UnsafeMutablePointer<Void>) -> Void = {
    (readStream, event, data) -> Void in
    switch event {
    case CFStreamEventType.ErrorOccurred:
        self.isError = true
        break
    case CFStreamEventType.EndEncountered:
        self.isRunLoop = false
        break
    case CFStreamEventType.HasBytesAvailable:
        break
    case CFStreamEventType.OpenCompleted:
        break
    case CFStreamEventType.CanAcceptBytes:
        self.bytesWritten = CFWriteStreamWrite(readStream, self.buffer, self.leftOverSize)
        break
    default:
        break
    }
}

let registeredEvents: CFOptionFlags =
    CFStreamEventType.CanAcceptBytes.rawValue |
    CFStreamEventType.HasBytesAvailable.rawValue |
    CFStreamEventType.ErrorOccurred.rawValue |
    CFStreamEventType.EndEncountered.rawValue |
    CFStreamEventType.None.rawValue

var context = CFStreamClientContext(version: CFIndex(0), info: nil, retain: nil, release: nil, copyDescription: nil)
let stream = CFWriteStreamCreateWithFTPURL(nil, uploadURL).takeUnretainedValue()

CFWriteStreamSetClient(stream, registeredEvents, callback, &context)
HovyTech
  • 299
  • 5
  • 19
  • 1
    The callback must be a global function or a closure which does not capture context. Similar issues: http://stackoverflow.com/questions/33260808/swift-proper-use-of-cfnotificationcenteraddobserver-w-callback or http://stackoverflow.com/questions/33551191/swift-pass-data-to-a-closure-that-captures-context. – Martin R Feb 11 '16 at 18:35
  • How would i then know if there was an error? – HovyTech Feb 11 '16 at 18:40
  • You have to convert `self` to a void pointer, pass that as user data to the callback, and in the callback convert it back to an object pointer. See also http://stackoverflow.com/questions/33294620/how-to-cast-self-to-unsafemutablepointervoid-type-in-swift. – Martin R Feb 11 '16 at 18:45

1 Answers1

7

I assume you want to use this callback to pass it as the third argument (clientCB) to CFWriteStreamSetClient.

Due to the fact that that parameter has the following type definition

typedef void (*CFWriteStreamClientCallBack) ( CFWriteStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo );

you can only use either a global function or a closure that doesn't capture any variables (like self) from the surrounding context.

What you can do in this case is to pass self to the info field of the CFStreamClientContext structure (the 4th parameter of CFWriteStreamSetClient), and use that info to reconstruct self within the closure:

let callback: @convention(c) (readStream: CFWriteStream!, event: CFStreamEventType, data: UnsafeMutablePointer<Void>) -> Void = {
    (readStream, event, data) -> Void in
    // assuming your class name is Client
    let client = unsafeBitCast(data.memory, Client.self)
    switch event {
    case CFStreamEventType.ErrorOccurred:
        client.isError = true
    case CFStreamEventType.EndEncountered:
        client.isRunLoop = false
    case CFStreamEventType.HasBytesAvailable:
        break
    case CFStreamEventType.OpenCompleted:
        break
    case CFStreamEventType.CanAcceptBytes:
        client.bytesWritten = CFWriteStreamWrite(readStream, client.buffer, client.leftOverSize)
    default:
        break
    }
}

var context = CFStreamClientContext(version: 0, info: unsafeBitCast(self, UnsafeMutablePointer<Void>.self), retain: nil, release: nil, copyDescription: nil)
CFWriteStreamSetClient(stream, 0, callback, &context)

Note. As with Objective-C, you need to make sure self is not destroyed before the stream is, otherwise you'll likely get into crashes if the stream receives new events.

dastrobu
  • 1,600
  • 1
  • 18
  • 32
Cristik
  • 30,989
  • 25
  • 91
  • 127
  • I keep on getting this error: `Command failed due to signal: Segmentation fault: 11`. – HovyTech Feb 11 '16 at 19:52
  • At what line? How are you calling `CFWriteStreamSetClient`? – Cristik Feb 11 '16 at 19:55
  • @HovyTech this seems to be a swift compiler issue, see [this radar](https://openradar.appspot.com/24541346). However you passed `nil` to the second argument for `CFStreamClientContext`, please see my updated answer of how the code should look like (assuming Apple fixes the compiler crash) – Cristik Feb 11 '16 at 20:10