ARC Objective-C: How can self be deallocated? - objective-c

I am creating a workflow to navigate through websites, every step of the workflow has to load n frames and then knows its ready (I have to implement the timeout).
I don't understand why [self next] is giving me this error:
* -[WebWorkflow next]: message sent to deallocated instance 0x105796ef0
Considering this delegate function:
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
frameCounter++;
NSInteger frames = [(WebWorkflowStep *)[steps objectAtIndex:index] frames];
NSLog(#"Frame counter %ld of %ld", frameCounter, frames);
[self next];
}
And this next method:
-(void) next
{
if ( index < [steps count])
{
frameCounter = 0;
index = index + 1;
WebWorkflowStep *step = [steps objectAtIndex:index-1];
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:step forKey:#"selector"];
[[NSNotificationCenter defaultCenter] postNotificationName:EVENT_WORKFLOW_NEXT object:nil userInfo:userInfo];
}
}
Notes:
- WebWorflow a.k.a 'self' has been created/binded by another class with strong
Like so:
#interface AController : NSObject <APIProtocol>
{
WebView *webview;
NSMutableArray *accounts;
WebWorkflow *workflow;
}
#property (strong) WebWorkflow *workflow;
...
I do create the workflow like this:
workflow = [[WebWorkflow alloc] initWithWebView:webview];
NSArray *getPicturesWorkflow = [NSArray arrayWithObjects:
[[WebWorkflowStep alloc] initWithSelector:#"open" andLoadFrames:0],
[[WebWorkflowStep alloc] initWithSelector:#"login" andLoadFrames:2],
[[WebWorkflowStep alloc] initWithSelector:#"getPictures" andLoadFrames:8],
nil];
[workflow setSteps:getPicturesWorkflow];
And it gets initialized like:
-(id)initWithWebView:(WebView *)webview
{
self = [ super init];
if(self) {
timeout = 10;
index = 0;
web = webview;
frameCounter = 0;
[web setFrameLoadDelegate:self];
}
return self;
}

The AController instance owns a web view and is the web view's delegate. The AController instance is getting released (for some reason...we'd need to see how it's owner manages it). Since it might get released during a load, it should clean up after itself as follows:
- (void)dealloc {
[web stopLoading:self]; // or webView, not sure what you call it
}
This will prevent the crash. It will also abandon the load. If you don't want to do that, you'll need to figure out why the AController instance is being released.
The first step in doing that would be a breakpoint in the dealloc method.

Related

Where to init MutableArray?

I've tried to init/alloc it in initWithFrame but then objects wouldn't get added.
It'd only work in this method I'm calling but I call this method each time user refreshes the view so it'd init/alloc hundred times.
Not sure why it won't just work in initWithFrame.
I need to know the right way to init and add..!
-(void)queryParseMethod {
self.imageFilesArray = nil;
self.imageFilesArray = [[NSMutableArray alloc]init];
[self.imageFilesArray addObjectsFromArray:objects];
if (!error) {
for (PFObject *object in objects) {
int index = (int)[self.favArray indexOfObject:[object objectId]];
[self.imageFilesArray replaceObjectAtIndex:index withObject:object];
}
[self.favCV reloadData];
}}
Why not just:
if (self.imageFilesArray == nil) {
self.imageFilesArray = [[NSMutableArray alloc] init];
[self.imageFilesArray addObjectsFromArray:objects];
}
And make sure that imageFilesArray is a strong property.
Your most likely problem is that initWithFrame: isn't being called. If this view comes out of a storyboard, then you need to put this in awakeFromNib, since storyboard/nib-loaded objects initialize with initWithCoder:, not their designated initializer.
You generally don't want to try to do initialization in initWithCoder: because it's called too early. awakeFromNib is called after all your IBOutlets are assigned.
It is very common for experienced devs to break initialization out into its own method like this:
- (void)setup {
// Do your setup here
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self setup];
}
}
- (void)awakeFromNib {
[self setup];
}
Doing it this way makes sure that the object is initialized in either case.
Another common solution is lazy initialization, particularly for things like NSMutableArray:
#interface MyView
#property (nonatomic, readonly, strong) NSMutableArray *imageFilesArray;
#end
#implementation MyView
- (NSMutableArray *)imageFilesArray {
if (_imageFilesArray == nil) {
_imageFilesArray = [NSMutableArray new];
}
return _imageFilesArray;
}

How to add notification observers in NSManagedObject subclass to avoid call to unrecognized selector

I've been experiencing a problem with saving managed objects (in a background thread) resulting in calls to unrecognised selectors which seems to be related to the way I'm handling the observation of NSManagedObjectContextObjectsDidChangeNotification. Intermittently it will fail with -[NSFetchRequest myManagedObjectContextDidChange:]: unrecognized selector sent to instance. It's not always a NSFetchRequest, sometimes it's a NSKeyValueObservance or unspecified which makes me believe that the observer is still around after a managed object has been released.
I'm adding and removing the observer to NSManagedObjectContextObjectsDidChangeNotification as seen below. Is there anything wrong with that?
#interface Foo ()
#property (assign, nonatomic, getter = isObserving) BOOL observing;
#end
#implementation Foo
#synthesize observing = _observing;
- (void)awakeFromInsert {
[super awakeFromInsert];
self.addedDate = [NSDate date];
self.modificationDate = [self.addedDate copy];
[self commonAwake];
}
- (void)awakeFromFetch {
[super awakeFromFetch];
[self commonAwake];
}
- (void)awakeFromSnapshotEvents:(NSSnapshotEventType)flags {
[super awakeFromSnapshotEvents:flags];
[self commonAwake];
}
- (void)commonAwake
{
if (self.isObserving) return;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(myManagedObjectContextDidChange:)
name:NSManagedObjectContextObjectsDidChangeNotification
object:self.managedObjectContext];
self.observing = YES;
}
- (void)willTurnIntoFault
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextObjectsDidChangeNotification object:self.managedObjectContext];
self.observing = NO;
[super willTurnIntoFault];
}
- (void)myManagedObjectContextDidChange:(NSNotification*)notification {
NSDictionary *userInfo = [notification userInfo];
NSMutableSet *changedObjects = [NSMutableSet new];
NSSet *objects = nil;
objects = [userInfo objectForKey:NSInsertedObjectsKey];
[changedObjects unionSet:objects];
objects = [userInfo objectForKey:NSUpdatedObjectsKey];
[changedObjects unionSet:objects];
objects = [userInfo objectForKey:NSDeletedObjectsKey];
[changedObjects unionSet:objects];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF IN %#", self.bars];
NSSet *updatedObjects = [changedObjects filteredSetUsingPredicate:predicate];
if (updatedObjects.count > 0) {
NSDate *now = [NSDate date];
if (self.modificationDate == nil || [now timeIntervalSinceDate:self.modificationDate] > 1.0) {
self.modificationDate = now;
}
}
}
#end
Check to see when didTurnIntoFault is called. If it is called, try:
[[NSNotificationCenter defaultCenter] removeObserver:self]
Note that the contextDidChange notification might be called on a different thread then the didTurnIntoFault. And so you might have a race condition. Make sure the adding and removing of the observe is done on the same thread (which should be the one on which the managedOject and hence the managed object itself was created).

loadView cannot access my property, throws EXC_BAD_ACCESS

I have a property that gets set when initiating a new view controller.
- (IBAction)headerPressed:(UIButton *)sender {
RouteViewController *route = [[RouteViewController alloc] initWithRoute:[[Route getRoute:TEST_ROUTE] autorelease]];
[[self navigationController] pushViewController:circle animated:NO];
}
in the new view, which is a UIViewController
RouteViewController.h
#interface =RouteViewController : UIViewController <MKMapViewDelegate> {
Route *r;
}
-(id)initWithRoute:(Route *)route;
#property (retain, nonatomic) Route *r;
RouteViewController.m
-(id)initWithRoute:(Route *)route{
self = [super init];
r = route;
return self;
}
- (void)loadView {
[super loadView];
NSLog(#"TEST: %d", r.Route); // throws exception. Actually ANY time I access my r property
}
r.Route is the int value that the const TEST_ROUTE contains.
NOW, when I setup my breakpoint, AT the time of the NSLog, the inspector shows my object contains
Self >
r >
Route: 1
and
r >
Route: 1
So the property is available, and it DOES contain the correct data. But when i try ACCESSING the property, it throws an EXC_BAD_ACCESS.
Thoughts? :) (This is killing me here!)
There are a few problems here:
RouteViewController *route = [[RouteViewController alloc] initWithRoute:[[Route getRoute:TEST_ROUTE] autorelease]];
I think the autorelease message should be sent to the RouteViewController here and not the Route object, as the getRoute method should return an autoreleased object due to the naming convention.
i.e.
RouteViewController *route = [[[RouteViewController alloc] initWithRoute:[Route getRoute:TEST_ROUTE]] autorelease];
You need to retain ownership of the Route object passed into the initWithRoute method:
Either:
-(id)initWithRoute:(Route *)route{
self = [super init];
r = [route retain];
return self;
}
or:
-(id)initWithRoute:(Route *)route{
self = [super init];
self.r = route;
return self;
}
It looks like you're not using ARC, but you're also not retaining route in your init method. Change this:
r = route;
to this:
r = [route retain];

Singleton not updating variable immediately

I have a singleton here is the header file:
#import <Foundation/Foundation.h>
#interface Shared : NSObject
{
NSString *messages;
}
#property (nonatomic, retain) NSString *messages;
+ (Shared*)sharedInstance;
#end
Here is the implementation:
#import "Shared.h"
static Shared* sharedInstance;
#implementation Shared
#synthesize messages;
+ (Shared*)sharedInstance
{
if ( !sharedInstance)
{
sharedInstance = [[Shared alloc] init];
}
return sharedInstance;
}
- (id)init
{
self = [super init];
if ( self )
{
messages = [[NSString alloc] init];
}
return self;
}
#end
The problem is when the I use
[Shared sharedInstance].messages = someVariable;
I can use
NSLog([Shared sharedInstance].messages);
and it shows the right output, but when i check from another class, NSLog doesn't show any output. I have the NSLog in the viewDidLoad method of another class, so when I click a button to go to the next view, it should output the value of the string, but it only works the second time. If the variable is set to dog, first it outputs nothing, then when I close the view and try again, it outputs dog. however, if I then change the variable to cat, it will output dog, and on the next attempt, output cat. I want it to update immediately, rather than remain one behind all the time.
EDIT: Here's the code from the other classes
This particular section is from a view controller class in the method
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//Omitted, just preparing the DB, and emptying the array.
if ([db open])
{
FMResultSet *s = [db executeQueryWithFormat:#"SELECT ShabadID FROM Shabad WHERE Gurmukhi LIKE %#", currentLine];
while ([s next])
{
lineID = [s intForColumn:#"ShabadID"];
}
s = [db executeQueryWithFormat:#"SELECT Gurmukhi, ShabadID FROM Shabad WHERE ShabadID LIKE %i", lineID];
while ([s next])
{
//NSLog([s stringForColumn:#"Gurmukhi"]);
[paragraphArray addObject:[s stringForColumn:#"Gurmukhi"]];
}
Text = #"";
for (int i = 0; i<[paragraphArray count]; i++)
{
Text = [Text stringByAppendingFormat:#"%#\n", [paragraphArray objectAtIndex:i]];
}
[Shared sharedInstance].messages = Text;
}
Then in the another class, where I want the text to appear, in the viewDidLoad method,
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog([Shared sharedInstance].messages);
UITextView *myUITextView = [[UITextView alloc] initWithFrame:CGRectMake(0,30,310,450)];
myUITextView.text = [Shared sharedInstance].messages;
myUITextView.textAlignment = NSTextAlignmentCenter;
myUITextView.textColor = [UIColor blackColor];
myUITextView.font = [UIFont fontWithName:#"GurbaniLipiLight" size:24];
[myUITextView setBackgroundColor:[UIColor clearColor]];
myUITextView.editable = NO;
myUITextView.scrollEnabled = YES;
[ScrollerView addSubview:myUITextView];
}
Sure the NSLog doesn't show up right, but neither does the text in the textview, it does the same thing the NSLog does.
There is an assumption here about what order things happen in that's not quite right. Assuming there's a segue involved in this, didSelectRowAtIndexPath: is called after the new view controller is prepared but before it's displayed. Moving code to viewWillAppear: or viewDidAppear: delays execution until after the calling controller has set new data.
The other approach for communication between controllers that use a segue, is to use prepareForSegue: in the first controller to set data that the second controller needs. That way it should be available when the view is loaded.

NSCFArray leak in the NSMutablearray allocation

I am getting the leak at this allocation
filteredListContent = [[NSMutableArray alloc] initWithCapacity:[showList count]];
CODE:
-(void)reloadTable
{
EventListAppDelegate *appDelegate;
UIApplication app = [UIApplication sharedApplication];
appDelegate = (EventListAppDelegate *)[app delegate];
contactList = [appDelegate getAllContactsList];
inviteeList = [appDelegate getInviteeListForEvent:event.primaryKey];
if (isInvited == YES)
{
showList = [appDelegate getInviteeListForEvent:event.primaryKey];
}
else
{
showList = [appDelegate getAllContactsList];
}
filteredListContent = [[NSMutableArray alloc] initWithCapacity:
[showList count]];
[filteredListContent addObjectsFromArray: showList];
[self organizeContactItemsIntoIndexes];
self.title = [event.name capitalizedString];
[self getToolbar];
[theTableView reloadData];
}
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
[filteredListContent removeAllObjects];
ContactDTO *currentElement;
NSRange range;
for (currentElement in showList)
{
range = [currentElement.lastName rangeOfString:searchText
options:NSCaseInsensitiveSearch];
if(range.location == 0)
{
[filteredListContent addObject:currentElement];
}
}
[self organizeContactItemsIntoIndexes];
[theTableView reloadData];
}
- (void)dealloc
{
[filteredListContent release];
[super dealloc];
}
Your code will allocate a new instance of filteredListContent every time reloadTable is called, which will usually happen several times during the lifetime of your application. This causes a leak because the old instances are not released.
The best (and easiest) way to fix it would be to make filteredListContent a retain property:
in your class header:
#property (nonatomic, retain) NSMutableArray * filteredListContent;
in your reloadTable method:
self.filteredListContent = [NSMutableArray arrayWithCapacity:[showList count]];
Note the use of self. in the second code snippet. That syntax informs Cocoa that it should use the property accessor to set the value of filteredListContent, which will then send the appropriate retain and release messages for you.
You've posted three nearly-identical questions pertaining to memory leaks. It might be helpful for you to read through Apple's Memory Management Programming Guide.