Theo Verriet
Theo Verriet

Reputation: 13

No response when calling NSURLConnection in mixed Swift Objective-C environment

I've created the Class StartConnection to handle my NSURL requests. It gets called from my AppDelegate twice and that works well. It's called by one other class as well and that also works well. This is the implementation of StartConnection:

#import "StartConnection.h"
#import "BlaBlaBlog-swift.h"

@implementation StartConnection
{
    BOOL startedForBlog;
}

- (void)getRssFileWithUrl: (NSString*)rssUrlString forBlog:(BOOL)forBlog
{
    startedForBlog = forBlog;
    NSURL *url = [NSURL URLWithString:rssUrlString];
    NSURLRequest *rssRequest = [NSURLRequest requestWithURL:url];
    NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:rssRequest delegate:self];
    [connection start];
}

-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    dataSize = [response expectedContentLength];
}


- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    if (receivedData==nil )
    {
        receivedData = [[NSMutableData alloc]init];
    }
    // Append the new data to the instance variable you declared
    [receivedData appendData:data];
}

-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    self.receivedDataComplete = receivedData;
    if (startedForBlog){
        [self.delegate performSelector: @selector(receiveDataCompleted)];
    }
    else
        [self.delegate performSelector: @selector(receivePodCastDataCompleted)];
}

To get some hands on experience with SWIFT I've added an experimental SWIFT class to my code that also uses StartConnection.h. In the debugger I can see an instance of StartConnection being created and the getFileWithUrl methode seems to be kicked of normally. But that's all that happens. None of the delegate methods is called.

This is SWIFT class:

import UIKit

class CheckActuality: NSObject, GetReceivedDataProtocol, WordPressParserDelegate {

    var retrievePostData = StartConnection()
    var parseCompleted: Bool=false
    var result: Bool = true
    lazy var wpParser = WordPressParser()
    lazy var defaults = NSUserDefaults.standardUserDefaults()


    func isActual () -> Bool {
        var url = "http://blablablog.nl/new_api.php?function=get_recent_posts&count=1"
        self.retrievePostData.delegate=self
        self.retrievePostData.getRssFileWithUrl(url, forBlog:true)
        while !self.parseCompleted
        {
//           wait till wpparser has completed
        }
        if self.wpParser.arrayWithPostDictionaries.count == 1
//            Some info has been retrieved
        {
            var posts: NSArray = wpParser.arrayWithPostDictionaries
            var post: NSDictionary = posts.objectAtIndex(0) as NSDictionary
            var latestPostUrl: String = post.objectForKey("postUrl") as String
            var currentLatestPostUrl = defaults.stringForKey("ttt")

            if latestPostUrl != currentLatestPostUrl {
              result = false
            }
            else {
                result = true
            }
        }
        return result
    }


    func receiveDataCompleted () {
        if self.retrievePostData.receivedDataComplete != nil
        {
            self.wpParser.delegate=self
            self.wpParser.parseData(retrievePostData.receivedDataComplete)
        }
        else
        {
//            warning no internet
        }

    }

    func wpParseCompleted () {
        self.parseCompleted=true
    }

}

And to be complete, the call in my AppDelegate look like this:

//
//    retrieving PostData. Create a connection, set delegate to self en start with created url
//
    retrievePostData = [[StartConnection alloc]init];
    retrievePostData.delegate = self;
    NSString *url = [[wordPressUrl stringByAppendingString:apiString] stringByAppendingString:pageCountString];
    [retrievePostData getRssFileWithUrl:url forBlog:(BOOL)true];
//
//    retrieving PodcastData. Create a connection, set delegate to self en start with created url
//
    retrievePodCastData = [[StartConnection alloc]init];
    retrievePodCastData.delegate = self;
    [retrievePodCastData getRssFileWithUrl:podcastUrl forBlog:(BOOL)false];

I'm breaking my head for almost a day. Hope some of you far more experienced guys can help this starter out.

Upvotes: 1

Views: 434

Answers (1)

Rob
Rob

Reputation: 438257

The while !parseCompleted loop is blocking the main thread until the download and parsing is done. But the processing of the received data happens on the main thread, too, so if that thread is blocked, your app will be deadlocked.

I would eliminate that while loop and put all of the post processing inside your receivedDataComplete method.


By the way, implicit in this change is the fact that isActual must be an asynchronous method. Thus, rather than returning a Bool value, it should be a Void return type but you should employ the completionHandler pattern (and I'd change it to also return an error object, too):

class CheckActuality: NSObject, GetReceivedDataProtocol, WordPressParserDelegate {

    let errorDomain = "com.domain.app.CheckActuality"

    lazy var retrievePostData = StartConnection()
    lazy var wpParser = WordPressParser()
    lazy var defaults = NSUserDefaults.standardUserDefaults()

    var completionHandler: ((latestPost: Bool!, error: NSError?)->())!

    func isActual(completionHandler: (latestPost: Bool!, error: NSError?)->()) {
        // save the completionHandler, which will be called by `receiveDataCompleted` or `wpParseCompleted`

        self.completionHandler = completionHandler

        // now perform query

        let url = "http://blablablog.nl/new_api.php?function=get_recent_posts&count=1"  // as an aside, use `let` here
        retrievePostData.delegate = self                                                // also note that use of `self` is redundant here
        retrievePostData.getRssFileWithUrl(url, forBlog:true)
    }    

    func receiveDataCompleted () {
        if retrievePostData.receivedDataComplete != nil {
            wpParser.delegate = self
            wpParser.parseData(retrievePostData.receivedDataComplete)
        } else {
            // frankly, I'd rather see you change this `receiveDataCompleted` return the `NSError` from the connection, but in the absence of that, let's send our own error
            let error = NSError(domain: errorDomain, code: 1, userInfo: nil)
            completionHandler(latestPost: nil, error: error)
        }
    }

    func wpParseCompleted () {
        if wpParser.arrayWithPostDictionaries.count == 1 {                     // Some info has been retrieved
            let posts: NSArray = wpParser.arrayWithPostDictionaries
            let post: NSDictionary = posts.objectAtIndex(0) as NSDictionary
            let latestPost: String = post.objectForKey("postUrl") as String
            let currentlatestPost = defaults.stringForKey("ttt")

            completionHandler(latestPost: (latestPost != currentlatestPost), error: nil)
        }

        // again, I'd rather see you return a meaningful error returned by the WordPressParser, but I'll make up an error object for now
        let error = NSError(domain: errorDomain, code: 2, userInfo: nil)
        completionHandler(latestPost: nil, error: error)
    }

}

Now, I don't know if latestPost is the appropriate name for the value you were trying to return, so change that to whatever makes sense for your routine. Also, the name isActual doesn't really make sense, but I'll let you change that to whatever you want.

Anyway, when you use it, you'd use the trailing closure syntax to specify the completionHandler block that will be performed asynchronously:

let checkActuality = CheckActuality()

func someFunc() {
    checkActuality.isActual() { latestPost, error in 
        if error != nil {
            // do whatever error handling you want

            println(error) 
        } else if latestPost {
            // yes, latest post
        } else {
            // nope
        }
    }

    // note, do not try to check `latestPost` here because the 
    // above closure runs asynchronously
}

Needless to say, this is a completionHandler pattern, but looking at your code, you seem to favor delegate patterns. If you wanted to implement this using the delegate pattern, you can. But the idea is the same: This isActual method (whatever you end up renaming it to) runs asynchronously, so you have to inform the caller when it is complete.

Upvotes: 1

Related Questions