0

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)")
    }
}
picciano
  • 22,341
  • 9
  • 69
  • 82
  • Probably because nothing was retaining the delegate, so ARC was releasing it from memory. By removing the `__weak` statement, the GCDAsyncSocket retains the delegate and it doesn't get released by ARC. – galrito Jul 18 '14 at 14:50
  • That sounds like what is happening, but it should be retained by Server.swift in the var socket. – picciano Jul 19 '14 at 23:28
  • I'm not familiar with GCDAsyncSocket, but I went to see the class's header and the delegate property is declared as weak, so it doesn't retain it. – galrito Jul 20 '14 at 09:09
  • are you going to make your framework opensource or post it online anywhere? – nsij22 Apr 29 '15 at 02:24
  • @nsij22 It's incomplete, but have at it. https://github.com/picciano/SwiftServe – picciano Apr 29 '15 at 02:32
  • @picciano thanks for posting it, I was hoping to read through your server app to figure out how it was setup but its above my knowledge so i ended up just asking a question. http://stackoverflow.com/questions/29934544/gcdasyncsocket-multiple-connections-error – nsij22 Apr 29 '15 at 05:12

1 Answers1

3

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.

scythe42
  • 483
  • 2
  • 6
  • I marked the answer correct, as removing __weak did indeed solve the problem. The part I still don't understand is that the instance of Server and it's `var socket` are not getting deinit'd and are still active. It's just that the socket is not getting it's delegate set. Even debugging line-by-line show the setDeletage happening, but never working and not producing an error. – picciano Jul 22 '14 at 15:31
  • var socket is a result of your call to GCCDAsyncSocket. But self() is a parameter. The result of GCDAsyncSocket is retained but not the parameters used during the call as they are not referenced by anything before or during the call. In Swift every parameter is a reference, nothing is passed by value and that is the root cause of the problem. And without a strong reference the result from self() is gone once the invoked method finishes. In pure Objective-C it works as self is passed by value and not reference. – scythe42 Jul 23 '14 at 02:00
  • With Xcode 6.1.1 I was able to resolve this issue by making my Swift class a subclass of NSObject (no need to do any retaining). – Adam Preble Jan 12 '15 at 05:43