Reputation: 13
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
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