Received Memory Warning in ARC

893 Views Asked by At

I am making a call to syncWithCalendar and after events are successfully added, I get low memory warning and app terminates with "Received Low Memory" warning. The events generated and saved in calendar are more than 50. I tried using instruments but I am not able to find the code where memory leak occurs and also through live bytes that show in instruments I am not able to track the code that is causing the leak. Can anyone please help me solve this issue.

- (void)syncWithCalendar
{
    @autoreleasepool {
        [self deleteEventsIfExist];

        NSMutableDictionary *dictionary = [util readPListData];
        NSMutableArray *courses = [util getCourses];
        __block NSMutableArray *lessons;
        __block NSMutableDictionary *lesson;
        NSString *studentID = [util getProgramDetails].studentId;
        NSString *programName = [util getProgramDetails].programName;

        double offset[] = {0, 0, -300, -900, -1800, -3600, -7200, -86400, -172800};

        __block NSString *startDateString = @"", *endDateString = @"";
        NSTimeInterval relativeOffsetValue = 0;
        int index = [[dictionary objectForKey:@"event-alert-option"] intValue];

        relativeOffsetValue = offset[index];

        NSDateFormatter *formatter;
        formatter = [[NSDateFormatter alloc] init];
        [formatter setDateFormat:@"MM/dd/yyyy HH:mm:ss"];
        [formatter setDateFormat:@"MM/dd/yyyy"];

        NSString *currentDateString = [NSString stringWithFormat:@"%@ 09:00:00", [formatter        stringFromDate:[NSDate date]]];
        [formatter setDateFormat:@"MM/dd/yyyy HH:mm:ss"];

        NSDate *currentDate = [formatter dateFromString:currentDateString];

        EKEventStore *eventStore = [[EKEventStore alloc] init];

        if([eventStore respondsToSelector:@selector(requestAccessToEntityType:completion:)]) {
            // iOS 6 and later
            [eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
                if (granted){
                    //---- codes here when user allow your app to access theirs' calendar.

                    dispatch_async(dispatch_get_main_queue(), ^{
                        // Event creation code here.
                        for (int i=0; i<[courses count]; i++)
                        {
                            @autoreleasepool {
                                lessons = [[courses objectAtIndex:i] objectForKey:@"lessons"];
                                for (int j=0; j<[lessons count]; j++)
                                {
                                    @autoreleasepool {
                                        lesson = [lessons objectAtIndex:j];
                                        NSString *title = nil;
                                        title = [NSString stringWithFormat:@"%@ %@-Complete %@ lesson",studentID,programName,[lesson objectForKey:@"lesson-name"]];

                                        if ([[lesson objectForKey:@"actual-exam-date"] isEqualToString:@"00/00/0000"])
                                        {
                                            startDateString = [NSString stringWithFormat:@"%@ %@", [lesson objectForKey:@"plan-exam-date"], @"09:00:00"];
                                            endDateString = [NSString stringWithFormat:@"%@ %@", [lesson objectForKey:@"plan-exam-date"], @"18:00:00"];
                                        }
                                        else
                                        {
                                            if ([[lesson objectForKey:@"retake-actual-date"] isEqualToString:@"00/00/0000"])
                                            {
                                                startDateString = [NSString stringWithFormat:@"%@ %@", [lesson objectForKey:@"retake-plan-date"], @"09:00:00"];
                                                endDateString = [NSString stringWithFormat:@"%@ %@", [lesson objectForKey:@"retake-plan-date"], @"18:00:00"];
                                            }
                                        }

                                        if (!([startDateString isEqualToString:@""] && [endDateString isEqualToString:@""]))
                                        {
                                            EKEvent *event = [EKEvent eventWithEventStore:eventStore];
                                            event.title=title;
                                            event.startDate = [formatter dateFromString:startDateString];
                                            event.endDate = [formatter dateFromString:endDateString];
                                            event.allDay = NO;
                                            if (index != 0)
                                            {
                                                event.alarms = [NSArray arrayWithObjects:[EKAlarm alarmWithRelativeOffset:relativeOffsetValue], nil];
                                            }
                                            [event setCalendar:[eventStore defaultCalendarForNewEvents]];
                                            // Compare current date to event start date, if start date has been passed then preventing to sync with calendar
                                            NSComparisonResult result = [event.startDate compare:currentDate];
                                            if (result != NSOrderedAscending)
                                            {
                                                NSError *err = nil;
                                                [eventStore saveEvent:event span:EKSpanThisEvent commit:YES error:&err];
                                                if (err) {
                                                    NSLog(@"event not saved .. error = %@",err);
                                                } else {
                                                    NSLog(@"event added successfully");
                                                }
                                            }
                                        }
                                    } // autoreleasepool
                                } // lessons for loop
                            } // autoreleasepool

                        } // courses for loop
                        [self hideModal];
                    });
                }else
                {
                    //----- codes here when user NOT allow your app to access the calendar.
                    //           [self performSelectorOnMainThread:@selector(hideModal) withObject:nil waitUntilDone:NO];
                }
            }];
        } else {
            // sync calendar for <iOS6

        }

    } // autoreleasepool
}  


- (void)deleteEventsIfExist
{  
    @autoreleasepool {  
        NSMutableArray *courses = [util getCourses];  
        __block NSMutableArray *lessons;  
        __block NSMutableDictionary *lesson;  
        NSString *studentID = [util getProgramDetails].studentId;   
        NSString *programName = [util getProgramDetails].programName;   

        EKEventStore* store = [[EKEventStore alloc] init];
        dispatch_async(dispatch_get_main_queue(), ^{  
            // Event creation code here.  
            NSDate* endDate =  [NSDate dateWithTimeIntervalSinceNow:[[NSDate distantFuture] timeIntervalSinceReferenceDate]];  
            NSPredicate *fetchCalendarEvents = [store predicateForEventsWithStartDate:[NSDate date] endDate:endDate calendars:store.calendars];  

            NSArray *allEvents = [store eventsMatchingPredicate:fetchCalendarEvents];

            for (int i=0; i<[courses count]; i++)
            {
                @autoreleasepool {
                    lessons = [[courses objectAtIndex:i] objectForKey:@"lessons"];
                    for (int j=0; j<[lessons count]; j++)
                    {
                        @autoreleasepool {
                            lesson = [lessons objectAtIndex:j];

                            NSString *oldEventSubtitle = [NSString stringWithFormat:@"%@ %@-Complete %@ lesson",studentID,programName,[lesson objectForKey:@"lesson-name"]];
                            for (EKEvent *e in allEvents)
                            {
                                if ( [oldEventSubtitle isEqualToString:e.title])
                                {
                                    NSError* error = nil;
                                    [store removeEvent:e span:EKSpanThisEvent commit:YES error:&error];
                                    NSLog(@"deleting events");
                                }
                            }
                        } // autoreleasepool

                    } // lessons

                } // autoreleasepool

            } // courses
        });

    } // autoreleasepool
}
3

There are 3 best solutions below

0
On

It's a rough guess, but it seems the asynchronous invocations may lead to troubles.

In order to test this, just use dispatch_sync instead of dispatch_async and examine the memory consumption. If this leads to an improvement, then a solution is in sight, which involves to re-factor your current asynchronous "parallel" approach and turn it into an appropriate asynchronous "serial" approach or an complete synchronous approach.

This may also require to "serialize" all invocations of this asynchronous method:

[eventStore requestAccessToEntityType:EKEntityTypeEvent 
                           completion:^(BOOL granted, NSError *error) {
   ...
}]
0
On

You need to clear the cache when you are receiving memory warning, use this method it will help you.

-(void)applicationDidReceiveMemoryWarning:(UIApplication *)application {

    [[NSURLCache sharedURLCache] removeAllCachedResponses];
}
0
On

This is how I made a call to syncWithCalendar function

if([eventStore respondsToSelector:@selector(requestAccessToEntityType:completion:)]) {
// iOS 6 and later
[eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted,
NSError *error) {
if (granted){
dispatch_async(dispatch_get_main_queue(), ^{
[self syncWithCalendar];
});
} else {
// calendar access not granted     
}
}];
}

And in syncWithCalendar function everything remains same except the line of code that was creating the crash/memory issue. Below is the incorrect line of code that I was using earlier

// wrong
[self.eventstore saveEvent:event span:EKSpanThisEvent commit:YES error:&err];

The correct way to save event: (Note: I didn't require event identifier in my case)

// correct
[self.eventstore saveEvent:event span:EKSpanThisEvent commit:NO error:&err]; 

and then use [self.eventstore commit:NULL] after all the events are saved. This stopped the crash in my case. Hope this post will help other get the solution. Thanks !!!!