I've googled a lot for this, but get no clue for it.
First of all, i'm not using ARC.
let's say i am calling a asynchronous function, and passing a pointer A to it, initialially i thought, okay, let's pass a autoreleased pointer A to it, the async function will release A after it finished its operation. but seems it won't work.
NSURLRequest *request = [[[NSURLRequest requestWithURL:[NSURL URLWithString:#"someurl"]] autorelease];
[webView loadRequest:request];
Then there's a EXC_BAD_ACCESS error coming in, if i remove the autorelease, then it goes fine.
anyone knows about this?
Please read the basic memory management rules again.
You didn't create the NSURLRequest using a method containing the words “alloc”, “new”, “copy”, or “mutableCopy”, so you don't own it, so you shouldn't release it.
Also, you are not "calling an asynchronous function". When you call [webView loadRequest:], the method call happens immediately and synchronously. That method starts some asynchronous work behind the scenes, which completes later on -- but that doesn't affect the way that you call the method in the first place, or the memory management for its arguments.
Related
excuse me for not knowing this, but I would like to know why there is a delay when I try to establish a connection to the database.
I am basically pulling data from a database to display the info back onto a UITableView but there seems to be a delay in the connection establishment.
- (void)viewDidLoad
{
[super viewDidLoad];
[self fetchFromDatabase];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
[self fetchFromDatabase];
NSLog(#"FOUR");
}
- (void)fetchFromDatabase
{
// The URL of the database
NSURL *url = [[NSURL alloc] initWithString:SDTET_DATABASE_SCHEDULE];
// Establish the connection to the database
[NSURLConnection sendAsynchronousRequest:[ [NSURLRequest alloc] initWithURL:url] queue:[ [NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSLog(#"ONE !");
} ];
}
However, the output looks like so:
FOUR
FOUR
ONE !
Why doesn't ONE ! get printed out first?
How can I ensure the connection occurs before the numberOfSections is called, if possible?
Thanks
ONE gets printed last because you are calling an asynchronous method.
You can follow the flow of the program (which method calls which in what order). If we let our imagination stretch a bit, we could say that when a method is synchronous, it basically "says" to the next line of code to wait for it (the method) to finish.
However, if a method is asynchronous, it say "don't wait for me". The URL request method you are using is asynchronous. That means it starts to do its job but nobody waits, the execution of code continues in the next line immediately.
There is a piece in the puzzle that will clarify this more. The request method needs to be fed a completion block as a parameter. Explaining blocks is out of scope here, but you need to understand what block is to fully grasp the solution to your problem.
Suffice to say, once this asynchronous method finishes its job (which can be a long time and this is one of the reasons for asynchronous approach really), the block is invoked and executed.
So to answer your last question, you need to trigger a refresh on your table view, which you achieve by calling
[self.tableView reloadData]; inside the block. There is a nice logic here, since you know the block is executed ONLY after the asynchronous method concludes its work, you will refresh your table view knowing you have the updated data from db.
It's because it is an asynchronous request (meaning it doesn't necessarily happen immediately). It's a huge principle of computer science and it a much bigger concept than just Objective-C or iOS development.
In your case, you can get rid of the fetch being called in numberOfSectionsInTableView: and you can just call [self.tableView reloadData] in your completionHandler.
There is a delay because network access is slow. Sometimes it's REALLY slow.
Sometimes you can get a response in less than a second, and other times it can take the better part of a minute to get your response.
You should always write your network code to be asynchronous, which means that you submit the request with a method that returns immediately. Your program continues to run, and then you get notified once the response has been received.
That's what is going on with your code.
You don't want to send the request to load your data in numberOfSectionsInTableView. You should send the network request as early as possible, preferably before the view controller that will display the results is displayed.
However, it's pretty common to display a table view before you have the data to fill it. You might show an empty table view with a message that the data is loading, or with a progress dialog. In other cases you might know how many cells to display, and have the text data but not the images. In that case you might populate your cells with placeholder images. Once the data loads you can update the display.
In your case you should write your numberOfSectionsInTableView and numberOfRowsInSection: methods to return zero, and then make the completion method of your network call tell the table view to reload itself.
I need to pass some extra informations along with UIWebView loadRequest: so that it reaches my implementation of NSURLProtocol. The information cannot be bound to NSURLRequest because the information must be retained with NSURLRequest mainDocumentURL as well. So i subclassed NSURL and constructed NSURLRequest with it. I already knew that the NSURLRequest which reaches NSURLProtocol startLoading is NOT the instance i have fed to UIWebView loadRequest, so i implemented NSURL copyWithZone too, naively expecting that URL loading system will use it.
Now, NSURLProtocol canInitWithRequest is called not once as one would reasonably expect, but at least 4 times before startLoading. First 2 times of that, the incoming NSURLRequest still contains my custom NSURL implementation. Then an unfortunate internal code called CFURLCopyAbsoluteURL asks for the absoluteURL of my custom NSURL and the next canInitWithRequest (and subsequent startLoading) already gets a completely new NSURLRequest with fresh NSURL in it. copyWithZone is never called and my subclassed NSURL is lost.
Before i give up and implement an inferior and fragile solution with attaching stuff directly to the URL string, i would like to ask the wizards of higher level, whether they see a way how to catch that initial blink on the NSURLProtocol radar or how to trick CFURLCopyAbsoluteURL into carrying my custom instance. I have tried to hack NSURL absoluteURL by returning again a new instance of my custom NSURL class, but it didn't help. I have seen some promise in NSURLProtocol setProperty functionality, but now it appears pretty useless. URL loading system creates new instances of everything happily and NSURLRequest arrived in NSURLProtocol seems to be the same as the one entered into UIWebView only accidentally.
UPDATE: ok i wanted to keep the post as short as possible, but the even the first reply is asking for technical background, so here we go: i've got multiple UIWebViews in app. These views may run requests concurrently and absolutely can run requests for the same URL. It's like tabs in desktop browser. But i need to distinguish which UIWebView was the origin of each particular NSURLRequest arriving to the NSURLProtocol. I need a context being carried with each URL request. I can't simply map the URLs to data, because multiple UIWebViews may be loading the same URL at any moment.
UPDATE 2: Attaching the context information to NSURL is preferred and, as far as my understanding goes, the only usable. The issue is that requests for resources referenced inside page (images etc.) do not go through UIWebViewDelegate at all and end up in NSURLProtocol directly. I don't have a chance to touch, inspect or modify such requests anywhere prior to NSURLProtocol. The only contextual link for such requests is their NSURLRequest mainDocumentURL.
If there's some way to get your original NSURL used as mainDocumentURL that would be ideal. If there's no way to prevent it being copied, I thought of the following hack as an alternative:
Before the creation of each UIWebView, set the user agent string to a unique value. Supposedly this change only affects UIWebView objects that are created subsequently, so each view will end up with its own distinctive user agent string.
In the NSURLProtocol implementation, you can check the user agent string to identify the associated UIWebView and pass it through to the real protocol handler using the actual user agent string (so the server will see nothing different).
All this depends on the views really ending up with different UA strings. Let me know if you manage to get it to work!
You say that you can't put it on the NSURLRequest, but I'm not clear why from your updated discussion. That would be the most natural place to put it.
Implement webView:shouldLoadWithRequest:navigationType:.
Attach an extra property to the provided request using objc_setAssociatedObject. Then return YES. (It would be nice to use setProperty:forKey:inRequest: here, but UIWebView passes us a non-mutable request, so we can only attach associated objects. Yet another way that UIWebView is a pale shadow of OS X's WebView, which can handle this).
In the NSProtocol, read your extra property using objc_getAssociatedObject. The request should be the same one you were presented earlier. You suggest that this isn't the case. Are you saying that the request at webView:shouldLoadWithRequest:navigationType: is different than the request at initWithRequest:cachedResponse:client:?
Am I missing another requirement or quirk?
You can pass options through custom request headers, assuming that the targeted website or service provider don't somehow strip those in transit.
The challenge there would be coming up with an encoding scheme that can be reasonable encoded into an ASCII string for the header field value and then decoded into the actual value you want. For this, a custom NSValueTransformer would seem most appropriate.
I had the same problem. I finally stuck to the solution suggested by Matthew (using the user agent string). However, as the solution is not fleshed out, I add a new answer with more details. Furthermore, I found out that you do not need to send a request to make the user agent "stick". It is sufficient to get via javascript as suggested here.
Following steps worked for me:
(1) Get the current default user agent. You need it later to put it back into the request in NSURLProtocol. You need to use a new webview insatnce, as getting the user agent will make it stick to the webview, so you can not change it later on.
UIWebView* myWebview = [[UIWebView alloc] init];
NSString* defaultUserAgent = [myWebview stringByEvaluatingJavaScriptFromString:#"navigator.userAgent"];
[myWebview release]; // no needed with ARC, but to emphasize, that the webview instance is not needed anymore
(2) Change the value in the standardUserDefaults (taken from here).
NSDictionary* userAgentDict = [NSDictionary dictionaryWithObjectsAndKeys:#"yourUserAgent", #"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:userAgentDict];
(3) Make the new user agent string stick to your webView by getting it via javascript as done in (1), but this time on the webview instance you actually work with.
(4) Revert the default user agent in the standardUserDefaults as done here.
I am an iOS newbie and I am trying to understand some of the "good practice" ways of doing things in an iOS app.
I have a ViewController which I made to be the controller that is called when the app starts. In it I saw a function called viewDidLoad and I tried to modify it to check if the user has had a user_id and then call a function that would eventually make an asynchronous request to manage that user in a remote db. Here is my code:
- (void)viewDidLoad
{
[super viewDidLoad];
EmailUtil *email = [EmailUtil alloc];
email = [email init];
// This is just a test call to the function that would make a remote server request
[email setEmail: #"test" andBody: #"hello"];
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
if([standardUserDefaults objectForKey:#"user_id"] == nil)
{
NSLog(#"First time");
[standardUserDefaults setBool:YES forKey:#"user_id"];
}
else
{
NSString *subject = #"subject";
NSString *body = #"bod";
NSLog(#"Not first time");
}
}
So there are a few things I am unsure with here. Can the fact that I am calling a function to make a remote call from viewDidLoad cause problems in that function? Currently its not sending the remote request.
Also, should I deallocate memory for the objects I create right at the end of this function?
Or should I just move this code to another spot in the class where it would make more sense to have that code there?
I call the email object like this:
[email setEmail: #"test" andBody: #"hello"];
and here is the code for the EmailUtil class:
//
// EmailUtil.m
//
#import "EmailUtil.h"
#implementation EmailUtil
-(void) setEmail: (NSString *) subject andBody: (NSString *) body
{
NSString *final_url = [NSString stringWithFormat:#"http://www.my_url.com?subject=%#&body=%#",subject, body];
NSURL *url = [NSURL URLWithString:final_url];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url ];
// TODO: ok I dont really understand what this is
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSLog(#"On return");
NSLog(#"This is data: %#" , data);
NSLog(#"This is response: %#" , response);
NSLog(#"This is error: %#" , error);
NSLog(#"OK");
}];
}
#end
Thank you!
You said:
So there are a few things I am unsure with here. Can the fact that I
am calling a function to make a remote call from viewDidLoad cause
problems in that function? Currently its not sending the remote
request.
Feel free to make whatever calls you want in viewDidLoad. Just make sure to not doing anything that will block the UI (e.g. some long, complicated method). Any long-running, non-UI related task would be done asynchronously in a separate queue, but that's for another day, in the distant future.
Whether you do this here, or in application:didFinishLaunchingWithOptions:, is a function of what your UI is going to do. We'd need a better sense of your app's flow to answer that question. And we need to understand what how the EmailUtil presents itself (often these sorts of classes use a modal presentation, which makes sense to do from another view controller and not from application:didFinishLaunchingWithOptions:).
But, bottom line, I personally have application:didFinishLaunchingWithOptions: always take me to what will be the app's "main" or "home" page, and if I have something that I want to present on first time usage, but not have that be the main page, then I'll invoke this from my main view controller's viewDidLoad. Other guys will customize application:didFinishLaunchingWithOptions: with the "first time used" logic. It's a matter of personal preference.
Also, should I deallocate memory for the objects I create right at the end of this function?
The first rule of memory management is that you always should deallocate anything you own (generally anything you create) and only the things you own. But, if you're using ARC (which I highly encourage you to do), this is taken care of for you (objects are deallocated as they fall out of scope).
Or should I just move this code to another spot in the class where it
would make more sense to have that code there?
Personally, I'm not crazy about having an app send an email just because the main view controller appeared, as I assume this was just an example and not a real app. But if you were contemplating putting this in a different place, the "send email" function is more logically hooked to the user tapping on a "send email" button, rather than running off and doing it itself.
On other points:
This code is curious, though, because you're creating EmailUtil, a local var, setting setting some its properties, but then not doing anything with it, and then letting it fall out of scope. I presume you'd want some method to make this EmailUtil do its stuff, e.g. [email sendMessage], or whatever the appropriate method is.
You're also creating two local vars, subject and body, and not using them and letting them fall out of scope. I presume you wanted to update your email object's properties accordingly but have not gotten to that.
Is there a reason you're writing your own EmailUtil class rather than using MessageUI.framework? Hopefully EmailUtil is just a nice wrapper for the MessageUI.framework, or doing something completely different, but not replicating functionality that Apple already provides.
You said that the app will "make an asynchronous request to manage that user in a remote db". Wow. I do that sort of stuff all the time, now, but I wouldn't suggest asynchronous coordination with a server as a good first-time project. Hopefully when you said "eventually" you meant "a couple of months from now" and not "in the next week or two." Looks like you're still getting your sea legs on basic view controller and memory management stuff. You might want to see if you can constrain the functionality of your initial version of the app to something a little less ambitious. If you don't have some basic skills yet and try to do something complicated, you can end up with a real mess on your hands that you'll find yourself completely rewriting later. So, no offense, but see if you can come up with something a little simpler for your first real project. Or do a couple of test apps first. Just a thought.
Lvsti is quite right that the typical object creation construct is [[EmailUtil alloc] init]. What you have is equivalent, but non-standard.
I know it's a work in progress, but it looks like your setEmail:andBody: is setting properties and sending the message. I'd suggest (a) having #property entries for subject and body (which will automatically generate setSubject and setBody methods, as well as let you do stuff like email.subject = #"This is the subject of the email";); (b) if you want, have an init convenience method, e.g., initWithSubject:body: which will do the init your object and set these two properties; and (c) have a separate method that actually sends the message. It's not a good practice to have something that looks like a setter, is sort of a setter, but also does more material stuff, all in a single method. Methods that begin with the word "set" are generally standard setters and iOS conventions will lead other programmers to misread your code (as I just did). By following some standard iOS conventions, it will be easier if you ever get iOS programmers involved in reviewing and revising code.
You should move this code to the application:didFinishLaunchingWithOptions: method of your appdelegate.
From iOS-Dev-Center
You should use this method to initialize your application and prepare
it for running. It is called after your application has been launched
and its main nib file has been loaded. At the time this method is
called, your application is in the inactive state.
And if you are not using arc, only thing you should release is EmailUtil, no reason to deallocate something by hand
I am making a NSURL and passing it to a selector, which then passes it to another selector, etc. By the time it gets where it's going it logs just fine, but gives a sigabort when it's used. I suspect this means my object has been released by ARC. How can I make sure it stays around long enough to get used?
__strong NSURL *url = [[NSURL alloc] initWithString:str];
... passes to a selector
... passes to another
... and then to fetchVideoContent
- (void)fetchVideoContent:(NSURL *)url withGUID:(NSString *)guid;
{
NSMutableURLRequest *req;
req = [NSMutableURLRequest requestWithURL:url // <-- DIES ON THIS LINE (SIGABRT)
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:30.0];
...
That's the "strongest" thing I could think of and that still doesn't work. Any advice?
You need to ensure the initial url variable (__strong NSURL *url) continues to exist when the url object ends up at the fetchVideoContent method, if not, you'll get the error you're describing. Sounds to me like you're creating the url object in a method, using a local variable, and then passing that object through a few methods, that either cross to a new thread, or goes to the end of the runloop and back into the next run.
For example, if through the steps you've omitted, the current run loop ends, and the initial url variable goes out of scope, the url object will be freed, since nothing is actually holding on to it anymore. Passing the object to another method isn't enough to keep hold of it since no retain will be called on the parameter.
Short version is, make sure something holds onto url, you could make it a property of your class, an instance variable or even static if you'll only one instance of your class in use at a time.
First, you should verify that you are in fact dealing with a reference count issue -- run with zombies enabled.
I've no idea what all the URL is being passed through, but there are corner cases where explicit reference counting is required when ARC is enabled.
If MRC semantics are needed, you can use CFRetain and match that with a CFRelease, or you can create your own functions which are not compiled with ARC enabled.
Of course, you could simply use CFTypes instead (in this case).
I have a request :
[NSURLConnection connectionWithRequest:urlRequest delegate:self];
that is launched.
When I receive at any time :
- (void)connectionDidFinishLoading:(NSURLConnection*)connection
I change the content of an array (remove or add items).
In another part of my program, I have to parse that array to work with its content.
So the question is : while I'm working with the content of the array ("for xxx in array" loop), may the response of the server (that can come at any time) cause the code of connectionDidFinishLoading change that array, and make the whole thing crash ?
Yes and it will cause your app to crash. You can either work with a copy of the array that is being modified by the NSURLConnection delegate method OR wait for the method to finish before iterating through the items in your array.
Maybe block that particular operation while there is an active NSURLConnection so the rest of your UI is still usable.