strange behaviour from Mail and Message controller - pre initialised - objective-c

I have pre allocated the mail and messaging controllers on startup in my app delegate to save the initialisation time (over 10 secs) when the user is using my application...
__mailController = [[MFMailComposeViewController alloc] init];
__messageController = [[MFMessageComposeViewController alloc] init];
It works fine the first time the controller is displayed then the next time the message is not changed and the old message is still displayed ?? ... Is it likely that the controller is being deallocated ??? Strange as the views work correctly just that the message is not correct ?
- (IBAction)actionSMS:(id)sender {
if([MFMessageComposeViewController canSendText])
{
self.messageController.body = self.MessageDetail.text;
// controller.recipients = [NSArray arrayWithObjects:#"+919999999999", nil];
[self presentModalViewController:self.messageController animated:YES];
}
}

Once MFMailComposeViewController and MFMessageComposeViewController are presented to the user you can't make changes to the content they display.
MFMailComposeViewController Class Reference:
Important The mail composition interface itself is not customizable
and must not be modified by your application. In addition, after
presenting the interface, your application is not allowed to make
further changes to the email content. The user may still edit the
content using the interface, but programmatic changes are ignored.
Thus, you must set the values of content fields before presenting the
interface.
That means those values are somehow locked in the implementation of the MFM*ViewController at the moment you present the controller. So you can't reuse these viewControllers. iOS doesn't care if the controller is, like in your case, invisible or not. If it is presented the content is locked.
I would figure out why it takes 10 seconds to allocate them. And then dump that whole pre-allocation thingie. 10 seconds are definitely to much.

I had the same problem. Not only are the MF controllers only good for one time use, as you discovered, they also cannot be init-ed in the background because their UI elements need to be init-ed in the main thread.
In the end, I just present a UIActivityIndicatorView over a HUD, so the users will know the app is responding.

Related

Define a controller for NSDocument for document-based application

I'm not very sure how Document-Based Applications works.
I've created some actions for NSObject in the Mainmenu.xib. One of this is called when the user click on "File>new":
-(IBAction) newDocument:(id)sender{
Document* newDoc =[[Document alloc] init];
[[NSDocumentController sharedDocumentController]addDocument:newDoc];
[newDoc addWindowController: [[NSWindowController alloc] initWithWindowNibName:[newDoc windowNibName] owner:newDoc]];
[newDoc showWindows];
}
I've also this code inside the openDocument:(id) sender action that does the same but of course loading data to define the application workspace.
If I run the application it show a blank document without to call newDocument action. I don't know how to stop default blank document and to set newDocument: to be called.
Then if i do openDocument: too (so I've two documents, one blank and one not) and I do some operation on the second document it also replicate in the first blank one.
I've double check delegates, file owners, and also if the - (void)windowDidBecomeMain:(NSNotification *)notification return different pointers and all seem to be ok.
Probably I've not understood document based application work flow but I've read the Apple guide and other istructions. What do I miss?
An IBAction method is called, when the user did something. So this is not called from the system at app launch.
You can customize the behavior at app launch with -applicationShouldOpenUntitledFile: (NSApplicationDelegate) and – this is probably your next question – -applicationShouldHandleReopen:hasVisibleWindows: (NSApplicationDelegate). Changing the behavior in both cases is not recommended.
Looking to your action method, I see no reason, why you want to customize it.
A instance of your document class is created automatically.
You can create a window controller for it in your document subclass. This is documented.
Just let NSDocumentController do the work for you. What is the problem of the default behavior?
No. I thought to be confused instead the only problem was about releasing observer notification. When you call the close message for a NSDocument notification observers still persist. Working in ARC I miss this point.
So This is the solution at my issue. Thank you anyway.

Status application: sometimes panels are missing, multithreading possible issue

I have a status application, it's long to post it all so I'll describe it and post only part of the code:
In the xib file there are two objects: AboutController and PreferencesController;
The app delegate is able to launch AboutController's and PreferencesController's panels;
The panels are in the xib file too;
The user by selecting a status menu item is able to launch these two panels;
There is a timer that constantly downloads a HTML page and reads it;
When the page is downloaded, the stringValue of a label is changed.But the stringValue may also be changed from the PreferencesController.The page is downloaded from a background thread, but it's changed through the main queue.
Now I've got some questions:
Do I have to invalidate the timer when the application starts sleeping (computer goes in standby),and create another one when it returns on?
The label is updated in the main queue, so I still have to protect the label access with a mutex?
Sometimes the panels are missing: at the start of the application I am able to launch panels by clicking a menu item, but sometimes they are not launching.I don't know how to reproduce this bug always, it just happens randomly when the application is active for 2/3 hours usually, I have to relaunch the application to go around this.
The code is too long, that's only a piece of code:
- (void) checkPosts: (id ) sender
{
NSOperationQueue* queue=[NSOperationQueue new];
queue.maxConcurrentOperationCount=1;
[queue addOperationWithBlock:^
{
NSNumber* newPosts= [self updatePosts];
NSNumber* posts= [controller posts];
if([posts integerValue]!=[newPosts integerValue])
{
NSOperationQueue* queue=[NSOperationQueue mainQueue];
posts= newPosts;
[queue addOperationWithBlock:^
{
// This is where I may have a race condition
item.attributedTitle=[[NSAttributedString alloc]initWithString: [formatter stringFromNumber: posts] attributes: #{NSForegroundColorAttributeName : [controller color], NSFontAttributeName : [NSFont userFontOfSize: 12.5]}];
}];
// That's not so relevant:
NSUserNotification* notification=[NSUserNotification new];
notification.title= [NSString stringWithFormat: #"posts Changed to %#",posts];
notification.deliveryDate=[NSDate date];
notification.soundName= NSUserNotificationDefaultSoundName;
NSUserNotificationCenter* center=[NSUserNotificationCenter defaultUserNotificationCenter];
[center deliverNotification: notification];
center.delegate= self;
[controller setPosts: posts];
}
}];
}
A little background information:
This method works in a background thread;
[self updatePosts] downloads the HTML page and returns the number of posts;
[controller posts] reads the previous number of posts using NSUserDefaults;
item is a status menu's menu item.
More Details
This is how I get the timer:
// In the applicationDidFinishLaunching method
timer=[NSTimer scheduledTimerWithTimeInterval: [interval integerValue ] target: self selector: #selector(checkReputation:) userInfo: nil repeats: YES];
timer is a property:
#property (nonatomic, strong) NSTimer* timer;
interval is a NSNumber, for sure it's integer value is greater or equal than 1.
It's not entirely clear what's happening here. You've provided plenty of information but not everything needed to give a definitive answer. I'll try to address your questions first:
Do I have to invalidate the timer when the application starts sleeping
(computer goes in standby),and create another one when it returns on?
Have to? No. Should for cleanliness and certainty of the state? Yes, probably. You should probably specify exactly how you set up the timer since you can run into problems with it interacting with the run loop ... but I don't think this is your problem.
The label is updated in the main queue, so I still have to protect the
label access with a mutex?
As long as you update the UI from the main thread/queue, you should be fine. This is a standard design approach with blocks.
Sometimes the panels are missing: at the start of the application I am
able to launch panels by clicking a menu item, but sometimes they are
not launching.I don't know how to reproduce this bug always, it just
happens randomly when the application is active for 2/3 hours usually,
I have to relaunch the application to go around this.
If you don't know how to reproduce it, I'm not sure we can help you beyond "places to look." The first thought I've got is that you may be recreating multiple copies of your primary controllers when the app becomes active (since you asked about this earlier, I assume you've tried doing something with it). Make sure the same controllers are being reused.
Now on to the code.
NSOperationQueue* queue=[NSOperationQueue new];
The queue variable is local to the scope of the method. I see no retain/release, so I assume you're using ARC. In that case, you're not retaining the new queue you're creating and its life span is not guaranteed to survive as long as you need it to once the method completes and you've left its scope. You should make queue an instance variable so it sticks around. This way the queue can be reused every time the method is fired and it'll stay around long enough for other queues/threads to use.
I think it's likely this is your biggest culprit. Adjust it and update your question to reflect how it affects the condition of your app.

Prevent ARC from deallocating something that's not currently being used but will be soon?

I've run into this problem a couple of times and want to know the correct approach to take.
For an example, let's say I'm writing an iPhone app and I want a custom alert view class that uses blocks.
So I write the class, then later on in my code I go:
MyAlertView *alert = [MyAlertView alertWithBlahBlahBlah...];
[alert addButton:#"button" withBlock:^{ ... }];
[alert show];
Somewhere in the alert view class, we have
- (void)addButton:(NSString *)button withBlock:(void (^))block {
[_blocks setObject:[block copy] forKey:button];
}
- (void)show {
... drawing stuff ...
UIButton *button = ...
[button addTarget:self selector:#selector(buttonPressed:) ...];
...
}
- (void)buttonPressed:(id)sender {
((void (^)())[_blocks objectForKey:[sender title]])();
}
So, the alert view now shows up just fine. The problem is, if I tap a button, it attempts to send the buttonPressed: selector to the MyAlertView object that was displayed. The MyAlertView has, however, been removed from the superview at this time. ARC decides that because the alert view is not owned by anyone anymore, it should be deallocated, not knowing that a button needs to message it in the future. This causes a crash when the button is tapped.
What's the right way to keep the alert view in memory? I could make the MyAlertView object a property of the class that's using it, but that's kind of silly (what if I want to show two alerts at once?).
If an object were to remain in memory, and you do not have a reference to it, this is known as a memory leak. As I said in my comments, you need to keep some kind of reference to it so that a) it is not deallocated, b) you can send a message to it, and c) you can deallocate it before your class is deallocated.
The most obvious way to do this would be with a property in your class. Since you said that you don't want to do that (maybe you have a lot of them) then another possible solution would be to keep an array of cached objects that you plan on reusing and eventually deallocating.
I think you can use performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay to retain the alert view in runloop.
Actually, I just came across an wrapper implementation for UIAlertView using this skill.
Check UIAlertView input wrapper for more detail.
Quite simply, you're breaking the memory management rules. ARC doesn't change the rules, it just automates them. If you need an object to stay alive, it needs to have an owner. Every object in your app's object graph, all the way back to the application delegate, has an owner. It may not be obvious what that owner is (and sometimes the owner may be an autorelease pool), but there is one.
If you want this view to stick around, it needs to be owned by something, even if it's not "currently being used". If it's onscreen, it should be part of the view hierarchy. If it's not, the ideal owner is likely to be the object that created it.

Possible to Subclass UILocalNotification and Change Default "Close" Button Text and Method?

Searching for a way to change the "Close" button text/functionality of a UILocalNotification...
I've found that it's impossible to access/call the text/function from another object, although subclassing UILocalNotification should allow implementation method overrides... not to mention the creation of an accessor to get/set the "Close" button text field.
What do you guys think about this? What would Apple?
Has anyone tried?...
EDIT: 12/21/2011 12:01 PM
The question that I'm asking involves an understanding of oop: late/early binding, dynamic method lookup, and declared type vs. runtime type field and method handling.
Subclassing of UILocalNotification does work...
UILocalNotificationExampleSubclass * example = [UILocalNotificationExampleSubclass init];
...and the device does create an object, however, with type UILocalNotification and not UILocalNotificationExampleSubclass.
I'm looking for insight into the UILocalNotification.m file's methods.
If it does not have its own methods, what object (name please) takes an instance of a UILocalNotification, uses its fields, and displays the object (name please) we see on screen?
A UILocalNotification is just a storage for the notification's information. It does not perform anything.
Moreover your application does not display the notification. Another process does. So subclassing UILocalNotification is just useless.
EDIT at December 22nd, 17:53 UTC+1:
Yes, you can subclass UILocalNotification. But UILocalNotification is an abstract class and none of its properties is implemented. The alloc method is overridden so it returns an instance of UILocalNotification, a private subclass. That's why you cannot instantiate UILocalNotificationExampleSubclass.
But still, there is not point to subclass UILocalNotification because when you schedule a notification using -[UIApplication scheduleLocalNotification:] or present the notification immediately using -[UIApplication presentLocalNotification:], the operating system copies the notification.
That copy is stored in another process managed by the system, which uses its own private storage mechanism. A UILocalNotification is just a storage for a bunch of properties that is destined to get serialized and sent from the application to the operating system.
Now, we have that other process storing all the scheduled local notifications and waiting for a notification to fire. When that happens, that process will check if your application is in the foreground.
If your application is not in the foreground, that other process, which is totally out of our control, will create an alert and display the notification. We cannot customize that alert in any way, except by using the properties of the UILocalNotification class.
If your application is in the foreground, the notification will be sent back to the application that will create a new instance of UILocalNotification. Then, the UIApplication shared instance will access its delegate property and check if that delegate implements application:didReceiveLocalNotification:. If it does, you get the notification back and can do anything you want with that notification. For example, you may choose to display the notification using an alert view.
Configuring and displaying the alert view can be done like this:
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
UIAlertView *alertView =
[[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Alert", nil)
message:NSLocalizedString(notification.alertBody, nil)
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:NSLocalizedString(#"OK", nil), nil];
[alertView show];
[alertView release]; // unless your project uses Automatic Reference Counting
}
I hope this longer response did answer your question, if what I'm saying is true.

How to handle situation where page unloads due to low memory

//In App Delegate
UserProfileTableViewController *uptvc = [[UserProfileTableViewController alloc]init];
UITabBarItem *tempTabBarItem4 = [[UITabBarItem alloc]initWithTitle:#"Fans" image:nil tag:FANSTAB_INDEX];
//I am setting the user id information here directly in app delegate
uptvc.userId = [[UserStockInfo sharedUserStockInfo]getUserId];
UINavigationController *navconUptvc = [[UINavigationController alloc]initWithRootViewController:uptvc];
The problem arises when my UserProfileTableViewController gets unloaded due to low memory (might be due to using the camera feature in my app). The page will fail to load properly as it is missing the 'userId' information passed in from the app delegate (as seen above). I am unable to set this userId information directly in the UserProfileTableViewController (in view did load method) as other pages might pass a different userId when pushing the page onto their stack.
Any advise on how I can resolve this issue?
First off, you should keep your UserProfileTableViewController object into an ivar of the app delegate (since you allocate it there). Second, make the app delegate provide that userId to the controller. Third, if the navigation controller is removed from the interface/deallocated, then even with low memory your uptvc should not be deallocated either.
View controllers maintain the full hierarchy of controllers, even when running out of memory, what's deleted are views, and anything you tell them to remove.
You most certainly want to keep the UINavigationController in an ivar of the AppDelegate as well.