I am getting a leak on the helper.offlineQueue line where I alloc a NSOperationQueue object. The problem is, I am not quite sure where to release it in this method...
+ (void)flushOfflineQueue
{
// TODO - if an item fails, after all items are shared, it should present a summary view and allow them to see which items failed/succeeded
// Check for a connection
if (![self connected])
return;
// Open list
NSMutableArray *queueList = [self getOfflineQueueList];
// Run through each item in the quietly in the background
// TODO - Is this the best behavior? Instead, should the user confirm sending these again? Maybe only if it has been X days since they were saved?
// - want to avoid a user being suprised by a post to Twitter if that happens long after they forgot they even shared it.
if (queueList != nil)
{
SHK *helper = [self currentHelper];
if (helper.offlineQueue == nil)
helper.offlineQueue = [[NSOperationQueue alloc] init];
SHKItem *item;
NSString *sharerId, *uid;
for (NSDictionary *entry in queueList)
{
item = [SHKItem itemFromDictionary:[entry objectForKey:#"item"]];
sharerId = [entry objectForKey:#"sharer"];
uid = [entry objectForKey:#"uid"];
if (item != nil && sharerId != nil)
[helper.offlineQueue addOperation:[[[SHKOfflineSharer alloc] initWithItem:item forSharer:sharerId uid:uid] autorelease]];
}
// Remove offline queue - TODO: only do this if everything was successful?
[[NSFileManager defaultManager] removeItemAtPath:[self offlineQueueListPath] error:nil];
}
}
Thanks!
I expect you should just do:
helper.offlineQueue = [[[NSOperationQueue alloc] init] autorelease];
The SHK object itself should be retaining the queue and will release it when it is done. The reference you are holding due to the alloc can be released immediately.
Related
I am having an unexpected issue with a crash on my app which I am specifically struggling to debug because it occurs in background at a time determined by the iOS system. I have some capitalised comments to the code which show where the issue is being back traced to. I hope this is clear.
I believe it has to do with object deallocation.
I have tried using the __block before initialising the object but
this has not helped.
I have also tried dispatching the lines of code
in error to the main queue but that has not helped.
The actual crash is listed as AppName: __66-[BackgroundUpdateController initiateBackgroundHealthkitObservers]_block_invoke_5 + 160
I apologise if some of the code does not fit standard formatting and conventions. I am self taught from a variety of places and so do not have proper experience with code format.
Many Thanks
#import "BackgroundUpdateController.h"
NSUserDefaults *backgroundDefaults;
#implementation BackgroundUpdateController
-(id)init{
backgroundDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.HeartAnalyzer"];
return [super init];
}
-(void)initiateBackgroundHealthkitObservers{
// Check we should be running here
if(([backgroundDefaults integerForKey:#"sleepAnalysisEnabled"] != 1) || (![backgroundDefaults boolForKey:#"AutomaticSleepAdd"])) return;
// Initiate some variables, Use __block to ensure the backgroundHealthStore object does not get deallocated
__block HKHealthStore *backgroundHealthStore = [[HKHealthStore alloc] init];
HKQuantityType *activeEnergy = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned];
// Enable background delivery of active energy data from HealthKit
[backgroundHealthStore enableBackgroundDeliveryForType:activeEnergy frequency:HKUpdateFrequencyHourly withCompletion:^(BOOL success, NSError *error) {
}];
// Now setup an HKOberverQuery which triggers hourly if there are new active energy data points in HealthKit
HKObserverQuery *query = [[HKObserverQuery alloc] initWithSampleType:activeEnergy predicate:nil updateHandler:^(HKObserverQuery *query, HKObserverQueryCompletionHandler completionHandler, NSError *error) {
UIApplicationState state = [[UIApplication sharedApplication] applicationState];
if (state == UIApplicationStateBackground || state == UIApplicationStateInactive){// Only run when app is not in foreground
// Load some more variables with checks to ensure they are valid objects
NSDate *previousSavedDate = [backgroundDefaults objectForKey:#"DateBackgroundSleepLastSaved"];
if(previousSavedDate == nil) previousSavedDate = [NSDate distantPast];
NSDate *lastSleepCheck = [backgroundDefaults objectForKey:#"LastSleepCheck"];
if(lastSleepCheck == nil) lastSleepCheck = [NSDate distantPast];
// If the last save date was long enough ago and the last sleep check was long enough ago, proceed
if(([previousSavedDate timeIntervalSinceNow] < -(3600*18)) && ([lastSleepCheck timeIntervalSinceNow] < -(3600*2))){
[backgroundDefaults setObject:[NSDate date] forKey:#"LastSleepCheck"];
[backgroundDefaults setBool:NO forKey:#"BackgroundSleepFound"];
SleepTimesCalculator *sleepClass = [[SleepTimesCalculator alloc] init];
[sleepClass calculateSleepTimes:^{
NSLog(#"Background sleep time calculations complete");
if([backgroundDefaults boolForKey:#"BackgroundSleepFound"]){// Only continue is a sleep time was found
__block NSMutableArray *savedSleepObjects = [backgroundDefaults valueForKey:#"SleepTimesDataBase"];
if(savedSleepObjects.count > 0){
__block NSMutableDictionary *sleepObject = [savedSleepObjects objectAtIndex:0]; // THE __BLOCK USED TO PREVENT THE OBJECT BEING DEALLOCATED, STILL SEEMS TO BE BASED ON THE CRASH
NSDate *sleepStart = [NSDate dateWithTimeIntervalSinceReferenceDate:[[sleepObject valueForKey:#"CalculatedSleepTime"]integerValue]];// Get the sleep time start date object
NSDate *sleepEnd = [NSDate dateWithTimeIntervalSinceReferenceDate:[[sleepObject valueForKey:#"CalculatedWakeTime"]integerValue]];
NSInteger sleepSavedToHealth = [[sleepObject valueForKey:#"SavedToHealth"] integerValue];// Check its not already been saved by some other element of the app
if(sleepSavedToHealth != 1){
HKCategorySample *sleepSample = [HKCategorySample categorySampleWithType:[HKCategoryType categoryTypeForIdentifier:HKCategoryTypeIdentifierSleepAnalysis] value:1 startDate:sleepStart endDate:sleepEnd];// Generate sleep object for HealthKit
[backgroundHealthStore saveObject:sleepSample withCompletion:^(BOOL success, NSError *error) {
if (!success) NSLog(#"Uncommon Error! saveObject:sleepSample");
else{
dispatch_async(dispatch_get_main_queue(), ^{// DISPATCH TO MAIN QUEUE AN ATTEMPTED FIX FOR CRASH
sleepObject = [savedSleepObjects objectAtIndex:0];// Choose the most recent sleep time to save
[sleepObject setValue:[NSNumber numberWithInteger:1] forKey:#"SavedToHealth"];// THIS IS WHERE THE 'Last Exception Backtrace (0)' ENDS UP
[savedSleepObjects replaceObjectAtIndex:0 withObject:sleepObject];// Replace the object which now has the 'Saved' tag
[backgroundDefaults setObject:[NSDate date] forKey:#"DateBackgroundSleepLastSaved"];// Save the data of the last time we reached this point
[backgroundDefaults setObject:savedSleepObjects forKey:#"SleepTimesDataBase"];// Save the sleep times back to the database
});
}
}];
}
completionHandler();// Call the completion handler as we've been throught the sleepObjects array
}
else completionHandler();// Call the completion handler anyway
}
else completionHandler();// Call the completion handler anyway
}];
}
else completionHandler();
}
}];
[backgroundHealthStore executeQuery:query];// Execute the HealthKit healthstore query
}
#end
Prefixing __block does not guarantees existence of an object for #"CalculatedSleepTime" key in sleepObject
I think you have misinterpreted how __block works. This will be a great guide.
On a quick overview of the code, it seems like [sleepObject valueForKey:#"CalculatedSleepTime"] is returning nil & without a nullability check you are trying to extract the integerValue
So, consider:
NSMutableDictionary *sleepObject = [savedSleepObjects objectAtIndex:0];
id calculatedSleepTime = [sleepObject valueForKey:#"CalculatedSleepTime"];
if(calculatedSleepTime){
NSDate *sleepStart = [NSDate dateWithTimeIntervalSinceReferenceDate:[calculatedSleepTime integerValue]];
}
And it looks like you also don't require the __block prefix in HKHealthStore *backgroundHealthStore = [[HKHealthStore alloc] init];
I've a messages app and I started to create a widget.
Updating the core data with the new messages happens when user open the app.
My wish is when:
- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler
called I will get the UIViewController and call the my get messages thread.
Linking the UIViewController against my widget target gave me an error:
'sharedApplication' is unavailable....
So I canceled it.
What I'm trying to achieve:
1. widgetPerformUpdateWithCompletionHandler is being called
2. Application start the get messages thread/method
3. when it finish, it send back data to the widget using NSUserDefaults
My code:
1:
- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler
{
// Perform any setup necessary in order to update the view.
[self startGetMessages];
// If an error is encountered, use NCUpdateResultFailed
// If there's no update required, use NCUpdateResultNoData
// If there's an update, use NCUpdateResultNewData
completionHandler(NCUpdateResultNewData);
}
2:
- (void)startGetMessages
{
NSLog(#"%s", __PRETTY_FUNCTION__);
NSBundle *deviceBundle = [NSBundle mainBundle];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:deviceBundle];
id MainController = [storyboard instantiateViewControllerWithIdentifier:#"MainTableViewController"];
SEL getMessagesSelector = NSSelectorFromString(#"startGetMessages:");
if (MainController)
{
NSThread *startGetMessagesThread = [[NSThread alloc] initWithTarget:MainController
selector:getMessagesSelector
object:StringForInt(HRTableDataSourceKindUpdate)];
[startGetMessagesThread start];
}
}
3:
- (void)notifyWidgetForChanges
{
__block NSMutableDictionary *newMessages = [NSMutableDictionary new];
NSArray *results = [CoreDataPhotoRecord MR_findAllSortedBy:#"message.originalDate"
ascending:NO
withPredicate:[NSPredicate predicateWithFormat:#"(message.delete_message == %#) AND (message.type.integerValue == %d) AND (message.originalDate >= %#)",
#NO, NORMAL_MESSAGE, _notiftWidgetDate]];
NSLog(#"%s, _notiftWidgetDate: %#, newMessages.count: %d", __PRETTY_FUNCTION__, _notiftWidgetDate, newMessages.count);
[results enumerateObjectsUsingBlock:^(CoreDataPhotoRecord *photoDetails, NSUInteger idx, BOOL *stop)
{
if (photoDetails != nil && photoDetails.message != nil)
{
NSString *cleanMobile = [[ABAddressBook sharedAddressBook] getCleanMobile:photoDetails.message.mobile];
Contact *person = [[ABAddressBook sharedAddressBook] findContactWithPhoneNumber:cleanMobile];
ContactWidget *contact = [[ContactWidget alloc] init];
contact.name = (person != nil && person.name != nil && person.name.length > 0) ? person.name : cleanMobile;
[newMessages setObject:contact forKey:cleanMobile];
}
}];
[SharedUtilities archiveObject:newMessages.copy forKey:MESSAGES_KEY_NEW widget:true];
[DEFAULTS_WIDGET setObject:#"111" forKey:#"111"];
[DEFAULTS_WIDGET synchronize];
newMessages = nil;
results = nil;
}
widgetDefaults = [[NSUserDefaults alloc] initWithSuiteName:WIDGET_GROUP_NAME];
Nothing is happen since the MainController in step 2 is nil.
What can I do?
The nil problem occurs because you try to access application's storyboard from widget. It's not straightforward, since the containing app and widget extension are being kept in a separate bundles. So the [NSBundle mainBundle] in step 2) is not the same bundle as the one in your app.
Possible solutions include:
including the app's Main.storyboard in extensions bundle either via adding it to Copy Bundle resources list at widget's target Build Phases tab or just adding widget target to Main.storyboard list of Target Membership
moving the code responsible for getting the messages from MainController startGetMessages: into a shared framework that will be accessible both from the app and the widget, preferably into a dedicated object.
The second one is way better. As a rule of thumb it's best to follow SOLID principles when doing the object-oriented programming, where S stands for single responsibility. It should not be a responsibility of view controller to provide the messages fetching system-wide. Creating a dedicated object that will have only one job - to get messages - and sharing it across the targets is a way to go.
Please consult the docs for the detailed explanation on how to create the shared framework: https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW1
I have created a FacebookManager singleton that gets called on a background thread when my app launches. Everything is working just fine with the facebook manager the singleton, the app etc. However, when the app first launches, it is quite a few seconds before it is useful because the facebook manager has not finished doing its thing yet. So what I want to do, is use NSKeyedArchiver to save the facebookManager and all its dictionaries so that upon launch, the app has a navigable interface while the facebook data is being updated in the background. Make sense?
All within the FacebookManager.m, first, when the manager is done updating the friends dictionaries, etc, I call the method that saves the data:
- (BOOL)saveFacebookData
{
// returns success or failure
NSString *path = [self archivePath]; // just a helper method
return [NSKeyedArchiver archiveRootObject:self toFile:path];
}
Then in init, I am trying this, which doesn't seem to work. :
-(id)init
{
self = [super init];
NSString *path = [self archivePath];
self = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
// If the manager hadn't been saved previously, create a new new one
if (!self) {
if (_idsByNameDict == nil) {
_idsByNameDict = [[NSMutableDictionary alloc] init];
}
if (_photosByNameDict == nil) {
_photosByNameDict = [[NSMutableDictionary alloc] init];
}
if (_installedByNameDict == nil) {
_installedByNameDict = [[NSMutableDictionary alloc] init];
}
if (_allFriendsArray == nil) {
_allFriendsArray = [[NSArray alloc] init];
}
basicPermissions = NO;
extendedPermissions = NO;
// Create synchronous dispatch queue for all facebook activity
if (_facebookUpdateQueue == nil) {
_facebookUpdateQueue = dispatch_queue_create("com.facebookUpdateQueue", NULL);
}
}
I think my general strategy is sound but I am tripping over how to actually grab the archived version of the manager during init! Any advice?
Your class needs to implement <NSCoding> and both of its methods encodeWithCoder: to archive all of your property values and initWithCoder: to in archive them. Make sure to call super in the implementations. Generally, the class using the archived class would know about the archiving but you could hide that knowledge in init by using initForReadingWithData: to create your NSKeyedUnarchiver and then calling [self initWithCoder:...];.
I am working with Blackraccoon FTP client to do FTP operations,working with ARC.but i am getting leaks in instruments.
but there were no leaks in sample application here is my code
BRRequestCreateDirectory *createEventDir = [BRRequestCreateDirectory initWithDelegate:nil];
//NSString *EventCode = [[NSUserDefaults standardUserDefaults] stringForKey:kEventCodeKey];
createEventDir.path = #"/12341234";
createEventDir.hostname = #"media.example.com/httpdocs/events/";
createEventDir.username = #"badboy";
createEventDir.password = #"hai!";
createEventDir.tag = 103;
[createEventDir start];
createEventDir = nil;
sample code from FTP clent Blackraccoon FTP client
leaks showing in instruments like,but i am using ARC
can any one help me to solve this prob..
I ported and heavily modified BlackRaccoon. It is designed to use delegates. In other words, delegates are required.
BRRequestCreateDirectory *createEventDir = [BRRequestCreateDirectory initWithDelegate:nil];
//NSString *EventCode = [[NSUserDefaults standardUserDefaults] stringForKey:kEventCodeKey];
createEventDir.path = #"/12341234";
createEventDir.hostname = #"media.example.com/httpdocs/events/";
createEventDir.username = #"badboy";
createEventDir.password = #"hai!";
createEventDir.tag = 103;
[createEventDir start];
createEventDir = nil;
Is incorrect. It starts a lot of things going and then deletes the object - the action is undefined.
Instead you need something as indicated in the code that I provided (that doesn't leak).
First, the class that uses the ftp needs to have BRRequestDelegate to indicate the delegate protocol.
- (IBAction) createDirectory:(id)sender
{
//----- createEventDir must be a variable in your class...
createEventDir = [BRRequestCreateDirectory initWithDelegate: self];
createEventDir.path = #"/12341234;
createEventDir.hostname = #"media.example.com/httpdocs/events/";
createEventDir.username = #"badboy";
createEventDir.password = #"hai!";
[createEventDir start];
//----- createEventDir MUST NOT BE DELETED OR SET TO NIL HERE
}
Then you have to have the two delegates (at a minimum):
-(void) requestCompleted: (BRRequest *) request
{
//----- handle Create Directory
if (request == createEventDir)
{
NSLog(#"%# completed!", request);
//----- everything is done, NOW you can set it to nil
createEventDir = nil;
}
}
-(void) requestFailed: (BRRequest *) request
{
if (request == createEventDir)
{
NSLog(#"%#", request.error.message);
//----- everything is done, NOW you can set it to nil
createEventDir = nil;
}
}
If you go back and look at my test code you'll see how things work. If you are still having issues, post in the issues on http://github.com/lloydsargent/BlackRaccoon
Hopefully this will get you past your problem.
Analyzer keeps saying that I have a leak in the line with the * at the beginning and end, how would I fix this leak so it gets rid of the warning?
+ (void)flushOfflineQueue
{
// TODO - if an item fails, after all items are shared, it should present a summary view and allow them to see which items failed/succeeded
// Check for a connection
if (![self connected])
return;
// Open list
NSMutableArray *queueList = [self getOfflineQueueList];
// Run through each item in the quietly in the background
// TODO - Is this the best behavior? Instead, should the user confirm sending these again? Maybe only if it has been X days since they were saved?
// - want to avoid a user being suprised by a post to Twitter if that happens long after they forgot they even shared it.
if (queueList != nil)
{
SHK *helper = [self currentHelper];
if (helper.offlineQueue == nil)
***helper.offlineQueue = [[NSOperationQueue alloc] init];***
SHKItem *item;
NSString *sharerId, *uid;
for (NSDictionary *entry in queueList)
{
item = [SHKItem itemFromDictionary:[entry objectForKey:#"item"]];
sharerId = [entry objectForKey:#"sharer"];
uid = [entry objectForKey:#"uid"];
if (item != nil && sharerId != nil)
[helper.offlineQueue addOperation:[[[SHKOfflineSharer alloc] initWithItem:item forSharer:sharerId uid:uid] autorelease]];
}
// Remove offline queue - TODO: only do this if everything was successful?
[[NSFileManager defaultManager] removeItemAtPath:[self offlineQueueListPath] error:nil];
}
}
Thanks!
When you use properties they will often perform the proper memory management. In your situation you need to autorelease the class you set.
helper.offlineQueue = [[[NSOperationQueue alloc] init] autorelease];