I am trying to develop a watchOS app that collects Sensor(accelerator) data at a very high frequency(100Hz). I am using Core Data to store the data and export it to a CSV file, then send to iPhone.
The app crashed after a short time, because some thread's memory exceeded the limitation. And I also found out that the "performBackgroundTask" method creates too many threads and the CPU usage is more than 100%.
Another problem is when I fetch the data out, I got more data items than the that are actually stored in the Sqlite Database, which is quite confusing.
Below is the code and the method is called in this method([self.motionManager startDeviceMotionUpdatesToQueue:self.queue withHandler:^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error) {}])'s callback block:
- (void)workoutManager:(nonnull id)manager didReceiveAccelerationData:(nonnull YLAcceleration *)acceleration {
NSString *dateTime = [self.dateFormatter stringFromDate:[NSDate date]];
[self.coreDataStack.persistentContainer performBackgroundTask:^(NSManagedObjectContext * _Nonnull context) {
Acceleration *accData = [[Acceleration alloc] initWithContext:context];
accData.x = acceleration.xString;
accData.y = acceleration.yString;
accData.z = acceleration.zString;
accData.heartRate = self.heartRate;
accData.dateTime = dateTime;
accData.session = self.currentSession;
NSError *saveError = nil;
[context save:&saveError];
if (error) {
NSLog(#"Save data failed:%#", error);
}else{
NSLog(#"Save data succeeded:%#", [NSThread currentThread]);
}
}];
}
Thank you in advance.
Started to use Firebase and so far I like it. But now I am topping - trying to download images from Storage. But I don't want to download the images in background, I want to download them directly - hope I am clear.
So far I have tested:
for (NSInteger iLoop=0; iLoop<aFriendsKey.count; iLoop++) {
NSDictionary *dicFriend = [dicFriends objectForKey:[aFriendsKey objectAtIndex:iLoop]];
FIRStorage *storage = [FIRStorage storage];
FIRStorageReference *storageRef = [storage referenceForURL:[dicFriend objectForKey:#"avatarURL"]];
// Download in memory with a maximum allowed size of 1MB (1 * 1024 * 1024 bytes)
[storageRef dataWithMaxSize:5 * 1024 * 1024 completion:^(NSData *data, NSError *error){
if (error != nil) {
// Uh-oh, an error occurred!
} else {
[aFriendsAvatar addObject:data];
}
}];
}
[_tvFriends reloadData];
and it works fine, but the images are not downloaded in time, that means they are not available when I reload the UITableView. As you see from before code, I am trying to load all images in a NSMutableArray and use this NSMutableArray to show the images in the UITableView.
But because of the delay between background download and reloadData, never any image is shown.
Any idea or solution?
It's not a good idea to block the main thread while loading the images or the app will become entirely unresponsive to the user. Instead, you simply want to be notified when all of the downloads have completed and then reload the table view. This can be done with a dispatch_group_t from Grand Central Dispatch (GCD).
dispatch_group_t group = dispatch_group_create();
for (NSInteger iLoop=0; iLoop<aFriendsKey.count; iLoop++) {
NSDictionary *dicFriend = [dicFriends objectForKey:[aFriendsKey objectAtIndex:iLoop]];
FIRStorage *storage = [FIRStorage storage];
FIRStorageReference *storageRef = [storage referenceForURL:[dicFriend objectForKey:#"avatarURL"]];
// Download in memory with a maximum allowed size of 1MB (1 * 1024 * 1024 bytes)
dispatch_group_enter(group);
[storageRef dataWithMaxSize:5 * 1024 * 1024 completion:^(NSData *data, NSError *error){
if (error != nil) {
// Uh-oh, an error occurred!
} else {
[aFriendsAvatar addObject:data]
}
dispatch_group_leave(group);
}];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[_tvFriends reloadData];
});
You can think of a dispatch group as a simple counter of outstanding operations. Before we start each download, we call dispatch_group_enter on the group to increment it's count. When each download finishes, we call dispatch_group_leave to decrement the count. We register a listener block with dispatch_group_notify so that when the count reaches 0, our block is called and we know all operations have finished, and it is safe to reload the table view. Also, this approach will not block the main thread, meaning the user can still interact with the UI while the downloads are happening.
In iOS 9.3 Apple release new APIs. Now developers can see if a user is currently a member of Apple Music. I'm trying to understand how it works.
My code -only first time- asks the user whether to access the music library but I don't understand how to determine in what ways you can detect if user is a member and open Apple Music to join it. These actions in Shazam works really great. How can I do something like that?
Thanks in advance!
[SKCloudServiceController requestAuthorization:^(SKCloudServiceAuthorizationStatus status) {
NSLog(#"status is %ld", (long)status);
SKCloudServiceController *cloudServiceController = [[SKCloudServiceController alloc] init];
[cloudServiceController requestCapabilitiesWithCompletionHandler:^(SKCloudServiceCapability capabilities, NSError * _Nullable error) {
NSLog(#"%lu %#", (unsigned long)capabilities, error);
if (capabilities >= SKCloudServiceCapabilityAddToCloudMusicLibrary || capabilities==SKCloudServiceCapabilityMusicCatalogPlayback) {
NSLog(#"You CAN add to iCloud!");
} else {
NSLog(#"The ability to add Apple Music track is not there. sigh.");
}
}];
}];
You should check for the SKCloudServiceCapabilityMusicCatalogPlayback flag in capabilities. The code in your question checks whether the capabilities variable equals that flag, but you need to use bitwise operators. Here is how you'll do that.
SKCloudServiceController *controller = [SKCloudServiceController new];
[controller requestCapabilitiesWithCompletionHandler:^(SKCloudServiceCapability capabilities, NSError * _Nullable error) {
if (error != nil) {
NSLog(#"Error getting SKCloudServiceController capabilities: %#", error);
} else if (capabilities & SKCloudServiceCapabilityMusicCatalogPlayback) {
// The user has an active subscription
} else {
// The user does *not* have an active subscription
}
}];
You can tell that SKCloudServiceCapabilityMusicCatalogPlayback is a flag and not just a regular constant value because the value uses bitwise operators (the "<<" shown in Apple's documentation).
SKCloudServiceCapabilityMusicCatalogPlayback = 1 << 0
https://developer.apple.com/reference/storekit/skcloudservicecapability/skcloudservicecapabilitymusiccatalogplayback?language=objc
Can we access the heart rate directly from the apple watch? I know this is a duplicate question, but no one has asked this in like 5 months. I know you can access it from the Health App but I'm not sure how "real-time" that will be.
Heart Rate Raw Data information is now available in Watchkit for watchOS 2.0.
WatchOS 2 includes many enhancements to other existing frameworks such as HealthKit, enabling access to the health sensors that access heart rate and health information in real-time.
You could check this information in the following session which is total 30 minutes presentation.If you do not want to watch entire session, then you directly jump to Healthkit API features which is in between 25-28 min:
WatchKit for watchOS 2.0 Session in WWDC 2015
Here is the source code implementation link
As stated in the HKWorkout Class Reference:
The HKWorkout class is a concrete subclass of the HKSample class.
HealthKit uses workouts to track a wide range of activities. The
workout object not only stores summary information about the activity
(for example, duration, total distance, and total energy burned), it
also acts as a container for other samples. You can associate any
number of samples with a workout. In this way, you can add detailed
information relevant to the workout.
In that given link, the following part of the code defines sample rate of heartRate
NSMutableArray *samples = [NSMutableArray array];
HKQuantity *heartRateForInterval =
[HKQuantity quantityWithUnit:[HKUnit unitFromString:#"count/min"]
doubleValue:95.0];
HKQuantitySample *heartRateForIntervalSample =
[HKQuantitySample quantitySampleWithType:heartRateType
quantity:heartRateForInterval
startDate:intervals[0]
endDate:intervals[1]];
[samples addObject:heartRateForIntervalSample];
As they state there:
You need to fine tune the exact length of your associated samples
based on the type of workout and the needs of your app. Using 5 minute
intervals minimizes the amount of memory needed to store the workout ,
while still providing a general sense of the change in intensity over
the course of a long workout. Using 5 second intervals provides a
much-more detailed view of the workout, but requires considerably more
memory and processing.
After exploring HealthKit and WatchKit Extension, My findings are as follows:
We do not need the WatchKit Extension to get the Heart Rate Data.
You just need to have an iPhone with paired Apple watch (which is obvious)
The Default Apple Watch Heart Rate monitor app updates the HealthKit data immediately only when it is in the foreground.
When the Default Apple Watch Heart Rate monitor app is in the Background, it updates the HealthKit data at the interval of 9-10 mins.
To get the Heart rate data from the HealthKit following query needs to be fired periodically.
func getSamples() {
let heathStore = HKHealthStore()
let heartrate = HKQuantityType.quantityType(forIdentifier: .heartRate)
let sort: [NSSortDescriptor] = [
.init(key: HKSampleSortIdentifierStartDate, ascending: false)
]
let sampleQuery = HKSampleQuery(sampleType: heartrate!, predicate: nil, limit: 1, sortDescriptors: sort, resultsHandler: resultsHandler)
heathStore.execute(sampleQuery)
}
func resultsHandler(query: HKSampleQuery, results: [HKSample]?, error: Error?) {
guard error == nil else {
print("cant read heartRate data", error!)
return
}
guard let sample = results?.first as? HKQuantitySample else { return }
// let heartRateUnit: HKUnit = .init(from: "count/min")
// let doubleValue = sample.quantity.doubleValue(for: heartRateUnit)
print("heart rate is", sample)
}
Please update me if anyone gets more information.
Happy Coding.
Update
I've updated your code to be clear and general, and be aware that you need to get authorization for reading HeathKit data and adding info.plist key Privacy - Health Records Usage Description
There is no direct way to access any sensors on the Apple Watch. You will have to rely on access from HealthKit.
An Apple evangelist said this
It is not possible to create a heart monitor app at this time. The
data isn't guaranteed to be sent to iPhone in real-time, so you won't
be able to determine what's going on in any timely fashion.
See https://devforums.apple.com/message/1098855#1098855
You can get heart rate data by starting a workout and query heart rate data from healthkit.
Ask for premission for reading workout data.
HKHealthStore *healthStore = [[HKHealthStore alloc] init];
HKQuantityType *type = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
HKQuantityType *type2 = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceWalkingRunning];
HKQuantityType *type3 = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned];
[healthStore requestAuthorizationToShareTypes:nil readTypes:[NSSet setWithObjects:type, type2, type3, nil] completion:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(#"health data request success");
}else{
NSLog(#"error %#", error);
}
}];
In AppDelegate on iPhone, respond this this request
-(void)applicationShouldRequestHealthAuthorization:(UIApplication *)application{
[healthStore handleAuthorizationForExtensionWithCompletion:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(#"phone recieved health kit request");
}
}];
}
Then implement Healthkit Delegate:
-(void)workoutSession:(HKWorkoutSession *)workoutSession didFailWithError:(NSError *)error{
NSLog(#"session error %#", error);
}
-(void)workoutSession:(HKWorkoutSession *)workoutSession didChangeToState:(HKWorkoutSessionState)toState fromState:(HKWorkoutSessionState)fromState date:(NSDate *)date{
dispatch_async(dispatch_get_main_queue(), ^{
switch (toState) {
case HKWorkoutSessionStateRunning:
//When workout state is running, we will excute updateHeartbeat
[self updateHeartbeat:date];
NSLog(#"started workout");
break;
default:
break;
}
});
}
Now it's time to write **[self updateHeartbeat:date]**
-(void)updateHeartbeat:(NSDate *)startDate{
//first, create a predicate and set the endDate and option to nil/none
NSPredicate *Predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:nil options:HKQueryOptionNone];
//Then we create a sample type which is HKQuantityTypeIdentifierHeartRate
HKSampleType *object = [HKSampleType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
//ok, now, create a HKAnchoredObjectQuery with all the mess that we just created.
heartQuery = [[HKAnchoredObjectQuery alloc] initWithType:object predicate:Predicate anchor:0 limit:0 resultsHandler:^(HKAnchoredObjectQuery *query, NSArray<HKSample *> *sampleObjects, NSArray<HKDeletedObject *> *deletedObjects, HKQueryAnchor *newAnchor, NSError *error) {
if (!error && sampleObjects.count > 0) {
HKQuantitySample *sample = (HKQuantitySample *)[sampleObjects objectAtIndex:0];
HKQuantity *quantity = sample.quantity;
NSLog(#"%f", [quantity doubleValueForUnit:[HKUnit unitFromString:#"count/min"]]);
}else{
NSLog(#"query %#", error);
}
}];
//wait, it's not over yet, this is the update handler
[heartQuery setUpdateHandler:^(HKAnchoredObjectQuery *query, NSArray<HKSample *> *SampleArray, NSArray<HKDeletedObject *> *deletedObjects, HKQueryAnchor *Anchor, NSError *error) {
if (!error && SampleArray.count > 0) {
HKQuantitySample *sample = (HKQuantitySample *)[SampleArray objectAtIndex:0];
HKQuantity *quantity = sample.quantity;
NSLog(#"%f", [quantity doubleValueForUnit:[HKUnit unitFromString:#"count/min"]]);
}else{
NSLog(#"query %#", error);
}
}];
//now excute query and wait for the result showing up in the log. Yeah!
[healthStore executeQuery:heartQuery];
}
You also have a turn on Healthkit in capbilities. Leave a comment below if you have any questions.
Using FB SDK 3.6, I am attempting to capture FB User IDs and save to Parse datastore in the cloud to build a custom audience to market to. My call is as follows:
[FBRequestConnection startForCustomAudienceThirdPartyID:nil
completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
NSString *uid = error ? nil : [result objectForKey:#"custom_audience_third_party_id"];
if ([uid length] > 0) {
PFObject *newInstall = [PFObject objectWithClassName:#"NewInstalls"];
[newInstall setObject:uid forKey:#"FacebookUID"];
[newInstall saveEventually]; //saves whenever user is online
}
}];
It works beautifully on the simulator (v6.1), but when I run on the device (iPhone 5, v6.1.2) it makes the call, but the completion handler never runs. Any suggestions?
I found that it was 'Limit Ad Tracking' being enabled in iOS Settings that caused the completionHandler to never be called. Turning that off made it work for me.