Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Closure Capture Context Swift

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)
like image 613
HovyTech Avatar asked Mar 14 '23 07:03

HovyTech


1 Answers

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.

like image 171
Cristik Avatar answered Mar 27 '23 19:03

Cristik