I'm beginning coding with the DeviceMotion class. After following Apple's documenation, i have the following:
- (void)viewDidLoad {
[super viewDidLoad];
myMM = [[CMMotionManager alloc] init];
myMM.deviceMotionUpdateInterval = 1.0/30.0;
theQ = [[NSOperationQueue currentQueue] retain];
motionHandler = ^ (CMDeviceMotion *motionData, NSError *error) {
if (motionData.rotationRate.z > 5.5 || motionData.rotationRate.z < -5.5) {
NSLog(#"Rotation of Z."); // Reference A
}
};
-(IBAction)toggleClick{
NSLog(#"toggle");
if(myMM.gyroAvailable){
if(myMM.deviceMotionActive){
NSLog(#"Stopping Motion Updates..");
[myMM stopDeviceMotionUpdates];
} else {
NSLog(#"Starting Motion Updates..");
[myMM startDeviceMotionUpdatesToQueue:theQ withHandler:motionHandler];
}
}
else {
NSLog(#"No motion available. Quit!");
}
This code works fine, however when I want to do any code except an NSLog (even something as simple as incrementing an integer) in place of the 'reference A', I get an EXEC Bad Access in the console.
I've looked around, and all I've found is that it's a memory leak of sorts. Does anyone know whats going on? If not, how can I figure it out? I'm pretty inexperienced with Instruments, but if I'm pointed in the right direction I'd be much appreciated.
EXC_BAD_ACCESS is an OS-level exception meaning that you are trying to access memory that doesn't belong to you. I think this has something to do with your block being local to the scope, so once it goes out of scope, it is destroyed. You need to create a copy of it on the heap.
Try this answer from the renowned Dave DeLong. Also, as with the normal Cocoa memory management rules, don't forget to release it if you've made a copy.
For example:
motionHandler = Block_copy(^ (CMDeviceMotion *motionData, NSError *error) {
if (motionData.rotationRate.z > 5.5 || motionData.rotationRate.z < -5.5) {
NSLog(#"Rotation of Z."); // Reference A
}
});
// and then later:
- (void) dealloc
{
[motionHandler release];
//and all others.
[super dealloc];
}
Related
Example project: http://cl.ly/360k3M3a2y05
I'm playing with the Reddit API for a school project, and came across this library for using it in Objective-C/Swift.
The professor wants us to get our toes wet with Swift, which I'm happy to do, and the goal of the project is to add an extra function onto an existing website's API. (I chose Reddit obviously.)
The mentioned library doesn't have a way to get all the subscriptions for a particular user (only to get one page at a time with the option to paginate), so I want to add the option to get them all in one clean call.
I'm leveraging the method in the aforementioned library that allows you to paginate, the method looks like this:
- (NSURLSessionDataTask *)subscribedSubredditsInCategory:(RKSubscribedSubredditCategory)category pagination:(RKPagination *)pagination completion:(RKListingCompletionBlock)completion {
NSMutableDictionary *taskParameters = [NSMutableDictionary dictionary];
[taskParameters addEntriesFromDictionary:[pagination dictionaryValue]];
NSString *path = [NSString stringWithFormat:#"subreddits/mine/%#.json", RKStringFromSubscribedSubredditCategory(category)];
return [self getPath:path parameters:taskParameters completion:^(NSHTTPURLResponse *response, id responseObject, NSError *error) {
if (!completion) return;
if (responseObject)
{
// A crude check to see if we have been redirected to the login page:
NSString *path = [[response URL] path];
NSRange range = [path rangeOfString:#"login"];
if (range.location != NSNotFound)
{
completion(nil, nil, [RKClient authenticationRequiredError]);
return;
}
// Parse the response:
NSArray *subredditsJSON = responseObject[#"data"][#"children"];
NSMutableArray *subredditObjects = [[NSMutableArray alloc] initWithCapacity:[subredditsJSON count]];
for (NSDictionary *subredditJSON in subredditsJSON)
{
NSError *mantleError = nil;
RKSubreddit *subreddit = [MTLJSONAdapter modelOfClass:[RKSubreddit class] fromJSONDictionary:subredditJSON error:&mantleError];
if (!mantleError)
{
[subredditObjects addObject:subreddit];
}
}
RKPagination *pagination = [RKPagination paginationFromListingResponse:responseObject];
completion([subredditObjects copy], pagination, nil);
}
else
{
completion(nil, nil, error);
}
}];
}
My addition is rather simple, I just call this above method recursively and save the pagination after each successful request, until there's no pages left, and then return the result:
- (void)allSubscribedSubredditsInCategory:(RKSubscribedSubredditCategory)category completion:(void (^)(NSArray *subreddits, NSError *error))completion {
RKPagination *pagination = [RKPagination paginationWithLimit:100];
[self recursiveSubscribedSubredditsWithPagination:pagination subredditsSoFar:[NSArray array] completion:completion];
}
- (void)recursiveSubscribedSubredditsWithPagination:(RKPagination *)pagination subredditsSoFar:(NSArray *)subredditsSoFar completion:(void (^)(NSArray *subreddits, NSError *error))completion {
[self subscribedSubredditsInCategory:RKSubscribedSubredditCategorySubscriber pagination:pagination completion:^(NSArray *newSubreddits, RKPagination *newPagination, NSError *newError) {
// If pagination is nil, we cannot go any further and have reached the end
if (newPagination == nil) {
NSArray *newSubredditsSoFar = [subredditsSoFar arrayByAddingObjectsFromArray:newSubreddits];
NSArray *subredditsWithoutDuplicates = [[NSSet setWithArray:newSubredditsSoFar] allObjects];
completion(subredditsWithoutDuplicates, newError);
} else {
NSArray *newSubredditsSoFar = [subredditsSoFar arrayByAddingObjectsFromArray:newSubreddits];
[self recursiveSubscribedSubredditsWithPagination:newPagination subredditsSoFar:newSubredditsSoFar completion:completion];
}
}];
}
So it looks like this in my viewDidLoad of my view controller:
RKClient.sharedClient().signInWithUsername("includedinproject", password: "includedinproject") { (error) -> Void in
RKClient.sharedClient().allSubscribedSubredditsInCategory(.Subscriber, completion: { (subreddits, error) -> Void in
print(subreddits)
}) <-- error occurs here?
}
However, whenever I call it, I get an EXC_BAD_ACCESS runtime error that doesn't really provide anything other than a memory address, and it appears to be caused at the end of the method in viewDidLoad, as labeled above.
The weird thing that occurs, however, is that this only occurs seemingly on the iPhone 4s simulator. If I build it to run on say, the newest 6s, it works fine. I'm puzzled (it has to work on all simulators for full points).
I went to my professor about it and he has no idea. We emulated the project in Objective-C (rebuilt the project as an Objective-C one) and the call seems to work fine.
My professor even did something with Instruments (not much experience myself) looking at "Zombies" and enabled it in the project as well, and nothing seemed to give him information either, we're both pretty confused.
What is going on here that's causing it to work great in Objective-C, and in Swift if the device isn't a 4s? Example project is at the top.
After seeing this question, I tried to code up a quick program that would save the watches accelerometer and gyroscope data to a file.
#implementation InterfaceController{
NSMutableArray *accData;
bool recording;
}
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
// Configure interface objects here.
self.motionManager = [[CMMotionManager alloc] init];
[self.motionManager setAccelerometerUpdateInterval:.01];
}
- (IBAction)startStopRecording {
if (!recording){//We are starting to record.
recording = YES;
accData = [[NSMutableArray alloc] init];
[self.startRecording setTitle:#"Stop Recording"];
[self.motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
[accData addObject:[NSString stringWithFormat:#"%f, %f, %f", accelerometerData.acceleration.x, accelerometerData.acceleration.y, accelerometerData.acceleration.z]];
}];
}else{
recording = NO;//we are stopping the recording
[self.motionManager stopAccelerometerUpdates];
[self.startRecording setTitle:#"Start Recording"];
[InterfaceController openParentApplication:#{ #"accData": accData } reply:^(NSDictionary *replyInfo, NSError *error) { //this method saves the array to a csv file.
NSLog(#"Data has been saved.");
}];
}
}
I had plotted this data and for the life of me, no matter how hard I shook the watch, all my plots looked like this:
Until 8 hours later, I started to suspect that I wasn't grabbing the acceleration data from the watch, but rather from the phone (sitting still on the table next to me). I ran some tests and confirmed that this is exactly what is happening.
Which leads me to the original question. How do I pull acceleration/gyro/data from the watch and not from the iPhone?
The problem was that I wasn't running watchOS2. I assumed I was but it's still in beta and I hadn't installed it. The data I was getting was accelerometer data from the phone. Also, currently, you can only get acc data from the watch using watchOS2 and not gyro data.
you can use CoreMotion framework to get activity data.
while I can only get accel data, the gyro often return false.
I am starting in Unit testing with objective-c and I need to know how to test blocks with OCMockito and Xcode 6.
I am testing an Interactor, this interactor should return an array as a block argument and I has to ask the Provider file for the elements.
This is the method I want to test:
- (void)userPoiListsWithSuccessBlock:(MNBSavePoisInteractorSuccess)success {
self.poiListEntityArray = [self.poiListProvider poiListsForUser:self.loggedUser];
self.poiListViewObjectArray = [self viewPoiListObjectListWithPoiLists:self.poiListEntityArray];
success(self.poiListViewObjectArray);
}
First, I setup the elements that I am going to use
self.mockPoiListProvider = mock([PoiListProvider class]);
self.sut = [[MNBSavePoisInteractor alloc] initWithManagedObjectContext:self.coreDataStack.managedObjectContext andPoiListProvider:self.mockPoiListProvider];
- (UserEntity *)loggedUserMock {
UserEntity *mockLoggedUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([UserEntity class]) inManagedObjectContext:self.coreDataStack.managedObjectContext];
mockLoggedUser.userId=#"1";
mockLoggedUser.username=#"user";
mockLoggedUser.loggedUser=#YES;
return mockLoggedUser;
}
- (InMemoryCoreDataStack *)coreDataStack{
if (!_coreDataStack) {
_coreDataStack = [[InMemoryCoreDataStack alloc] init];
}
return _coreDataStack;
}
- (PoiListEntity *)poiListFake {
PoiListEntity *fake = [NSEntityDescription insertNewObjectForEntityForName:#"PoiListEntity" inManagedObjectContext:self.coreDataStack.managedObjectContext];
fake.name = #"Test";
fake.poisCount = #2;
[fake addContributorsObject:[self loggedUserMock]];
return fake;
}
Then, I do the test. I am using Xcode 6 waitForExpectation to manage the asynchronous methods. I think I am doing something wrong.
- (void)waitForExpectation {
[self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) {
if (error) {
NSLog(#"Timeout Error: %#", error);
}
}];
}
- (void)testShouldReturnPoiLists {
XCTestExpectation *expectation = [self expectationWithDescription:#"Waiting method ends"];
[given([self.mockPoiListProvider poiListsForUser:[self loggedUserMock]]) willReturn:#[[self poiListFake]]];
[self.sut userPoiListsWithSuccessBlock:^(NSArray *results) {
[expectation fulfill];
XCTAssert(resutls.count == 1, #"Results %zd", resutls.count);
}];
[self waitForExpectation];
}
I understood if I give the object in willReturn in the given method, when I call the sut method that I want to test it should return what I give before. Is that true?
Thank you
I see no asynchronous code. You just want a block that captures the results, so use a __block variable to make the results available outside of the block. Then you can assert whatever you want:
- (void)testShouldReturnPoiLists {
[given([self.mockPoiListProvider poiListsForUser:[self loggedUserMock]]) willReturn:#[[self poiListFake]]];
__block NSArray *capturedResults;
[self.sut userPoiListsWithSuccessBlock:^(NSArray *results) {
capturedResults = results;
}];
assertThat(capturedResults, hasLengthOf(1));
}
The relationship between the length of 1 and the fake is hard to tell. Let's also parameterize the faking code:
- (PoiListEntity *)poiListFakeWithName:(NSString *)name count:(NSNumber *)count {
PoiListEntity *fake = [NSEntityDescription insertNewObjectForEntityForName:#"PoiListEntity" inManagedObjectContext:self.coreDataStack.managedObjectContext];
fake.name = name;
fake.poisCount = count;
[fake addContributorsObject:[self loggedUserMock]];
return fake;
}
With that, we can write more interesting tests.
I do want to add that it's important to "listen to the tests." There's a lot of convoluted set-up to dance around Core Data. That tells me that if you can rewrite things to be independent of Core Data — completely ignorant of it — everything will be much simpler.
I Added ios-8's new touchID API to my app.
It usually works as expected, BUT when entering app while my finger is already on home-button - API's success callback is called but pop-up still appears on screen. after pressing CANCEL UI becomes non-responsive.
I also encountered the same issue, and the solution was to invoke the call to the Touch ID API using a high priority queue, as well as a delay:
// Touch ID must be called with a high priority queue, otherwise it might fail.
// Also, a dispatch_after is required, otherwise we might receive "Pending UI mechanism already set."
dispatch_queue_t highPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.75 * NSEC_PER_SEC), highPriorityQueue, ^{
LAContext *context = [[LAContext alloc] init];
NSError *error = nil;
// Check if device supports TouchID
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
// TouchID supported, show it to user
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:#"Unlock Using Touch ID"
reply:^(BOOL success, NSError *error) {
if (success) {
// This action has to be on main thread and must be synchronous
dispatch_async(dispatch_get_main_queue(), ^{
...
});
}
else if (error) {
...
}
}];
}
});
When testing our app, we found a delay of 750ms to be optimal, but your mileage may vary.
Update (03/10/2015): Several iOS developers, like 1Password for example, are reporting that iOS 8.2 have finally fixed this issue.
Whilst using a delay can potentially address the issue, it masks the root cause. You need to ensure you only show the Touch ID dialog when the Application State is Active. If you display it immediately during the launch process (meaning the Application is still technically in an inactive state), then these sorts of display issues can occur. This isn't documented, and I found this out the hard way. Providing a delay seems to fix it because you're application is in an active state by then, but this isn't guarenteed.
To ensure it runs when the application is active, you can check the current application state, and either run it immediately, or when we receive the applicationDidBecomeActive notification. See below for an example:
- (void)setup
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidBecomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// We need to be in an active state for Touch ID to play nice
// If we're not, defer the presentation until we are
if([UIApplication sharedApplication].applicationState == UIApplicationStateActive)
{
[self presentTouchID];
}
else
{
__weak __typeof(self) wSelf = self;
_onActiveBlock = ^{
[wSelf presentTouchID];
};
}
}
-(void)applicationDidBecomeActive:(NSNotification *)notif
{
if(_onActiveBlock)
{
_onActiveBlock();
_onActiveBlock = nil;
}
}
- (void)presentTouchID
{
_context = [[LAContext alloc] init];
_context.localizedFallbackTitle = _fallbackTitle;
[_context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:_reason
reply: ^(BOOL success, NSError *authenticationError)
{
// Handle response here
}];
}
This accepted answer does not address the underlying cause of the problem: invoking evaluatePolicy() twice, the second time while the first invocation is in progress. So the current solution only works sometimes by luck, as everything is timing dependent.
The brute-force, straightforward way to work around the problem is a simple boolean flag to prevent subsequent calls from happening until the first completes.
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
if ( NSClassFromString(#"LAContext") && ! delegate.touchIDInProgress ) {
delegate.touchIDInProgress = YES;
LAContext *localAuthenticationContext = [[LAContext alloc] init];
__autoreleasing NSError *authenticationError;
if ([localAuthenticationContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authenticationError]) {
[localAuthenticationContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:kTouchIDReason reply:^(BOOL success, NSError *error) {
delegate.touchIDInProgress = NO;
if (success) {
...
} else {
...
}
}];
}
I started getting the "Pending UI mechanism already set." error mentioned as well, so I decided to see if other apps were affected. I have both Dropbox and Mint set up for Touch ID. Sure enough Touch ID wasn't working for them either and they were falling back to passcodes.
I rebooted my phone and it started working again, so it would seem the Touch ID can bug out and stop working. I'm on iOS 8.2 btw.
I guess the proper way to handle this condition is like those apps do and fallback to password / passcode.
My english is little short. I attach Google Play Games to iPhone5. Everything is fine except leaderboard rank. When I attempt to get rank from leaderboard, It always return zero.
Below is my code. What is problem?
- (void)viewDidLoad {
[super viewDidLoad];
[GPGManager sharedInstance].statusDelegate = self;
}
- (IBAction)signInWasClicked:(id)sender {
[[GPGManager sharedInstance] signInWithClientID:CLIENT_ID silently:NO];
}
- (IBAction)submitScore: (UIButton *)sender {
GPGScore* myScore = [[GPGScore alloc] initWithLeaderboardId:LEADERBOARD_ID];
[myScore submitScoreWithCompletionHandler:^(GPGScoreReport *report, NSError *error){
if (error) {
// error
NSLog(#"ERROR");
} else {
// Success. Score retern fine. score return right value but rank is not.
NSLog(#"%#, %lld", report.highScoreForLocalPlayerAllTime.formattedScore,
report.highScoreForLocalPlayerAllTime.rank);
}
}];
}
In Google developer's "Leaderboard in iOS" section, there is no mention about leaderboard rank. But in GPGScoreReport object, there is GPGScore object and in GPGScore object, score and rank value are on it.
Help me please.
GPGLeaderboard *myLeaderboard = [GPGLeaderboard leaderboardWithId:LEADERBOARD_ID];
myLeaderboard.timeScope = GPGLeaderboardTimeScopeAllTime;
GPGLocalPlayerScore* localScore = [myLeaderboard localPlayerScore];
GPGLocalPlayerRank* localRank = [localScore publicRank];
NSLog(#"Current rank : %#", localRank.formattedRank);
I didn't try this code, but according to the class references, it should work fine. Let me know whether it works or not.
Also, put that code "after" submitting your score.