Delegate not getting set

2019-09-06 20:37发布

问题:

I am building a framework in Swift that uses GCDAsyncSocket written in Objective-C.

The error I am receiving is:

Couldn't start socket: Error Domain=GCDAsyncSocketErrorDomain Code=1 "Attempting to accept without a delegate. Set a delegate first." UserInfo=0x61000026b940 {NSLocalizedDescription=Attempting to accept without a delegate. Set a delegate first.}

I have tried setting the delegate in the init method (shown below) and also tried setting it using the setDelegate method after initialization.

While debugging, I have verified that the setDelegate code is getting called and that the value passed in (self) actually contains a reference.

UPDATE: when I modify the GCDAsyncSocket.m to remove the __weak keyword from the declaration of the delegate, it works, but I still so not understand why I should have to do that. The line was: __weak id delegate, changed to id delegate

Here is the class causing the problem:

class Server: GCDAsyncSocketDelegate
{
    let boundHost:String?
    let port:UInt16
    var socket:GCDAsyncSocket?

    init(boundHost: String?, port: UInt16)
    {
        if boundHost {
            self.boundHost = boundHost!
        }
        self.port = port

        println("Server created with host: \(self.boundHost) and port: \(self.port).")
    }

    convenience init(port:UInt16) {
        self.init(boundHost: nil, port: port)
    }

    func startServer() -> Bool
    {
        if !socket
        {
            socket = GCDAsyncSocket(delegate: self, delegateQueue: dispatch_get_main_queue())
        }

        var error:NSError?
        if !socket!.acceptOnInterface(boundHost, port: port, error: &error)
        {
            println("Couldn't start socket: \(error)")
            return false;
        }
        else
        {
            println("Listening on \(port).")
            return true
        }
    }

    func stopServer() -> Bool
    {
        if socket
        {
            socket!.disconnect()
            return true
        }
        return false
    }

    func socket(sock:GCDAsyncSocket!, didAcceptNewSocket newSocket:GCDAsyncSocket!)
    {
        println("New socket received: \(newSocket)")
    }
}

回答1:

This is an issue with GCDAsyncSocket and delegates inside Swift code (can apply to other delegate scenarios as well).

What you are experiencing here is:

  1. Swift creates a reference to self for the call of GCDAsyncSocket()

  2. Objective-C does something with it, but treats it as __weak basically not creating an additional reference Swift needs to care about.

  3. Swift finishes the call to GCDAsyncSocket() and ARC removes the reference to self as there is no other reference. Swift does not know that Objective-C still needs it.

  4. And as the delegate is declared as __weak it's gone in the Objective-C part as well.

  5. GCDAsyncSocket has no delegate anymore and complains about it.

Solutions:

  1. Add NSObject as a super class to your Swift class and then retain "self" inside your class to some global class variable inside your init(). Then ARC sees the reference and everything is fine. This will create a reference on the Swift side for the lifetime of the Object and __weak in Objective-C will work as intended.

  2. Or remove the __weak from GCDAsyncSocket and add a socket.Delegate(nil) in a Deinit() method of your Swift class. This will create a reference on the Objective-C side preventing ARC on the Swift side to dealloc it until you do it manually.

GCDAsyncSocket tries not to leak memory. In a pure Objective-C project this works fine as intended, but when using Objective-C from Swift you need to retain delegates on the Swift side if they are declares as __weak in Objective-C.