zic10
zic10

Reputation: 2330

self reference swift closure

I have a lazy property in swift that has a callback that looks like this.

lazy var apiClient: MyApiClient =
    {

        var apiClient : MyApiClient = MyApiClient()

        apiClient.detailSearchFinishedCallBack = {
            (detailModel : DetailModel!) in

        }

        return apiClient
    }()

I have another lazy load property that I would like to access inside the closure that looks like this:

  lazy var loadingView : LoadingView =
    {
        var loadingView : LoadingView = LoadingView()
        loadingView.frame = CGRectMake(0, 0, 200, 200)
        loadingView.center = self.view.center

        return loadingView
    }()

However, I'm unable to reference the loading view inside the closure. In objective c, this would look something like this.

-(LoadingView *)loadingView
{
    if(!_loadingView)
    {
        _loadingView = [LoadingView new];
        _loadingView.frame = CGRectMake(0, 0, 200, 200);
        _loadingView.center = self.view.center;
    }
    return _loadingView;
}

-(MyApiClient *)apiClient
{
    if(!_apiClient)
    {
        _apiClient = [MyApiClient new];

        __weak FeedViewController *_self = self;

        self.apiClient.detailSearchFinishedCallBack = ^(DetailModel  *detailModel)
        {
              [_self.loadingView stopAnimating];
        };
    }

    return _apiClient;
}

Would someone be kind enough to show me the equivalent of this in Swift?

UPDATE:

  lazy var apiClient: MyApiClient = {
            let apiClient: MyApiClient = MyApiClient()
            apiClient.detailSearchFinishedCallBack = { [weak self] (detailModel: DetailModel!) in

                println(self?.loadingView.frame)

                return
            }
            return apiClient
            }()

So I went ahead and tried implementing the proposed solution but I get a compilation error. Specifically I'm running into this error:

Command /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc failed with exit code 1

I'm not sure if this is something to do with my swift configuration in my project or not but I'm using the bridging header to import my objective c header files to be exposed to Swift. I can't think of anything else that could be causing this but any help would be appreciated to resolve this.

Upvotes: 4

Views: 3029

Answers (2)

Rob
Rob

Reputation: 437632

The syntax would be:

lazy var apiClient: MyApiClient = {
    let apiClient: MyApiClient = MyApiClient()
    apiClient.detailSearchFinishedCallBack = { [weak self] detailModel in
        self?.loadingView?.stopAnimating()
    }
    return apiClient
}()

At the time I originally answered this question, the Swift compiler had problem interpreting the above syntax, so I proposed the following ugly workaround outlined below. But you can now use the above simple, logical syntax.


Original answer:

You are using a closure to provide a default value for a property. But in the Setting a Default Property Value with a Closure or Function section of the Initialization Chapter of The Swift Programming Language says:

Note: If you use a closure to initialize a property, remember that the rest of the instance has not yet been initialized at the point that the closure is executed. This means that you cannot access any other property values from within your closure, even if those properties have default values. You also cannot use the implicit self property, or call any of the instance’s methods.

Having said that, one could make an argument that the compiler should make allowances for lazy variables, but it doesn't.


You say you want an implementation of this Objective-C code:

-(MyApiClient *)apiClient
{
    if(!_apiClient)
    {
        _apiClient = [MyApiClient new];

        __weak FeedViewController *_self = self;

        self.apiClient.detailSearchFinishedCallBack = ^(DetailModel  *detailModel)
        {
              [_self.loadingView stopAnimating];
        };
    }

    return _apiClient;
}

Perhaps you could consider a more literal Swift conversion, using private stored property which is handled by a computed property:

private var _apiClient: MyApiClient!

var apiClient: MyApiClient {
    if _apiClient == nil {
        _apiClient = MyApiClient()

        _apiClient.detailSearchFinishedCallBack = { [weak self] detailModel in
            if let loadingView = self?.loadingView {
                loadingView.stopAnimating()
            }
        }
    }

    return _apiClient
}

Note, this is not thread safe (but your Objective-C rendition isn't either), but it accomplishes what you asked for.

As an aside there are other approaches to solve this:

  • You could move the initialization code from these closures and into some other logical shared initialization routine, e.g. viewDidLoad or what have you. This way, the order in which things are initialized is explicit and unambiguous.

  • You could further decouple the API from the UI by having the API post notifications that the view could observe.

Upvotes: 3

rintaro
rintaro

Reputation: 51911

I'm not sure what you unable to do.

Actually, Xcode has some problem about using self in lazy var x:Type = { ... }() pattern. For example, it doesn't auto-complete the properties or methods, but it works.

This code compiles and works in Xcode 6.3 Beta2:

class LoadingView: UIView {}
class DetailModel {}

class MyApiClient {
    var detailSearchFinishedCallBack: ((DetailModel!) -> Void)?
}

class MyViewController1: UIViewController {

    override func viewDidLoad() {
        self.apiClient.detailSearchFinishedCallBack?(DetailModel())
    }

    lazy var apiClient: MyApiClient = {
        let apiClient: MyApiClient = MyApiClient()
        apiClient.detailSearchFinishedCallBack = { [weak self] (detailModel: DetailModel!) in

            println(self?.loadingView.frame)

            return
        }
        return apiClient
    }()

    lazy var loadingView: LoadingView = {
        let loadingView: LoadingView = LoadingView(frame: CGRectMake(0, 0, 200, 200))
        loadingView.center = self.view.center
        return loadingView
    }()
}

As for Xcode 6.1.1, for some reasons, the compiler crashes. It seems, we have to reference the self at least once outside of the closure.

        self // <- reference `self` in anyway

        apiClient.detailSearchFinishedCallBack = { [weak self] (detailModel: DetailModel!) in
            println(_self?.loadingView.frame)
            return
        }

Finally, if you feel something unsafe, you can do straight-forward equivalent to Objective-C:

class MyViewController2: UIViewController {

    private var _apiClient: MyApiClient? = nil
    var apiClient: MyApiClient {
        if _apiClient == nil {
            let apiClient : MyApiClient = MyApiClient()
            apiClient.detailSearchFinishedCallBack = { [weak self] (detailModel : DetailModel!) in
                println(self?.loadingView.frame)
                return
            }
            _apiClient = apiClient
        }
        return _apiClient!
    }

    private var _loadingView: LoadingView? = nil
    var loadingView : LoadingView {
        if _loadingView == nil {
            let loadingView : LoadingView = LoadingView(frame: CGRectMake(0, 0, 200, 200))
            loadingView.center = self.view.center
            _loadingView = loadingView
        }
        return _loadingView!
    }
}

Upvotes: 1

Related Questions