I have and application which give data usage in intervals. I want to detect if the device has been rebooted before using the data usage. I tried using mach_absolute_time() but i could not understand anything.Is it possible?
You can get the system uptime like this:
let systemUptime = NSProcessInfo.processInfo().systemUptime; // swift
NSTimeInterval timeInterval = [NSProcessInfo processInfo].systemUptime; //Objective c
You can store it in NSUserDefaults and use it to determine if a reboot occurred.
Please also check this thread for more information.
Swift 3:
let systemUptime: TimeInterval = ProcessInfo.processInfo.systemUptime
Here is one I made. It takes the current time in GMT and the time since last reboot to extrapolate a date for when the device was last restarted. Then it keeps track of this date in memory using NSUserDefaults. Enjoy!
#define nowInSeconds CFAbsoluteTimeGetCurrent()//since Jan 1 2001 00:00:00 GMT
#define secondsSinceDeviceRestart ((int)round([[NSProcessInfo processInfo] systemUptime]))
#define storage [NSUserDefaults standardUserDefaults]
#define DISTANCE(__valueOne, __valueTwo) ((((__valueOne)-(__valueTwo))>=0)?((__valueOne)-(__valueTwo)):((__valueTwo)-(__valueOne)))
+(BOOL)didDeviceReset {
static BOOL didDeviceReset;
static dispatch_once_t onceToken;
int currentRestartDate = nowInSeconds-secondsSinceDeviceRestart;
int previousRestartDate = (int)[((NSNumber *)[storage objectForKey:#"previousRestartDate"]) integerValue];
int dateVarianceThreshold = 10;
dispatch_once(&onceToken, ^{
if (!previousRestartDate || DISTANCE(currentRestartDate, previousRestartDate) > dateVarianceThreshold) {
didDeviceReset = YES;
} else {
didDeviceReset = NO;
}
});
[storage setObject:#(currentRestartDate) forKey:#"previousRestartDate"];
[storage synchronize];
return didDeviceReset;
}
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];
Has anyone else noticed a problem with complication entries not updating properly. I've just added some initial support to my app, but noticed that they weren't displaying what i expected them to display. For example's sake, and for ease of testing this issue quickly I'd create a timeline
A -> B -> C
0s 10s 20s
Yet all I'd see was complication entry A staying around past the time that B and C should have displayed.
My normal app itself isn't set to create regularly spaced complications like this, it has many aspects of timers that it exposes that can be set by the user, but one such aspect just allows the user to start multiple timers at once, which all will finish after the user defined durations they choose. Unlike the iOS clock app's timer you're able to specify the timer durations in seconds, and so its perfectly possible that 2 timers would finish within seconds of each other, on the whole though its more likely that they'll be minutes apart. Also there shouldn't be too many complication entries being added, though other more complicated aspects of my app could easily add 10s or even ~100 complication entries depending on how complex a task the user has setup within it. For now though, this simpler example is easier to discuss and test.
I updated Xcode to the latest version (7.3.2) with no improvements, and sent a build to my actual phone and watch and again no improvement. Until once it did work. On further debugging I discovered that i could make the timeline behave itself by simply lowering my watch (to turn off the screen) and then wake it up again, all whilst it was mid executing my timeline. Having done this the timeline would then work properly from then on.
I've created a test app to demonstrate the problem, which does reproduce the problem fully, so I'm going to send it to apple on a bug report. Just thought i'd see if anyone else had noticed this issue.
Also when my test app executes i get the following logging output with an error that doesn't make sense
-[ExtensionDelegate session:didReceiveUserInfo:]:67 - complication.family=1 in activeComplications - calling reloadTimelineForComplication
-[ComplicationController getTimelineStartDateForComplication:withHandler:]:43 - calling handler for startDate=2016-06-15 22:08:26 +0000
-[ComplicationController getTimelineEndDateForComplication:withHandler:]:73 - calling handler for endDate=2016-06-15 22:08:46 +0000
-[ComplicationController getCurrentTimelineEntryForComplication:withHandler:]:148 - calling handler for entry at date=2016-06-15 22:08:26 +0000
-[ComplicationController getTimelineEntriesForComplication:afterDate:limit:withHandler:]:202 - adding entry at date=2016-06-15 22:08:36 +0000; with timerEndDate=2016-06-15 22:08:46 +0000 i=1
getTimelineEntriesForComplication:afterDate:limit:withHandler: -- invalid entries returned. (1 entries before start date 2016-06-15 22:08:46 +0000). Excess entries will be discarded.
The relevant information from this log is as follows
getTimelineStartDateForComplication - calling handler for startDate=22:08:26
getTimelineEndDateForComplication - calling handler for endDate=22:08:46
getCurrentTimelineEntryForComplication - calling handler for entry at date=22:08:26
getTimelineEntriesForComplication:afterDate - adding entry at date=22:08:36
getTimelineEntriesForComplication:afterDate:limit:withHandler: -- invalid entries returned. (1 entries before start date 22:08:46). Excess entries will be discarded.
Which you can see in the error from the system at the end that it is using the start date of 22:08:46, which was actually what I told Clockkit was my timeline's endDate, NOT the startDate. I'm not sure if this is related to the behaviour i'm seeing as I see the same error when it works after I hide/show the screen.
I've put a video of this behaviour in my test app online here. The details of this test app are as follows
Full code that should just run in the relevant simulators is available here, the relevant complication modules are also listed here for reference.
In my extension delegate, i receive userInfo from the iOS app and schedule a reload of my complication timeline
ExtensionDelegate.m
- (void)session:(WCSession *)session didReceiveUserInfo:(NSDictionary<NSString *, id> *)userInfo
{
DbgLog(#"");
WKExtension *extension = [WKExtension sharedExtension];
DbgLog(#"self=%p; wkExtension=%p; userInfo=%#", self, extension, userInfo);
self.lastReceivedUserInfo = userInfo;
CLKComplicationServer *complicationServer = [CLKComplicationServer sharedInstance];
for (CLKComplication *complication in complicationServer.activeComplications)
{
DbgLog(#"complication.family=%d in activeComplications - calling reloadTimelineForComplication", complication.family);
[complicationServer reloadTimelineForComplication:complication];
}
}
Then in my ComplicationController are the following methods to handle the complication side of things
ComplicationController.m
#define DbgLog(fmt, ...) NSLog((#"%s:%d - " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#interface ComplicationController ()
#end
#implementation ComplicationController
#pragma mark - Timeline Configuration
- (void)getSupportedTimeTravelDirectionsForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTimeTravelDirections directions))handler
{
handler(CLKComplicationTimeTravelDirectionForward|CLKComplicationTimeTravelDirectionBackward);
}
- (void)getTimelineStartDateForComplication:(CLKComplication *)complication withHandler:(void(^)(NSDate * __nullable date))handler
{
NSDate *startDate;
WKExtension *extension = [WKExtension sharedExtension];
assert(extension.delegate);
assert([extension.delegate isKindOfClass:[ExtensionDelegate class]]);
ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)extension.delegate;
if (extensionDelegate.lastReceivedUserInfo)
{
NSDictionary *userInfo = extensionDelegate.lastReceivedUserInfo;
startDate = [userInfo objectForKey:#"date"];
}
DbgLog(#"calling handler for startDate=%#", startDate);
handler(startDate);
}
- (NSDate*)getTimelineEndDate
{
NSDate *endDate;
WKExtension *extension = [WKExtension sharedExtension];
assert(extension.delegate);
assert([extension.delegate isKindOfClass:[ExtensionDelegate class]]);
ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)extension.delegate;
if (extensionDelegate.lastReceivedUserInfo)
{
NSDictionary *userInfo = extensionDelegate.lastReceivedUserInfo;
NSDate *startDate = [userInfo objectForKey:#"date"];
NSNumber *duration = [userInfo objectForKey:#"duration"];
NSNumber *count = [userInfo objectForKey:#"count"];
NSTimeInterval totalDuration = duration.floatValue * count.floatValue;
endDate = [startDate dateByAddingTimeInterval:totalDuration];
}
return endDate;
}
- (void)getTimelineEndDateForComplication:(CLKComplication *)complication withHandler:(void(^)(NSDate * __nullable date))handler
{
NSDate *endDate=[self getTimelineEndDate];
DbgLog(#"calling handler for endDate=%#", endDate);
handler(endDate);
}
- (void)getPrivacyBehaviorForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationPrivacyBehavior privacyBehavior))handler {
handler(CLKComplicationPrivacyBehaviorShowOnLockScreen);
}
#pragma mark - Timeline Population
- (CLKComplicationTemplate *)getComplicationTemplateForComplication:(CLKComplication *)complication
forEndDate:(NSDate *)endDate
orBodyText:(NSString *)bodyText
withHeaderText:(NSString *)headerText
{
assert(complication.family == CLKComplicationFamilyModularLarge);
CLKComplicationTemplateModularLargeStandardBody *template = [[CLKComplicationTemplateModularLargeStandardBody alloc] init];
template.headerTextProvider = [CLKSimpleTextProvider textProviderWithText:headerText];
if (endDate)
{
template.body1TextProvider = [CLKRelativeDateTextProvider textProviderWithDate:endDate style:CLKRelativeDateStyleTimer units:NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitSecond];
}
else
{
assert(bodyText);
template.body1TextProvider = [CLKSimpleTextProvider textProviderWithText:bodyText];
}
return template;
}
- (CLKComplicationTimelineEntry *)getComplicationTimelineEntryForComplication:(CLKComplication *)complication
forStartDate:(NSDate *)startDate
endDate:(NSDate *)endDate
orBodyText:(NSString *)bodyText
withHeaderText:(NSString *)headerText
{
CLKComplicationTimelineEntry *entry = [[CLKComplicationTimelineEntry alloc] init];
entry.date = startDate;
entry.complicationTemplate = [self getComplicationTemplateForComplication:complication forEndDate:endDate orBodyText:bodyText withHeaderText:headerText];
return entry;
}
- (void)getCurrentTimelineEntryForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTimelineEntry * __nullable))handler
{
// Call the handler with the current timeline entry
CLKComplicationTimelineEntry *entry;
assert(complication.family == CLKComplicationFamilyModularLarge);
WKExtension *extension = [WKExtension sharedExtension];
assert(extension.delegate);
assert([extension.delegate isKindOfClass:[ExtensionDelegate class]]);
ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)extension.delegate;
if (extensionDelegate.lastReceivedUserInfo)
{
NSDictionary *userInfo = extensionDelegate.lastReceivedUserInfo;
NSDate *startDate = [userInfo objectForKey:#"date"];
NSNumber *duration = [userInfo objectForKey:#"duration"];
//NSNumber *count = [userInfo objectForKey:#"count"];
NSTimeInterval totalDuration = duration.floatValue;
NSDate *endDate = [startDate dateByAddingTimeInterval:totalDuration];
entry = [self getComplicationTimelineEntryForComplication:complication forStartDate:startDate endDate:endDate orBodyText:nil withHeaderText:#"current"];
}
if (!entry)
{
NSDate *currentDate = [NSDate date];
entry = [self getComplicationTimelineEntryForComplication:complication forStartDate:currentDate endDate:nil orBodyText:#"no user info" withHeaderText:#"current"];
}
DbgLog(#"calling handler for entry at date=%#", entry.date);
handler(entry);
}
- (void)getTimelineEntriesForComplication:(CLKComplication *)complication beforeDate:(NSDate *)date limit:(NSUInteger)limit withHandler:(void(^)(NSArray<CLKComplicationTimelineEntry *> * __nullable entries))handler
{
NSArray *retArray;
assert(complication.family == CLKComplicationFamilyModularLarge);
WKExtension *extension = [WKExtension sharedExtension];
assert(extension.delegate);
assert([extension.delegate isKindOfClass:[ExtensionDelegate class]]);
ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)extension.delegate;
if (extensionDelegate.lastReceivedUserInfo)
{
NSDictionary *userInfo = extensionDelegate.lastReceivedUserInfo;
NSDate *startDate = [userInfo objectForKey:#"date"];
if ([startDate timeIntervalSinceDate:date] < 0.f)
{
assert(0);
// not expected to be asked about any date earlier than our startDate
}
}
// Call the handler with the timeline entries prior to the given date
handler(retArray);
}
- (void)getTimelineEntriesForComplication:(CLKComplication *)complication afterDate:(NSDate *)date limit:(NSUInteger)limit withHandler:(void(^)(NSArray<CLKComplicationTimelineEntry *> * __nullable entries))handler
{
NSMutableArray *timelineEntries = [[NSMutableArray alloc] init];
assert(complication.family == CLKComplicationFamilyModularLarge);
WKExtension *extension = [WKExtension sharedExtension];
assert(extension.delegate);
assert([extension.delegate isKindOfClass:[ExtensionDelegate class]]);
ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)extension.delegate;
if (extensionDelegate.lastReceivedUserInfo)
{
NSDictionary *userInfo = extensionDelegate.lastReceivedUserInfo;
NSDate *startDate = [userInfo objectForKey:#"date"];
NSNumber *duration = [userInfo objectForKey:#"duration"];
NSNumber *count = [userInfo objectForKey:#"count"];
NSInteger i;
for (i=0; i<count.integerValue && timelineEntries.count < limit; ++i)
{
NSTimeInterval entryDateOffset = duration.floatValue * i;
NSDate *entryDate = [startDate dateByAddingTimeInterval:entryDateOffset];
if ([entryDate timeIntervalSinceDate:date] > 0)
{
NSDate *timerEndDate = [entryDate dateByAddingTimeInterval:duration.floatValue];
DbgLog(#"adding entry at date=%#; with timerEndDate=%# i=%d", entryDate, timerEndDate, i);
CLKComplicationTimelineEntry *entry = [self getComplicationTimelineEntryForComplication:complication forStartDate:entryDate endDate:timerEndDate orBodyText:nil withHeaderText:[NSString stringWithFormat:#"After %d", i]];
[timelineEntries addObject:entry];
}
}
if (i==count.integerValue && timelineEntries.count < limit)
{
NSDate *timelineEndDate = [self getTimelineEndDate];
CLKComplicationTimelineEntry *entry = [self getComplicationTimelineEntryForComplication:complication forStartDate:timelineEndDate endDate:nil orBodyText:#"Finished" withHeaderText:#"Test"];
[timelineEntries addObject:entry];
}
}
NSArray *retArray;
if (timelineEntries.count > 0)
{
retArray = timelineEntries;
}
// Call the handler with the timeline entries after to the given date
handler(retArray);
}
#pragma mark Update Scheduling
/*
// don't want any updates other than the ones we request directly
- (void)getNextRequestedUpdateDateWithHandler:(void(^)(NSDate * __nullable updateDate))handler
{
// Call the handler with the date when you would next like to be given the opportunity to update your complication content
handler(nil);
}
*/
- (void)requestedUpdateBudgetExhausted
{
DbgLog(#"");
}
#pragma mark - Placeholder Templates
- (void)getPlaceholderTemplateForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTemplate * __nullable complicationTemplate))handler
{
CLKComplicationTemplate *template = [self getComplicationTemplateForComplication:complication forEndDate:nil orBodyText:#"doing nothing" withHeaderText:#"placeholder"];
// This method will be called once per supported complication, and the results will be cached
handler(template);
}
#end
Perhaps you can see if you have the same problems with your own complications in your own apps.
I don't think I'm doing anything wrong, nothing that should cause this odd behaviour, just feels like a bug to me. Unfortunately its one that undermines my app which can work with quite small scale timeline entries in some cases and I'd rather not have them just not work if a user pays attention and keeps the watch screen on whilst testing it out.
Thanks for your time,
Cheers
The error about an entry before the start date is accurate, and the entry is correctly discarded.
The reason why is that getTimelineEntriesForComplication:afterDate: is meant to return future entries after the specified date. What you did was return an entry before the specified date.
The starting date for providing future entries. The dates for your timeline entries should occur after this date and be as close to the date as possible.
Without any posted code to examine, I'd guess that your beforeDate:/afterDate: conditional code is reversed.
Other issues regarding multiple timeline entries per minute:
Regarding the ten second time interval, I can point out several issues for you to consider:
The complication server will request a limited number of entries.
Providing your entries will always be ten seconds apart, and that the server requested 100 entries (either in the past or the future direction), that amounts to 1000 seconds worth (under 17 minutes). This introduces two problems in general:
Given the short span of the timeline, the timeline would need to be updated several times an hour. This is not energy efficient, and you may exhaust your daily complication budget if you extend or reload it too often.
With such a short duration, time travel is effectively useless, as rotating the digital crown could quickly travel more than 16 minutes into the (past or) future, where there would not (initially) be any further entries to display.
The complication server caches a limited number of entries.
Even if you extend your timeline, the server will eventually prune (discard) entries from an extended timeline. Again assuming a 10-second interval, the complication server would only be able to keep about 2 hours worth of entries cached. Furthermore, you have no control over which entries would be discarded.
From a time travel perspective, 5 of every 6 timeline entries would never be displayed while time traveling (since time travel changes by minutes).
This would certainly present some confusion to the user, as they would not be able to view every entry.
A note about update limits:
While you could add multiple entries per minute, you may want to rethink whether that is practical. Considering that most users won't observe most of the frequent changes, it's likely a lot of wasted effort for little gain.
Regarding energy efficiency, you should also consider the hard limits that Apple imposes for updating the complication. Even in watchOS 3 (where you're encouraged to keep the complication and dock snapshot regularly updated in the background), you'll run up against limits of 4 background updates per hour, and 50 complication updates per day. See watchOS - Show realtime departure data on complication for specifics.
My company has created an enterprise app that is used out on the field.
We've discovered that our iPhone 5 does not function properly below 0*C.
One solution we're looking to experiment with is running heavy CPU-intensive tasks (equivalent to running a 3D game) which will add heat to the device (outside of the other external heat we're using to help them).
Using a laser temperature gauge indoors as a baseline, I measured 24*C on the iPhone 5 screen when it's idling on the app home screen. When I launch an intensive 3D game, the phone heats up and the screen registers 35*C within a few minutes.
How can we recreate an intensive background-threaded CPU process that does not consume resources and crash the app? I've tried various open-source iOS benchmark apps from GitHub but when I put them on an infinite loop in a background thread, after a few minutes the app goes out of memory and crashes.
Here's an example of one of the benchmarks I put in to the background on an infinite loop. (while 1=1). No matter what variation of benchmark code I try, the "Total Bytes" will grow without stopping until the app crashes. Click to see Instruments Screenshot.
Does anyone have examples of CPU-intensive code that I could use that would make the device get nice and hot and also prevent the app from eating resources indefinitely?
Battery drain is not an issue as we will use external battery packs connected to offset any drain.
All help appreciated!
Adam
UIApplication * application = [UIApplication sharedApplication];
if([[UIDevice currentDevice] respondsToSelector:#selector(isMultitaskingSupported)])
{
NSLog(#"Multitasking Supported");
__block UIBackgroundTaskIdentifier background_task;
background_task = [application beginBackgroundTaskWithExpirationHandler:^ {
//Clean up code. Tell the system that we are done.
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
}];
//To make the code block asynchronous
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
//### background task starts
NSLog(#"Running in the background\n");
static NSString *staticString = #"some const value string to referece";
while (1 == 1)
{
NSDictionary *dictionary;
NSString *string;
NSDate *staticStart = [NSDate date];
NSLog(#"start timing");
int times = 10000000;
while (times--) {
static dispatch_once_t onceToken;
static NSDictionary *dict;
dispatch_once(&onceToken, ^{
dict = #{#"somekey":#"someotherlongvalue", #"someotherkey":#"someotherlongvalue", #"onelastkey":#"onelastvalue"};
});
dictionary = dict;
}
NSDate *staticEnd = [NSDate date];
NSLog(#"finished static dict in %f sec", [staticEnd timeIntervalSinceDate:staticStart]);
times = 10000000;
while (times--) {
dictionary = #{#"somekey":#"someotherlongvalue", #"someotherkey":#"someotherlongvalue", #"onelastkey":#"onelastvalue"};
}
NSDate *dictEnd = [NSDate date];
NSLog(#"finished dict create in %f sec", [dictEnd timeIntervalSinceDate:staticEnd]);
times = 10000000;
while (times--) {
static dispatch_once_t stringOnceToken;
static NSString *dispatchString;
dispatch_once(&stringOnceToken, ^{
dispatchString = #"someotherlongvalue";
});
string = dispatchString;
}
NSDate *staticStringEnd = [NSDate date];
NSLog(#"finished static string in %f sec", [staticStringEnd timeIntervalSinceDate:dictEnd]);
times = 10000000;
while (times--) {
string = #"someotherlongvalue";
}
NSDate *stringEnd = [NSDate date];
NSLog(#"finished string create in %f sec", [stringEnd timeIntervalSinceDate:staticStringEnd]);
times = 10000000;
while (times--) {
string = staticString;
}
NSDate *refEnd = [NSDate date];
NSLog(#"finished string reference in %f sec", [refEnd timeIntervalSinceDate:stringEnd]);
}
//#### background task ends
//Clean up code. Tell the system that we are done.
[application endBackgroundTask: background_task];
background_task = UIBackgroundTaskInvalid;
});
}
else
{
NSLog(#"Multitasking Not Supported");
}
You have lots of autoreleased objects that are never given a chance to be released. Add an autorelease pool to your outer loop:
while (1 == 1) {
#autoreleasepool {
// original contents of your loop
}
}
Is there any way to capture the event occurs when a user connects to a particular WiFi network in iOS app. It is fine even if this can be achieved using any private library which doesn't require super user privileges (jail break). I just want to capture the changing event of the connected SSID.
I would recommend simply using what Larme posted, and setting up an NSTimer to check every second or so, what the SSID of your current network is, if you detect a change, simply do whatever you need to do. Keep in mind, changing WiFi networks is not something that happens instantaneously, so having a 1 second resolution is not bad
In applicationDidFinishLoading
NSTimer *ssidTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(fetchSSIDInfo) userInfo:nil repeats:YES];
In AppDelegate
- (id)fetchSSIDInfo {
NSArray *ifs = (__bridge_transfer id)CNCopySupportedInterfaces();
NSLog(#"Supported interfaces: %#", ifs);
id info = nil;
NSString *ifnam = #"";
for (ifnam in ifs) {
info = (__bridge_transfer id)CNCopyCurrentNetworkInfo((__bridge CFStringRef)ifnam);
NSLog(#"%# => %#", ifnam, info);
if (info && [info count]) { break; }
}
if ([info count] >= 1 && [ifnam caseInsensitiveCompare:prevSSID] != NSOrderedSame) {
// Trigger some event
prevSSID = ifnam;
}
return info;
}
Something like that. I can not check if code is typo free as I am not in front of a mac, but it should not be too different
You can fetch details from your wifi connection:
- (NSDictionary *)getConnectionDetails
{
NSDictionary *connectionDetails = [NSDictionary dictionary];
CFArrayRef myArray = CNCopySupportedInterfaces();
if (myArray) {
CFDictionaryRef myDict = CNCopyCurrentNetworkInfo(CFArrayGetValueAtIndex(myArray, 0));
connectionDetails = (__bridge_transfer NSDictionary*)myDict;
}
return connectionDetails;
}
And then if check [connectionDetails valueForKey:#"BSSID"] you will get BSSID.
Also please note that you must to import #import <SystemConfiguration/CaptiveNetwork.h>
You want SystemConfiguration, which has facilities for seeing notifications on all sorts of networking changes. In particular you'll want to use SCDynamicStoreSetNotificationKeys to listen for changes to the devices and SCNetworkConfiguration to get information about the available interfaces.
I am about to release an iPhone app for a small business. The app has a call button which calls the business with an ibaction. I would like to know if i can find out how many times this action occurs so i can get a better idea of how effective the app is, and how to go about it.
Thanks for your time.
You could implement a web-service with a simple method which will increment a counter on a server. And call it asynchronously every time user taps a button. I would do it like this:
#define TAP_COUNT_KEY #"tapcnt"
#define MAKE_URL(X) [NSURL URLWithString:[NSString stringWithFormat:#"http://my_service_address?cnt=%d", X]]
dispatch_queue_t serviceQueue = dispatch_queue_create("Tap counter queue", NULL);
dispatch_async(serviceQueue, ^{
NSUInteger counter = [[NSUserDefaults standardUserDefaults] integerForKey:TAP_COUNT_KEY] + 1;
NSURL *serviceUrl = MAKE_URL(counter);
NSError *error = nil;
[NSString stringWithContentsOfURL:serviceUrl encoding:NSUTF8StringEncoding error:&error];
if (error)
NSLog(#"%#", [error localizedDescription]);
else
counter = 0;
[[NSUserDefaults standardUserDefaults] setInteger:counter forKey:TAP_COUNT_KEY];
});
dispatch_release(serviceQueue);
On IBAction method, you can increment counter by +1. Then you can store it into NSUserDefaults by key-value pair. And every time, increase counter and store it. So it'll maintain counter state until application will be uninstalled.
int counter;
-(IBAction)click:(id)sender
{
NSUserDefaults *_countDefaults = [NSUserDefaults standardUserDefaults];
counter = [[_countDefaults integerForKey:#"Counter"] intValue];
counter++;
[_countDefaults setInteger:counter forKey:#"Counter"];
[_countDefaults synchronize];
}
Hopefully, you'll get idea.
Thanks.
You can increase your button tag value and access it where ever you want,
-(IBAction)btnPanic:(id)sender
{
UIButton *btn=(UIButton *)sender;
btn.tag=btn.tag+1;
}