For the life of me, I cannot figure out what's going on here.
As an overview, I have an application that I've created a custom navigation bar, a custom containment view controller, and callbacks to tell me when expensive processes have been finished executing inside individual view controllers (such as a large set of images).
Inside my containment view controller when navigating to a child view controller, a call back is set on the child view controller to call the transition animation after it has finished it's set of expensive processes.
The callbacks are created as such
#interface UIViewController (CallBack)
typedef void (^CompletionBlock)(void);
#property (nonatomic, copy) CompletionBlock callBackBlock;
- (void)doneLoadingImages;
#end
static char const *const CompletionBlockTagKey = "CompletionBlockTag";
#implementation UIViewController (CallBack)
#dynamic callBackBlock;
- (CompletionBlock)callBackBlock
{
return objc_getAssociatedObject(self, CompletionBlockTagKey);
}
- (void)setCallBackBlock:(CompletionBlock)callBackBlock
{
objc_setAssociatedObject(self, CompletionBlockTagKey, callBackBlock, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)doneLoadingImages
{
[self callBackBlock]();
self.callBackBlock = nil;
}
#end
These callbacks are set before the child view controller is added through addChildViewcontroller:, and are fired in a dispatch_async block such as this
__block __weak ThisUIViewController *me = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image1 = [UIImage imageForNibFromFileName:#"picture_name"];
dispatch_async(dispatch_get_main_queue(), ^{
me.imageViewToSet.image = image1;
[me doneLoadingImages];
});
});
This process goes through fine the first time a UIViewcontroller gets called from my container view controller through my navigation code. According to the profiler, it dumps memory properly as well after I navigate away from it indicating my retain count is 0 for it.
BUT, what happens when I navigate to the same UIViewcontroller again is that the images are loaded super fast, and the doneLoadingImages gets called super fast as well, causing hang ups on my main thread and causing the UI to become unresponsive until everything has been set UI wise.
My imageForNibFromFileName: method is just a convenience category method that uses imageForContentsOfFile: internally. Anybody have some insight on what I may be doing wrong here?
Turns out it wasn't a retain issue. I'm not exactly sure why, but I had to separate the images into their own dispatch_async call from the global_queue instead of chaining them all in one async block. If anybody has an explanation of why this has to be done or what's going on in the background, that would be appreciated.
Related
I am writing an application where the user should be able to click on a button and have the views switch. I have created IBActions that load my method switchSubView on the desired view, but have been having some issues in doing so. I have currently written methods to animate the transition between two NSViews connected through Interface Builder as IBOutlets. I am able to use the methods to transition between two views, but when I try to switch from the initial view to another view, then switch to another, and then switch back, I get EXC_BAD_ACCESS. I can't seem to debug the problem. The debugger shows both of the NSView objects to point to distinct memory locations. To clarify, I would like to reuse the views throughout the duration of the application's execution. I would not like any of the views to be deallocated.
Here is the code:
- (void)switchSubViews:(NSView *)firstView withView:(NSView *)secondView {
[firstView setSubviews:[NSArray array]];
[self prepareViews:firstView];
[[firstView animator] addSubview:secondView];
}
/* Make a "move" animation effect. */
- (void)prepareViews:(NSView *)prepareView {
CATransition * transition = [CATransition animation];
[transition setType:kCATransitionPush];
[transition setSubtype:kCATransitionFromRight];
[prepareView setAnimations:[NSDictionary dictionaryWithObject:transition forKey:#"subviews"]];
[prepareView setWantsLayer:YES];
}
Basically, I would call it like this, given initialView and newView:
[self switchSubViews:initialView withView:newView];
Can anyone help me debug this?
Thanks.
Update: After commenting out the for loop and the removeFromSuperview code to debug, I am still receiving the bad access error. The properties for the view are set to #property (nonatomic, strong), so shouldn't the views be retained? If not, how can I explicitly retain my NSView(s) with ARC enabled? Is it possible to tell ARC to retain it, or is it already retaining the view since it is set with #property(nonatomic, strong)?
Second Update: I researched a little and found out that I can cause ARC to retain the object by creating a strong pointer to the object. I tried it by creating instance variables:
#interface AppDelegate : NSObject <NSApplicationDelegate, NSWindowDelegate> {
/* Some instance variables here... */
__strong NSView * retainFirstView;
__strong NSView * retainSecondView;
}
#property (nonatomic, strong) NSView * retainFirstView;
#property (nonatomic, strong) NSView * retainSecondView;
And then in the .m file:
#import <Quartz/Quartz.h> /* Animation. */
#import "AppDelegate.h"
/* Some other imports necessary... */
#implementation AppDelegate
#synthesize retainFirstView;
#synthesize retainSecondView;
/* Some subview switching code... */
[self setRetainFirstView:firstView];
[self setRetainSecondView:secondView];
#end
but I still receive the error. It seems that the view that I am swapping out is consistently being deallocated. I've spent significant time on this and haven't been able to get rid of the error. Any help would be appreciated. If this isn't the proper way to retain the view, please let me know.
Update 3: To test, I have disabled ARC temporarily and did this before calling setSubviews or addSubview:
[firstView retain];
[secondView retain];
and it still failed. I am quite confused, as I no longer see what could be causing the deallocation.
Update 4: I checked for zombies and messages sent to deallocated objects by doing Product -> Profile in Xcode, and to my surprise, I wasn't notified of any. I am very confused on why this is occurring.
You're getting EXC_BAD_ACCESS because calling -removeFromSuperview will deallocate that view if nothing else is retaining it. It's very likely that a view that is being deallocated (therefore having an object pointer that points to garbage) is later passed into -switchSubviews:withView, causing your crash. You should always retain views that you are going to be using later on before calling -removeFromSuperview to make sure that they are not deallocated.
As a sidenote, the code for -switchSubviews:withView: is needlessly complicated. It could be reduced to this:
- (void)switchSubViews:(NSView *)firstView withView:(NSView *)secondView {
firstView.subviews = [NSArray array];
[self prepareViews:firstView];
[[firstView animator] addSubview:secondView];
}
I did find an answer to this title and I did do a little research but I'm still not getting the flow. Here is what I want to happen:
1) click a button on the presenter view to open a modal view. 2) retrieve some value and click a button to close the modal view....sending the value to the presentor view and execute a method.
I get that this works like a callback but I still can't figure out where to put the callback stuff.
So, how exactly do I do this? A) In the presentViewController completion block, should I include the presenter view method to execute when modal view is completed?
Or: B) In the modal view's dismissViewControllerAnimated completion block, should I include the presenter view method to execute when modal view is completed?
Can somebody help me with some sample code? Or at least help me get the flow of which block to put the code in?
Thank you, P
You talk about completion blocks so I am assuming you do not want to use delegates.
In the viewController that will be presented modally you need to provide a public completion handler, that will be called when it is dismissed.
#interface PresentedViewController : UIViewController
#property (nonatomic, strong) void (^onCompletion)(id result);
#end
Then in the implementation you need to call this completion block on dismissal. Here I assume the viewController is dismissed on a button click
- (IBAction)done:(id)sender
{
if (self.onCompletion) {
self.onCompletion(self.someRetrievedValue);
}
}
Now back in the viewController that presented the modal you need to provide the actual completion block - normally when you create the viewController
- (IBAction)showModal;
{
PresentedViewController *controller = [[PresentedViewController alloc] init];
controller.onCompletion = ^(id result) {
[self doSomethingWithTheResult:result]
[self dismissViewControllerAnimated:YES completion:nil];
}
[self presentViewController:controller animated:YES completion:nil];
}
This will create the new viewController to be presented modally and define what needs to happen on completion.
You can do this with delegates, that's the way Apple seems to recommend, but that seems like overkill to me. You have a reference to the presenter with the presentingViewController property, so you can just set the value of a property in the presenter from the presented controller in the button click method:
self.presentingViewController.someProp = self.theValueToPass;
[self dismissViewControllerAnimated:YES];
Using delegates is a good way to handle this:
In your PresentedViewController.h
#protocol PresentedViewControllerDelegate <NSObject>
-(void) viewWillDismiss;
#end
#property (nonatomic, weak) id <PresentedViewController> delegate;
Then in your PresentingViewController.h, you would subscribe to this delegate
#interface PresentingViewController : UIViewController <PresentedViewControllerDelegate>
in the .m you must implement the delegate method
- (void) viewWillDismiss {
}
and before you present the view controller set the delegate property you made as self.
presentingViewController.delegate = self;
Obviously not every implementation detail has been done here, but this should get you started.
I have two methods that get called from within -drawRect:
- (void)drawRect:(CGRect)rect {
if(drawScheduleFlag) {
[self drawSchedule];
drawScheduleFlag = false;
}
else
[self drawGrid];
}
-drawGrid is called at initialization time. The other method (-drawSchedule) is called from this code, which is on the main thread:
- (void) calendarTouched: (CFGregorianDate) selectedDate {
// NSLog(#"calendarTouched - currentSelectDate: %d/%d/%d", selectedDate.month, selectedDate.day, selectedDate.year);
// NSLog(#"Main thread? %d", [NSThread isMainThread]);
// get data from d/b for this date (date, staff name, cust name, time, length, services required)
//------ stub -------
scheduledDate.year = 2012;
scheduledDate.month = 6;
scheduledDate.day = 20;
staffName = #"Saori";
custName = #"Brian";
startTime.hour = 11;
duration = 2;
servicesReqd = #"Nails";
drawScheduleFlag = true;
[self setNeedsDisplay];
return;
}
I know the code is being executed, but nothing happens to draw the schedule. Why is -[self setNeedsDisplay] not causing the -drawRect to be called?
UPDATE: I have put breaks in so I'm positive it's not being called. The original grid is drawn once; when the user taps a calendar date, -calendarTouched is called and completely executed. The drawScheduleFlag is set to true, and -[self setNeedsDisplay] gets called, but -drawRect does not. It appears that the UIView is not being invalidated (which -setNeedsDisplay is supposed to do), therefore -drawRect is not called.
UPDATE #2: I have a .xib for a UIViewController with two (2) UIViews in it. The first UIView takes about 1/3 of an iPad screen, the second UIView takes the bottom 2/3 of the screen. Each UIView has it's own specific class; the top UIView displays a calendar and is working correctly, capturing touches and changing the date selected.
The bottom UIView is supposed to show the schedule for the date picked in the top UIView. This is where the problem is. Since the top UIView is working, I will put up the code for the bottom UIView's class. It basically draws the schedule grid when -drawRect is first called. Once the user has selected a day, it is supposed to invalidate the UIView to draw the actual schedule on the grid.
Here is the code for Schedule.h: http://pastebin.com/NQpj0i07
Here is the code for Schedule.m: http://pastebin.com/YBbE8y0T
Here is the code for the controller: http://pastebin.com/nDqBCivj
Note that both pastebin's expire in 24 hours from Jun 28, 5:38 PM PST
Repeating what I said in the comments, the problem you are having is that your UIView subclass object is getting deallocated before the drawRect call gets executed, which means that whatever is holding the reference to the object, in this case your controller, is releasing it before you mean to, little issue that sometimes comes up with using ARC.
In the controller code that holds this view you should have something like this declared:
#property (weak, nonatomic) IBOutlet Schedule *scheduleView;
And synthesized:
#synthesize scheduleView = _scheduleView;
And in the controller's dealloc method:
self.scheduleView = nil;
If you're using storyboards, which you appear to not be using since you made a .xib file for this, you should have it hooked up properly. If not simply instantiate it and assign it.
Expanding on what I said before, I'm not sure what you're trying to do but taking a look at your code you are creating the view for a brief moment when the notification gets called and right there it's getting deallocated because no one is holding a reference to it. By doing what I said and control+click drag from the controller to the view in interface builder and hooking it up it will hold the reference to it, and you'll only have 1 Schedule object created.
After that you'll have to modify your code to work with this instance of Schedule:
- (void) testNotification:(NSNotification *) notification {
// was the calendar tapped?
if ([[notification name] isEqualToString:#"calendarUpdated"]) {
NSDictionary *passedData = notification.userInfo; // get passed data
CFGregorianDate currentSelectDate;
NSData *data = [passedData objectForKey:#"currentSelectDate"];
[data getBytes:¤tSelectDate length:sizeof(CFGregorianDate)];
[self.scheduleView calendarTouched:currentSelectDate];
}
return;
}
The other option you have if you're not using interface builder to set everything up is the following in your viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
self.scheduleView = [[Schedule alloc] init];
[self.view addSubView:self.scheduleView]; // Probably some extra code for positioning it where you want
// notify me when calendar has been tapped and CFGregorianDate has been updated
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(testNotification:) name:#"calendarUpdated" object:nil ];
}
As a final note, considering you're new to iOS development (as we all were) you can go to iTunes and look for Stanford's iPad and iPhone Development course (CS193P) on iTunes U, it's completely free and it will teach you most of what you need to know for developing in iOS.
Scenario, in context of a card game:
User moves a card on the screen. As a result of a move, coordinates of the card change. If card is found to be in some specific location, we'd like to make sure Card object (Model) is updated to include those coordinates.
But View should not be talking to the Model directly .., so
Instead of updating Card directly, View is going to notify its Controller that "Card has landed". Upon receiving this notification, i'd like for a Controller to update Card's location instead of the View (Controller updates Model)
Question 1:
Am i thinking about this type of scenario correctly?
Question 2:
Is it possible to send data to a controller along with a notification?
You do not need NSNotifications for your scenario: a straightforward delegate-based approach should do.
The view should define a delegate interface, and provide a non-retaining delegate property. The controller should implement the delegate interface, and set itself as view's delegate. The view would then notify its delegate without even knowing that it notifies the controller. The controller would then pass the notification along to the model.
#protocol CardDelegate
-(void)cardHasLanded:(SOCard*)card atPosition:(SOPosition*)pos;
#end
#interface MyView
#property (weak, nonatomic,readwrite) id<CardDelegate> delegate;
#end
#implementation MyViewController
-(id)init { // This should be in your designated initializer
self = [super init];
if (self) {
MyView *view = [[MyView alloc] init];
view.delegate = self;
self.view = view;
}
return self;
}
-(void)cardHasLanded:(SOCard*)card atPosition:(SOPosition*)pos {
// Update the model
}
#end
#implementation MyView
#synthesize delegate;
-(void) doSomething {
// ...
if (cardHasLanded) {
[delegate cardHasLanded:card atPosition:pos];
}
// ... more code
}
#end
That's what the userInfo dictionary and object of NSNotification are used for. In this case, it's the object you want. For example:
// In your model header file
extern NSString * const CardMovedNotification;
// In your model implementation file
NSString * const CardMovedNotification = #"CardMoved";
...
[[NSNotificationCenter defaultCenter] postNotificationName:CardMovedNotification object:theCardThatMoved];
Your obversers then can get the card via [notification object]. If you need to pass more information, you'd create a dictionary and pass that as userInfo, via postNotificationName:object:userInfo:. The observer then can query it via [notification userInfo].
I agree that no notifications are needed. Usually for UI object coordinates I won't use a model object because the coordinates are coupled to the UI object (or else where will you draw it ?). What you do need to do is register your controller to the touch end event passing in the UI object where the touch was made and updating (in the responding controller method) the object's coordinates if they indeed within the desired location. (as done with buttons and other UI objects)
So it seems to me like there's a Catch-22 situation here. Note the following widely (and smartly) held stances on application architecture:
Apple (and most alpha devs) recommend not using a singleton or accessing the app delegate singleton for retrieving a NSManagedObjectContext. Rigidity, poor design, etc. OK-- I agree!
Iterating over a UITabbarController's child view controllers and giving each child VC a reference to the context (dependency injection) is also stupid, because you are loading every tab in your application at application launch time, just to pass a reference. This also goes against everything Apple recommends for application architecture.
How do you resolve this? Do you use NotificationCenter to post a notification when a view controller awakes from a nib, and have the app delegate pass in the context reference then? That's about the only way I can think of that jives with both #1 and #2, but it also feels like some contortion to me.
Is there a more elegant way?
Edit: doing a notification when initializing a view controller can be a race condition, since if you're doing things with Storyboard, your tabbar's child view controllers tend to be initialized (though sans-view loading) at launch. So you'd have to do such a notification in viewDidLoad, which is a bad idea vis-a-vis MVC convention. It also ties your hands from doing anything with data models (like pre-caching for performance) before the user does anything view-related.
When you pass NSManagedObject instances to a view controller, that view controller can retain those objects. It can then get to the NSManagedObjectContext through those managed objects by calling
-[NSManagedObject managedObjectContext]
I'm not sure if this will work in your particular case, but often it will. The app delegate or the root view controller creates a context and then passes along managed objects.
Update
If you need to use the context in multiple places, there's another pattern that I've found useful:
Subclass NSManagedObjectContext:
#interface MyManagedObjectContext : NSManagedObjectContext
+ (MyManagedObjectContext *)mainThreadContext;
#end
i.e. a singleton context for the UI / main thread. This is cleaner than using the App delegate, since other classes won't have to get to the App delegate, but can use this class directly. Also the initialization of the store and model can be encapsulated into this class:
#implementation MyManagedObjectContext
+ (MyManagedObjectContext *)mainThreadContext;
{
static MyManagedObjectContext *moc;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
moc = [[self alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
// Setup persistent store coordinator here
});
return moc;
}
#end
I see that some time has passed since you posted your question, but I have a different approach that I would like to share with you and everybody else.
I assume that you want to inject the managed object context in all/some of the view controllers that are shown as tabs of the UITabViewController and that you are using a Storyboard with a UITabBarController as the rootViewController.
In your AppDelegate header make your AppDelegate to implement the UITabBarControllerDelegate protocol.
#interface AppDelegate : UIResponder <UIApplicationDelegate, UITabBarControllerDelegate>
...
In your AppDelegate implementation add the following UITabBarControllerDelegate method. It will take care of setting the managed object context in any view controller that has the property.
- (BOOL) tabBarController:(UITabBarController *)tabBarController
shouldSelectViewController:(UIViewController *)viewController {
if ([viewController respondsToSelector:#selector(setManagedObjectContext:)]) {
if ([viewController performSelector:#selector(managedObjectContext)] == nil) {
[viewController performSelector:#selector(setManagedObjectContext:) withObject:self.managedObjectContext];
}
}
return YES;
}
In your application:didFinishLaunchingWithOptions: set self as the UITabBarController delegate.
UITabBarController *tabBarController = (UITabBarController *) self.window.rootViewController;
tabBarController.delegate = self;
Sadly the first view controller to be loaded is not ready at that time (in tabBarController.selectedViewController), and doesn't invoke the delegate either. So the easiest way to set the first one is to observe the selectedViewController property of the TabBarController
[tabBarController addObserver:self forKeyPath:#"selectedViewController"
options:NSKeyValueChangeSetting context:nil];
and set it there.
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
[object removeObserver:self forKeyPath:#"selectedViewController"];
UIViewController *viewController = [change valueForKey:NSKeyValueChangeNewKey];
if ([viewController respondsToSelector:#selector(setManagedObjectContext:)]) {
if ([viewController performSelector:#selector(managedObjectContext)] == nil) {
[viewController performSelector:#selector(setManagedObjectContext:) withObject:self.managedObjectContext];
}
}
}
I would create a core data provider class, which is a singleton. This class could either just provide the persistent store so that each view controller can create it's own context if needed. Or you could use the provider class more like a manager and return new (if needed) context. It should of course also setup a merge notification to each context so that you can listen to changes between threads.
With this setup each view controller can ask the provider/manager for the context and the provider/manager will handle everything internally and return a context for the view controller.
What do you think?
IMHO I find it more elegant not to do Core Data heavy lifting in a ViewController.
I'd suggest that you encapsulate data manipulation into an object and let the object reference/encapsulate your Core Data stack.
When your application and datamodel grows, you want to be really strict with how you pass MOCs. This becomes even more important if youre using concurrency and have to deal with multiple MOCs. Besides, your ViewControllers only need a MOC for FetchedResultsControllers. In most other cases youre fine passing ManagedObjects.