I want to handle the error case where endTurnWithNextParticipants fails. I therefore store match data by calling the cacheFailedEndTurnWithNextParticipants method at the end of this post. I provided the function only for reference.
The problem is that I don't know how I shall restore the data in the uncache method. How can I load the GKTurnBasedPlayers for the nextParticipants list? I use GKPlayer::loadPlayersForIdentifiers now, but I get the following warning:
"/Users/eternity/Documents/Xcode/DominationSK/DominationSK/GKMatchCache.m:134:74: Incompatible pointer types sending 'NSArray<GKPlayer *> * _Nullable' to parameter of type 'NSArray<GKTurnBasedParticipant *> * _Nonnull'"
How can I load GKTurnBasedParticipants from ID's?
Shall this retransmitting be done in another way?
-(void)uncache
{
if (_cache.count > 0)
{
NSDictionary *d = _cache[0];
[_cache removeObjectAtIndex:0];
[GKTurnBasedMatch loadMatchWithID:d[MATCH_ID]
withCompletionHandler:^(GKTurnBasedMatch * _Nullable match, NSError * _Nullable error) {
if (error == nil)
{
bad load method --> [GKPlayer loadPlayersForIdentifiers:d[PARTICIPANTS]
withCompletionHandler:^(NSArray<GKPlayer *> * _Nullable nextParticipants, NSError * _Nullable error) {
if (error == nil)
{
NSNumber *timeout = d[TIMEOUT];
warning --> [match endTurnWithNextParticipants:nextParticipants
turnTimeout:timeout.longValue
matchData:d[DATA]
completionHandler:^(NSError * _Nullable error){
// This is not finished
}];
}
else
{
[self addToCache:d]; // TODO: Add time to live
}
}];
}
else
{
[self addToCache:d]; // TODO: Add time to live
}
}];
}
}
// Just for reference
-(void)cacheFailedEndTurnWithNextParticipants:(GKTurnBasedMatch*)match
participants:(NSArray<GKTurnBasedParticipant*>* _Nonnull)nextParticipants
turnTimeout:(NSTimeInterval)timeout
matchData:(NSData* _Nonnull)matchData
error:(NSError* _Nonnull)error
{
if ([self shallBeCached:error])
{
NSMutableArray *playerIds = [[NSMutableArray alloc] init];
for (GKTurnBasedParticipant *p in nextParticipants)
{
[playerIds addObject:p.player.playerID];
}
NSDictionary *d = #{MATCH_ID : match.matchID,
PARTICIPANTS : playerIds,
TIMEOUT : [NSNumber numberWithLong:timeout],
DATA : matchData};
[self addToCache:d];
}
}
It's possible to get the participants from the match, and then retrieve player IDs from that array, and finally update the participants attribute in the match.
Related
My boss said at iOS beta version 13.2 can read bank credit card info by iPhone's NFC function. But I don't know how to achieve it.
What I know might work method is NFCTagReaderSession, but it doesn't work at following codes:
[self.tagSes invalidateSession];
self.tagSes = [[NFCTagReaderSession alloc]initWithPollingOption:NFCPollingISO15693 delegate:self queue:dispatch_get_main_queue()];
if (NFCTagReaderSession.readingAvailable) {
self.tagSes.alertMessage = #"send card to phone's back";
[self.tagSes beginSession];
} else {
NSLog(#"no support NFC");
}
- (void)tagReaderSession:(NFCTagReaderSession *)session didDetectTags:(NSArray<__kindof id<NFCTag>> *)tags{
NSLog(#"%#\n",tags);
self.curentTag = [tags firstObject];
if (self.curentTag.type == NFCTagTypeMiFare) {
id<NFCMiFareTag> mifareTag = [self.curentTag asNFCMiFareTag];
NSData *data = mifareTag.identifier;
NSLog(#"data:%#",data);//** 1. there can print card type is NFCMiFareTag **
[session connectToTag:self.curentTag completionHandler:^(NSError * _Nullable error) {
if (error) {
NSLog(#"fail:%#",error);//** 2. then ,in here throw a fail,tell me connect break **
return ;
}
[mifareTag readNDEFWithCompletionHandler:^(NFCNDEFMessage * _Nullable messages, NSError * _Nullable error) {
NSLog(#"--->>%#,%#",messages,error);
NSArray *ary = messages.records;
for (NFCNDEFPayload *rec in ary) {
NFCTypeNameFormat typeName = rec.typeNameFormat;
NSData *payload = rec.payload;
NSData *type = rec.type;
NSData *identifier = rec.identifier;
NSLog(#"TypeName : %d",typeName);
NSLog(#"Payload : %#",payload);
NSLog(#"Type : %#",type);
NSLog(#"Identifier : %#",identifier);
}
}];
}];
}
}
I'm sorry, but I can't find the answer in plain English, or at least every answer I see assumes I have a certain amount of knowledge that must not have. I just need to delete a CKSubscription. How do I do this? All tips and help will be greatly appreciated.
Assuming you don't have the subscriptionID of the subscription you want to delete, you should first fetch all of the subscriptions and figure out the subscriptionID of the subscription you want to delete:
[[[CKContainer defaultContainer] publicCloudDatabase] fetchAllSubscriptionsWithCompletionHandler:^(NSArray<CKSubscription *> * _Nullable subscriptions, NSError * _Nullable error) {
if (!error) {
//do your logic to find out which subscription you want to delete, and get it's subscriptionID
} else {
//handle error
}
}];
Then, now that you have the subscriptionID, simply delete it:
CKModifySubscriptionsOperation *operation = [[CKModifySubscriptionsOperation alloc] initWithSubscriptionsToSave:nil subscriptionIDsToDelete:YOUR_SUBSCRIPTION_ID];
operation.modifySubscriptionsCompletionBlock = ^(NSArray <CKSubscription *> * __nullable savedSubscriptions, NSArray <NSString *> * __nullable deletedSubscriptionIDs, NSError * __nullable operationError) {
//handle the results when the operation has completed
};
[[[CKContainer defaultContainer] publicCloudDatabase] addOperation:operation];
Don't forget to always handle all the possible errors properly.
I'm using GKPeerPickerController and GKSession classes and I'm trying to send rather large amount of data (appr 20 mb, images). The problem is that when I send more then, say 10 megabytes the appropriate delegate method of the receiver (- (void) receiveData:(NSData *)data fromPeer:(NSString *)peer inSession: (GKSession *)session context:(void *)context;) simply does not getting called. Is there some size restrictions? There is no completion handlers or errors returned. The data is sent to nowhere...I have another question also - is it possible to notify the sender that the data is received? (So that I can send queued packages). Thanks in advance!
Added
This method forms a dictionary with objects which I want to send
- (void)sendQuiz:(id<BlitzModelQuizProtocol>)quiz {
if ([quiz.backgroundType intValue] == BackgroundTypeUser) {
NSMutableDictionary * background = [NSMutableDictionary new];
NSString * filePath = [[BUIFileManager sharedInstance] filePathWithFileName:quiz.backgroundPath];
NSData * imageData = [NSData dataWithContentsOfFile:filePath];
[background setObject:imageData forKey:kQuizBackgroundImage];
[background setObject:quiz.backgroundPath forKey:kQuizBackgroundName];
[self.objectsToSend setObject:background forKey:kQuizBackground];
}
for (id<BlitzModelQuestionProtocol>question in quiz.questions) {
// Improve this logic when answers become > 1
if ([question.smileyType intValue] == SmileyTypeCustom) {
NSMutableArray * customSmiles = [NSMutableArray new];
for (id<BlitzModelAnswerProtocol>answer in question.answers) {
NSLog(#"smiley is: %#", answer.smiley);
NSMutableDictionary * smiles = [NSMutableDictionary new];
NSString * filePath = [[BUIFileManager sharedInstance] filePathWithFileName:answer.smiley];
NSData * imageData = [NSData dataWithContentsOfFile:filePath];
[smiles setObject:answer.smiley forKey:kSmileName];
[smiles setObject:imageData forKey:kSmileImage];
[customSmiles addObject:smiles];
}
[self.objectsToSend setObject:customSmiles forKey:kCustomSmiles];
}
}
NSArray * statistics = [self statisticsForQuizId:quiz.objectId];
if ([statistics count] > 0) {
NSMutableArray * blitzStatistics = [NSMutableArray new];
for (id<BlitzModelStatisticProtocol>stat in statistics) {
BlitzStatistic * statistic = [[BlitzStatistic alloc] initWithObject:stat];
[blitzStatistics addObject:statistic];
}
[self.objectsToSend setObject:blitzStatistics forKey:kStatiscticObjects];
}
else {
BlitzQuiz * quizModelObject = [[BlitzQuiz alloc] initWithObject:quiz];
[self.objectsToSend setObject:quizModelObject forKey:kQuizObject];
}
NSData * data = [NSKeyedArchiver archivedDataWithRootObject:self.objectsToSend];
[self sendDataToPeers:data];
}
This is my sendData method:
- (void) sendDataToPeers:(NSData *) data {
NSString * title;
if (self.currentSession) {
NSError * error = nil;
if ([self.currentSession sendDataToAllPeers:data
withDataMode:GKSendDataReliable
error:&error]) {
NSLog(#"quiz sent");
}
else {
NSLog(#"error desc is: %#", [error localizedDescription]);
}
}
}
The method - (BOOL)sendDataToAllPeers:(NSData *)data withDataMode:(GKSendDataMode)mode error:(NSError **)error returns YES with no error (it's nil). What am I doing wrong?
Added
Sometimes the data is received successfully though sendData still returns NO without any error. Neither of delegate methods that handle error is getting called.
I am able to fetch all messages using fetchMessagesByUIDOperationWithFolder:, however, message.flags all return 0 when some messages are unread, most are read and some are starred.
MCOIMAPMessagesRequestKind requestKind = MCOIMAPMessagesRequestKindHeaders;
NSString *folder = #"INBOX";
MCOIndexSet *uids = [MCOIndexSet indexSetWithRange:MCORangeMake(1, UINT64_MAX)];
MCOIMAPFetchMessagesOperation *fetchOperation = [session fetchMessagesByUIDOperationWithFolder:folder requestKind:requestKind uids:uids];
[fetchOperation start:^(NSError * error, NSArray * fetchedMessages, MCOIndexSet * vanishedMessages)
{
if ( ! error ) {
for ( MCOIMAPMessage * message_ in fetchedMessages ) {
// I only want UNREAD messages.
}
}
}
I have tried using if ( message_.flags & MCOMessageFlagSeen ) but still, all flags return as 0.
What is the proper way to see if the message is UNREAD?
For anyone having the same issue, you need to also include the kind request for flags: MCOIMAPMessagesRequestKindFlags.
MCOIMAPMessagesRequestKind requestKind = MCOIMAPMessagesRequestKindHeaders|MCOIMAPMessagesRequestKindFlags;
Then, look for the unread flag:
for ( MCOIMAPMessage * message_ in fetchedMessages ) {
if ( message_.flags == 0 ) {
// I have a suspicion that this is not the correct
// way to do this, but it seems to work the way I need.
}
}
You can use 0 or better as below, which is 0 too, but who knows if they decide to change it to something else later:
if(message_.flags == MCOMessageFlagNone)
CMMessageSession *session = [CMessageManager shareManager].session;
MCOIMAPSession *imapSession = [session imapSession];
MCOIMAPFolderStatusOperation *folderOpera = [imapSession folderStatusOperation:folder];
if (!folderOpera) {
NSLog(#"获取imap邮箱状态失败");
[self fetchUnReadCountFromDB:folder complete:complete];
return;
}
[folderOpera start:^(NSError * _Nullable error, MCOIMAPFolderStatus * _Nullable status) {
if (error) {
NSLog(#"获取imap邮箱状态失败: %#", error.localizedDescription);
[self fetchUnReadCountFromDB:folder complete:complete];
return;
}
NSInteger count = status.unseenCount;
if (complete) {
complete(folder, count);
}
}];
I am building an app for displaying assets (PDF, Video, Etc).
It starts by downloading a JSON and parsing it into Core Data Objects <-- This part works fine.
These objects are a hierarchical set of Nodes that have a relationships set up in my model. each node can either be a FILE or a FOLDER. <-- no problems.
Then I have instance methods built into my NSManagedObject Subclasses that will download the file associated with that object (ie. a PDF). Then it sets
self.isAvailable = [NSNumber numberWithBool:YES];
Meanwhile, I have a UITableView that displays a list of assets. eventually it will update in real-time, but for now this is where I am having issue. I first had the view controller keep a pointer to the CoreData object that represents the folder it displays, but it appears that If the context gets updated, the pointer becomes invalid (ie. fails to fault).
Core data is not being very specific on what the problem is, or even where its happening, but it seems to crash when I set isAvailable with
*** Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x1d5f9e50 <x-coredata://EDE66B97-B142-4E87-B445-76CAB965B676/Node/p58>''
I feel like the problem is that I shouldn't just keep a strong reference to a core data object as my model. Is there a better (less crashy) way to do this?
I have started playing with NSFetchedResultsController and using objectID's instead, but I haven't gotten anywhere yet.
- (void)populateChildren {
NSString * urlString = [NSString stringWithFormat:#"%#/%#", [CMPConstants hostURLString], self.SBUCode];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:self.downloadQueue completionHandler:^(NSURLResponse * response, NSData * data, NSError * error) {
if (data) {
NSDictionary * dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
[self processParsedObject:dict];
} else {
NSLog(#"%#", urlString);
}
}];
}
#pragma mark - Parse JSON into NSManagedObjects
- (void)processParsedObject:(id)object {
[self processParsedObject:object depth:0 parent:nil key:nil];
[[NSManagedObjectContext MR_contextForCurrentThread] MR_saveToPersistentStoreAndWait];
}
- (void)processParsedObject:(id)object depth:(int)depth parent:(Node *)parent key:(NSString*)key {
if ([object isKindOfClass:[NSDictionary class]]) {
if (depth == 0) {
// Grab content node if depth is 0;
object = [object valueForKey:#"content"];
}
// FIXME: Change this to a real primary key once we get one.
static NSString * primaryKey = #"name";
// Look for existing object
Node * testNode = [Node MR_findFirstByAttribute:primaryKey withValue:[object valueForKey:primaryKey]];
// Create new node pointer
Node * newNode;
if (testNode) {
// Update existing Node
newNode = testNode;
} else {
// Build a new Node Object
newNode = [Node MR_createEntity];
newNode.isAvailable = [NSNumber numberWithBool:NO];
}
// Get keys
NSArray * keys = #[#"name",
#"type",
#"index",
#"size",
#"videoDemensions",
#"videoId",
#"fileName",
#"fileType",
#"path"];
if ([[object valueForKey:#"type"] isEqual:[NSNull null]]) {
NSLog(#"%#", object);
}
// Loop to set value for keys.
for (NSString * key in keys) {
id value = [object valueForKey:key];
if (![[object valueForKey:key] isKindOfClass:[NSNull class]]) {
[newNode setValue:value forKey:key];
}
}
// Set calculated properties.
[newNode setSbu:[self SBUCode]];
[newNode setParent:parent];
[[NSManagedObjectContext MR_contextForCurrentThread] MR_saveToPersistentStoreAndWait];
// Sync local file.
if (!newNode.isAvailable.boolValue) {
[newNode aquireFileInQueue:self.downloadQueue];
}
// Process children
for(NSString * newKey in [object allKeys]) {
id child = [object objectForKey:newKey];
[self processParsedObject:child depth:depth+1 parent:newNode key:newKey];
}
} else if ([object isKindOfClass:[NSArray class]]) {
for(id child in object) {
[self processParsedObject:child depth:depth+1 parent:parent key:nil];
}
} else {
// Nothing here, this processes each field.
}
}
This Method is an instance method of the Node class.
- (void)aquireFileInQueue:(NSOperationQueue *)queue {
if ([self.type isEqualToString:#"VIDEO"]) {
// Videos are available, but not downloaded.
self.isAvailableValue = YES;
return;
}
if (self.path == nil || self.fileName == nil) {
NSLog(#"Path or Filename for %# was nil", self.name);
return;
}
// Build the download URL !! MAKE SURE TO ADD PERCENT ESCAPES, this will protect against spaces in the file name
// Also make sure to slash-separate the path and fileName
NSURL * downloadURL = [NSURL URLWithString:[NSString stringWithFormat:#"%#/%#",
[self.path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
[self.fileName stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
// Build the download request
NSURLRequest * downloadRequest = [NSURLRequest requestWithURL:downloadURL];
// FIXME: Authentication Code for JSON service
// Show network activity indicator
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
// Send Asynchronus Request for fileData
[NSURLConnection sendAsynchronousRequest:(NSURLRequest *)downloadRequest queue:queue completionHandler:^(NSURLResponse * response, NSData * data, NSError * error) {
// Hide network activity indicatior
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
// Cast URL Response to HTTPURLResponse
NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse *)response;
// If statusCode is 200 (successful) and data is not nil, save data
if (httpResponse.statusCode == 200 && data) {
[data writeToURL:[self fileURL] atomically:NO];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self setIsAvailable:[NSNumber numberWithBool:YES]];
}];
}
}];
}
- (void)prepareForDeletion {
// Remove file from Filesystem
[[NSFileManager defaultManager] removeItemAtURL:[self fileURL] error:nil];
}
- (NSURL *)fileURL {
// Return local file URL
return [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/%#", [Node applicationDocumentsDirectory], self.fileName]];
}
I am not familiar with MagicalRecords
A 'Could not fullfil fault' error occur when a context is holding an un-faulted object (an object stub), but the actual object in the database does not exist (deleted or was never saved).
My first advice:
If you work in a multithreaded environment, try to hold faulted objects.
use -existingObjectWithId:error: and fetch requests with:
[fetchRequest setReturnsObjectsAsFaults:NO];
[fetchRequest setIncludesPropertyValues:YES];
[fetchRequest setRelationshipKeyPathsForPrefetching:/*relationships you can afford to prefetch*/];
My second advice (to debug your issue):
Print you deletedObjects set before each save you make to the store to see which context caused the fault.
My third advice:
merge changes to the main context (my guess is that MagicalRecords does that for you).
note 1: deletes may be implied (you don't explicitly use deleteObject: by setting a relationship in cascade/deny mode for example)
note 2: you can not avoid this exception in a multithreaded environment (AFAIK), unless you pass all your saves through the main context (using parentContext) or by always using prefetched objects (not using relationships directly).