GKAchievement reportAchievements:withCompletionHandler: crash - ios

Everyone once in a while I will get crash reports for my App (via crittercism) with the following NSInvalidArgumentException crash:
+[GKAchievement reportAchievements:withCompletionHandler:]: unrecognized selector sent to class 0x3f940a84
The code looks like this:
NSMutableArray *achivementArray = [NSMutableArray arrayWithCapacity:20];
for (NSString *achievementID in achievementsToUpload) // achievementsToUpload is a NSSet
{
GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier:achievementID];
if (achievement)
{
...
[achivementArray addObject:achievement];
}
}
[GKAchievement reportAchievements:achivementArray withCompletionHandler:^(NSError *error)
{
if (error != nil)
{
NSLog(#"Error sending achievement: %#", error.localizedDescription);
}
}];
If the error is indeed inside reportAchievements:withCompletionHandler:, as far as I can see, the source of the error can come from two places: 1) achivementArray is not an NSArray or 2) error is not really an NSError.
In the case of #1, I create the array a few lines above the call and in case #2, I doubt the OS is sending back a non-NSError object.
Any ideas?

Header looks like:
+ (void)reportAchievements:(NSArray *)achievements withCompletionHandler:(void(^)(NSError *error))completionHandler __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_6_0);
So the issue may be in base sdk that you are using. This method is available starting from iOS 6.0.
Hope it will be helpful

Related

[MyClass initWithCoder:]: unrecognized selector sent to instance - On iOS16

I am using the [NSKeyedUnarchiver unarchiveObjectWithData:result] methode for unarchiving NSData in my objective c bases app.
NSMutableArray *array = (NSMutableArray *)[NSKeyedUnarchiver unarchiveObjectWithData:result];
This is working pretty fine upto iOS15. But since iOS16 I am getting a crash that [MyClass initWithCoder:]: unrecognized selector sent to instance 0x281069760.
In iOS16 code is working fine when I am using an array of NSDictionary or any system types like NSString. But I am adding my own custom class objects to the array and it makes the issue. (More intersting fact is that issue is only in real devices, not in simulators.)
I can see that unarchiveObjectWithData methos is depricated. So as suggested in xcode, now I have tried
NSError *err;
#try {
NSMutableArray *array = (NSMutableArray *)[NSKeyedUnarchiver unarchivedObjectOfClass:[NSMutableArray class] fromData:result error:&err];
}
#catch (NSException *exception) {
NSLog(#"Exception %#", exception.reason);
}
Now I am getting nil value for my array and the err is err = 0x0000000281ff1020 domain: nil - code: 4864
There were no issues before I updated my iPad to iPadOS 16. Any help is appreciated.
I have added the source code for this issue in this repo

Why am I getting this EXC_BAD_ACCESS error in Swift, but not in Objective-C? Cannot pinpoint it

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.

openParentApplication:reply: error with asynchronous network call in containing app

I'm getting stuck with an error when using my Watchkit Application. When I launch it, I ask the containing iOS app to get some data from network. The problem is that I get an error saying the containing app never calls 'reply()' :o But looking at my code, it should call it.
I tried to debug every step from openParentApplication to the 'reply()' call, and it seems to work well =X
Here is my code in the Watchkit extension
- (void)initDiaporamasWithSuccess:(void (^)())success andFailure:(void (^)(NSError*))failure {
NSLog(#"Ask to load diapos");
__weak typeof(self) weakSelf = self;
[WKInterfaceController openParentApplication:#{#"watchKit": #"watchKit.initDiapos"} reply:^(NSDictionary *replyInfo, NSError *error) {
if (error) {
NSLog(#"%#", error);
if (failure) {
failure(error);
}
return;
}
NSLog(#"got items : %#", replyInfo[#"diapos"]);
weakSelf.diaporamas = replyInfo[#"diapos"];
[weakSelf setDiaporama:replyInfo[#"firstDiapo"] AtIndex:0];
if (success) {
success();
}
}];
}
The result should be an NSDictionary containing an NSArray with some diaporamas basic informations, and an object (Diapo) containing the full informations of the first diaporama (e.g. self.diaporamas[0])
And here is the code in the containing app's AppDelegate :
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply {
// Maybe we could handle multiple watchKit extension calls that way ?
// Something like a key-value 'protocol' to run the right block of code
NSString *watchKitCall = userInfo[#"watchKit"];
NSLog(#"watchKit handled");
if ([watchKitCall isEqualToString:#"watchKit.initDiapos"]) {
[AppDelegate watchInitialObjects:^(NSDictionary *info) {
NSLog(#"Managed to get initial infos");
reply(info);
} failure:^(NSError *error) {
NSLog(#"Fail : %#", error);
reply(#{#"error": error});
}];
}
}
+ (void) watchInitialObjects:(void (^)(NSDictionary *info))success failure:(void (^)(NSError *error))failure {
NSDictionary *parameters = #{#"site" : #(14), #"limit" : #(10)};
[AppDelegate requestDiapoListWithParams:parameters success:^(NSArray *items) {
if ([items count] == 0)
{
NSError *error = [NSError errorWithDomain:#"com.domain.app" code:404 userInfo:nil];
failure(error);
return;
}
Diapo *firstDiapo = [items firstObject];
[AppDelegate requestDiapoDetailWithDiapo:firstDiapo success:^(Diapo *diapo) {
if (!diapo)
{
NSError *error = [NSError errorWithDomain:#"com.domain.app" code:404 userInfo:nil];
failure(error);
return;
}
NSDictionary *result = #{
#"firstDiapo" : diapo,
#"diapos" : items
};
success(result);
} failure:^(NSError *error) {
failure(error);
}];
} failure:^(NSError *error) {
failure(error);
}];
}
In the watchKitHandler, I call watchInitialObjects to get the diaporamas array and the first diaporama's informations.
In the watchInitialObjects, I make a first network call to get the array, and on success, I make an other network call to get the firs diaporama informations.
To make the calls and map the JSON into objects, I use RESTKit
I really don't get what could be the error =x
UPDATE
I forgot to write the error I get, here it is :
Error Domain=com.apple.watchkit.errors Code=2 "The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]" UserInfo=0x7fcb53e12830 {NSLocalizedDescription=The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]}
And I kept trying to know why I get this error, and I think I found it:
It seems that there is a (very little) timeout to do the work in the containing app. But I mapped the JSON data I received directly in the containing app and then, send those custom objects in the reply(). But when I removed the mapping part, it worked well !
So...that's why I think that was the problem =X
Does anybody could approve my thoughts or corrects me ?
After hours of searching and testing different codes, I finally found my problem...and it's obvious when we read the Apple documentation about 'application:handleWatchKitExtensionRequest:reply:' seriously...
here is the answer : (it's in the documentation)
The contents of the dictionary must be serializable to a property list file.
Which means that objects can ONLY be dictionaries, arrays, strings, numbers (integer and float), dates, binary data, or Boolean values
...I feel dumb ><

Calling objective-C typedef block from swift

I'm trying to call a method from swift.
The method is in a singleton written in objective-C
the block in the header file:
typedef void(^VPersonResultBlock)(Person *person, NSError *error);
- (void)askForMe:(VPersonResultBlock)block;
and here's the implementation of that method.
- (void)askForMe:(VPersonResultBlock)block
{
if (_me) block(_me,nil);
else {
[Person getMeWithBlock:^(PFObject *person, NSError *error) {
if (!error) {
_me = (Person *)person;
block(_me,nil);
}
else if (error) {
block(nil,error);
}
else {
NSDictionary *userInfo = #{
NSLocalizedDescriptionKey: NSLocalizedString(#"Operation was unsuccessful.", nil),
NSLocalizedFailureReasonErrorKey: NSLocalizedString(#"The operation failed to retrieve the user.", nil),
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(#"Check your network connection and try again", nil)
};
NSError *error = [[NSError alloc] initWithDomain:#"VisesAsyncErrorDomain" code:-10 userInfo:userInfo];
block(nil,error);
}
}];
}
}
In Objective-C, I can call this and it autocompletes without confusion.
[[VDataStore instance] askForMe:^(Person *person, NSError *error) {
// do things with myself that aren't strange
}];
Now let's say I want to call the same method from swift. The bridging header is setup, with the header file imported, but swift's expectation is confusing.
VDataStore.askForMe(VDataStore)
This is what shows up in the autocomplete options
(VPersonResultBlock!) -> Void askForMe(self: VDataStore)
what I was hoping for, was for this to autocomplete into a closure, and although it appears to see all of the information correctly, what it's expecting isn't lining up with what objective-C seems to understand.
How do I call this properly from swift?
Directly translate your ObjC calling code to Swift is
VDataStore.instance().askForMe() {
person, error in
// do things with myself that aren't strange
}
Your problem is that askForMe is instance method but you are accessing from class object VDataStore.askForMe. Swift will give you a function object that takes an instance as input.

End a turn in Game Center turn based match.

I´m having troubles with ending turns in my turn based game app.
The method I´m using is
GKTurnBasedMatch *currentMatch = [[GCTurnBasedMatchHelper sharedInstance] currentMatch];
[currentMatch endTurnWithNextParticipants:p turnTimeout:1000 matchData:data completionHandler:^(NSError *error) {
if (error) {
NSLog(#"%#", error);
}
}];
Here p is my NSArray for nextParticipants and this is my declaration for it:
and this is the declaration and assignment for
NSArray *p = [[currentMatch.participants reverseObjectEnumerator] allObjects];
I´m reversing the participants array to get the turn order of the players. (Only 2)
This all compile and runs without an error, but the turn never actually passes to the other player!
Thinking my p-array is the problem I´ve tried passing it without reversing it which produced the same result.
Does anyone know the correct way to handle this?
Replace your code with
GKTurnBasedMatch *currentMatch = [[GCTurnBasedMatchHelper sharedInstance] currentMatch];
GKTurnBasedParticipant *nextPerson = [currentMatch.participants objectAtIndex:((currentIndex + 1) % [currentMatch.participants count])];
[currentMatch endTurnWithNextParticipants:[NSArray arrayWithObject:nextPerson] turnTimeout:1000 matchData:matchData completionHandler:^(NSError *error) {
if (error) {
NSLog(#"%#", error);
}
}];

Resources