Monday, October 17, 2016

AirDrop Can't Discover On Mac Server

AirDrop the debatably easy to use wireless file transfer support system from Apple to share files between iOS and macOS devices.
AirDrop works very well, until it doesn't.
Each year since AirDrop was made available on the OSX it had worked very well for a few weeks, then stopped working between the Mac and iPhones.
Once a new OSX update went out, AirDrop would work again and I chalked it up to a bug fix, until a few more weeks it stopped working again.
Sierra is released, AirDrop works better than ever and I and transferring photos from my iPhone to a MacBook with ease, until just last week.
This update I was able to track the moment AirDrop stopped working for me, the only system change I made, was updating the Mac Server install. It finally dawned on me. Mac Server installed on the MacBook was breaking AirDrop.

If your AirDrop has stopped working on your Mac, and you know you've installed the Mac Server. The rest of the internet is resolved to turning off/on the bluetooth to wake up AirDrop discovery. And not the solve. You will have to choose between Server or AirDrop transfer until there's a fix. (I've file a Radar, update below)

If you have Mac Server already installed on the workstation MacBook and you would rather get AirDrop back at the sacrifice of Mac Server features, here is how you can remove Server and restore AirDrop.
  • Launch Server.app
  • Destroy the Open Directory, then disable the component
  • Turn off all Server.app services (Web, DNS, all..)
  • Close Server.app and run command below in terminal:
$ /Applications/Server.app/Contents/ServerRoot/usr/share/devicemgr/backend/wipeDB.sh
  • Delete Server.app (Drop to Trash), wait about 5 seconds for the services to notice app deletion.
  • Delete the folder /Library/Server
  • Delete any certificates relating to the server in Keychain Access
  • Restart, and try to AirDrop


Radar update: 
Though there was an issue AirDropping from a 2013 MacBook to iOS device resolved by the above steps. It did not duplicate after trying a second install of Server. This makes me think a portion of older Server's files or settings was not completely overwritten when I upgraded to Sierra Server. So much like turning it off and on again, do try to completely uninstall and reinstall.

Additionally Apple replied about a different MacBook 2011 I included in the issue:
"AirDrop between Mac computers and iOS devices is supported by the following Mac models:
MacBook (Early 2015 or newer)
MacBook Pro (2012 or newer)
MacBook Air (2012 or newer)
Mac mini (2012 or newer)
iMac (2012 or newer)
Mac Pro (Late 2013)"

Monday, September 19, 2016

Copyright Scare

As I'm working with memes lately I came across some copyright concern. When does a meme qualify as fair and common use after becoming a internet sensation and widely circulated, and when can the originating image that inspired the meme sue for license infringement. Turns out Getty Images got on board the Troll train and started firing off license infringement claims to suck up some extra money from the internet.
The meme in question is the Socially Awkward Penguin that supposedly is a National Geography image that Getty owns the rights to license, or at least the right to bully web sites, by the sounds of some of the articles. Well being a semi creative I wanted to take a page out of GetDigital book and create a legally safe inspired original illustration of a penguin on a blue meme background.
And because I believe memes belong to the internet, I'm sharing this illustration freely.




Saturday, September 17, 2016

Stellar Games: Message Meme

Stellar Games: Message Meme: Make! All The Memes! Stellar Games released Message Meme. The first and best of it's kind iMessage App extension for iOS10. Message M...

Thursday, May 10, 2012

UIStoryBoard Power Drill, Batteries Included

I got a lot of great response and questions about my post with the UIStoryBoard. This means people are interested in using it, and I'm not good with explaining things.

I did combo this drill down with a sorta complex core data model and assumed this was setup before handing off objects to each subsequent class. I'm going to nix the core data in favor of a simple input, but we are going to keep music on the table!

Lets start here : GitHub-UIStoryboardExamples

This Xcode project will start you out with some plist data that is a nested list of music genre, artist, album, song. At the same time not to complicate the ViewControllers on how it handles these different tiers the data is only nested as a "NSString name" , "NSArray items" pair. This way we concentrate straight on the loopback of the table segue.

First take a look at the "Auto" side of this app, this table view controller uses the cells prototype push outlet to auto loopback to the controller again. Because this does not utilize the didSelectRowAtIndexPath: (or to be clear) this does call but, performs the segue regardless. This practice will make use of the sender value in the prepareForSegue: to identify which cell was clicked on. Realize we are not making a decision where to go next, thats already happening via the segue, we are only deciding what to take with us. We send some values to the next view controller that is another instance of this current one, but is not this exact one. And the view controller is then stacked in the navcontroller and is now only aware of the sub-array we handed down to it.
This auto drilling can be a little limited because of the way we always drill into the same view controller class type. But! if you want to explore here, I suggest you make another cell prototype with a different identifier and a different segue outlet, and use it at your desired tier.

The "Manual" side takes on calling segues from code and needs the fake UIBarButtonItem to create the loopback segue. This little trick will let you define segue identifiers and then let you decide what direction to take when you call performSegueWithIdentifier: The other important thing is to know what object you want to send along here as the sender. The cell is no longer the sender that is triggering the segue, didSelectRowAtIndexPath is and we could likewise send the UITableViewCell or filter it down to the object we are after. This is the place I test for the data nesting and chose to end the drill down. Calling the different segue will push over to the details view, but the stack of drills downs is still managed by the navcontroller.

Good luck and happy storyboarding.

Sunday, February 26, 2012

Game On, TurnBased Game Center

I was excited to see Apple add the TurnBased system to their GameCenter last year. Since it's beta release I have been testing with it on and off for some time now to get the best experience from it that I believe I can offer. The difference for me is I approached this TurnBased GameCenter as a partial replacement for the PHP async system I was mildly developing before Apple's announcement.

Where I started in PHP;
I started with a SQLLite model that was going to mirror the online storage of the games. This maybe an over ambitious approach to a game largely played while online and connected, but to develop good UX I felt that the game should not just be a brick when no connectivity is around. (Even NewToy's games had offline modes before the take over)
GameCenter for 4.1 was in full swing and I made the decision to leap frog off the GameCenter generated Apple player_ids for organizing user accounts, while avoiding making people create some new account in my system. What this meant was if a user signed into the game with a GameCenter account, I would store there player_id, alias, and push-token. I even devised a pattern for flagging the account for open invites, direct invites, or dnd.
The challenge here was when the app had time to refresh was to synchronize the records of turns that were behind. This could be one turn record back, two if a 3 player game, maybe the phone was changed or deleted, then all records would need to be resync'd. In this design I was storing each turn as a record of 5 or more fields and doing these local vs server index checks to avoid transferring too much repeat json data.
So what did this mirror solve? The ability to look at the game board, plan your next move, even make your next move and cache the result for later upload, if and when no connectivity was available. Additional side effects, less lock down loading screens. If any persistent data about the handful of games a player is playing is saved and synced locally then I could stay away from locking down the interface with a loading overlay while online request get fulfilled.

Use Apple's servers;
Apple announces Api changes to handle games non-realtime, and right out of the gate they solve one trick that I was trying to work around. Push Notifying new players about a game they've yet to ever download. Even though GameCenter could always Push-Notify a friend of a game invite. But that process must have  gone a little like: Mike invites John to play a game, John is busy watching a Movie, Mike is left hang'in. Or John hasn't heard of this game, he visits the AppStore and reads about the game, Mike is tired of sitting at the invite screen waiting to connect, Mike closes the game and the invite is lost.
So now with the TurnBasesMatch objects the user involved are tracked, the whole game state needs to be compressed and fit into 4kb or less, and push notifications are handled by Game Center and I don't even need to request a token. So whats left for me to do?
If you are leaving all the data on Apple's end and only updating from request then there are a few online calls that seem to get chained up rather quickly.
- authenticateWithCompletionHandler: This one gets you in the door but is the first request you have to wait for before you can get updated info.
- loadMatchesWithCompletionHandler: This gets you some rudimentary data, nothing you can show many details about. Telling how many matches the player is involved in, who's turn it is (sorta) and when the last turn was taken (again sorta) I say this because the individual GKTurnBasedParticipant objects in the GKTurnBasedMatch object have the timestamps, if you'd like to show when the matches last turn was taken I figure a sort is the easiest way: 

NSSortDescriptor* sort = [NSSortDescriptor sortDescriptorWithKey:@"lastTurnDate" ascending:YES];
GKTurnBasedParticipant* pat = [[self.gkMatch.participants sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort]] lastObject];
self.lastTurnDate = pat.lastTurnDate;

That's not enough, we also need to show the user "who's" turn it is, the GKTurnBaseMatch only gave use the player_ids. So first thing is to get the ids into an array.

NSMutableArray* playerids = [NSMutableArray arrayWithCapacity:[m_gkMatch.participants count]];

[m_gkMatch.participants enumerateObjectsUsingBlock:^(GKTurnBasedParticipant* obj, NSUInteger idx, BOOL *stop) {
        [playerids addObject:obj.playerID];
}];

- loadPlayersForIdentifiers: Once you have this array of GKPlayers back with alias for the player_ids you sent in you can start to match up the ids from the GKTurnBasedParticipants and display a name for the user who's turn it is.
Then finally before you can rebuild you game board from the 4kb of data, you have to request it.
- loadMatchDataWithCompletionHandler:
Having all these separated calls can seem like a bit A->B->C->D but to tell you the truth it's not that different from the calls I was designing in PHP. Data that can plug together but can really be handled in any order or concurrently if you store it right.




Synchronizing GKObjects:
Well the idea isn't much different, When an opportunity to get data now from the local storage beats the need to retrieve data, I have something to show the user. If after a request is fulfilled and data has changed then I have a new opportunity to update things behind the scene while the user is not looking at something, or slide in the additional data.


For example: The Users phone knows of a game thats in progress, he is waiting for a friend to take a turn. Because this games data is being sync'd the players stored locally are Mike and John, As soon as the app is shown that info about John is already known and I don't have to retrieve the alias again before displaying it. Another invite comes in for a 3 player game including Mike, John, and now ID:32789462 well I can display this new cell in a table and use a temp alias of 'New Player' while the request goes out to get the players alias. In the mean time Mike opens the game board and looks at the last few turns. With the correlation of the loadPlayersForIdentifiers feeding directly back to the CoreData storage of the Player ManagedObject then UITableViews, GameBoradViewControllers can be notified of the changes to the Alias value independent of the Match Object.


In addition here are some other methods that I've built to help puzzle together some of the GKTurnBased separations.


Is it this local user's turn?
+ (bool)IsLocalTurn:(GKTurnBasedMatch*)aMatch {

    if ([[GKLocalPlayer localPlayer].playerID isEqualToString:aMatch.currentParticipant.playerID]) return true;
    else return false;
}


How many of these players are still in the game?
+ (NSArray*)ActivePlayers:(GKTurnBasedMatch*)aMatch {
    if (!aMatch) return NULL;
    NSMutableArray* actives = [NSMutableArray arrayWithCapacity:[aMatch.participants count]];
    [aMatch.participants enumerateObjectsUsingBlock:^(GKTurnBasedParticipant* player, NSUInteger idx, BOOL *stop) {
        if (player.status != GKTurnBasedParticipantStatusDeclined && player.status != GKTurnBasedParticipantStatusDone) {
            [actives addObject:player];
        }
    }];
    return [actives copy];
}


The next player should be a active user, or the game will get stuck, or error.
+ (GKTurnBasedParticipant*)NextPlayerAfter:(GKTurnBasedParticipant*)player InMatch:(GKTurnBasedMatch*)aMatch {
    if (!aMatch) return NULL;
    NSArray* actives = [SOTurnManager ActivePlayers:aMatch];
    int acount = [actives count];
    if (acount > 1) {
        int index = [actives indexOfObject:player] + 1;
        return [actives objectAtIndex:index%acount];
    }
    return NULL;
}



Tuesday, January 24, 2012

UIStoryboard Power Drill


I've read some good reasons why Storyboards are not ready for prime time. Some of the articles like Jonathan at toxic software.com simply help me find out that I wasn't doing something wrong, it just wasn't meant for that. But there are some benefits from using a storyboard that I wanted to keep, so I got adamant about finding workarounds. Here's one:




Drill down UITableViews - Standard



1. Provided you've started with a subclassed UITableViewController, or a TableView in a UIViewController that is already the root relationship from a UINavigationController.

2. You link the push outlet from the cell prototype back to the table view controller.
This will automatically provide the control to push-in another of the same ViewController when a cell is clicked.


3. Give the loopback segue an identifier string in the attributes inspector.

4. Instead of implementing tableView:didSelectRowAtIndexPath: in the subclassed viewcontroller you will use
prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

This method will be passed the segue object that is the "tableLoopBack" the sender object for this segue type will be the UITableViewCell.

5. Prepare what data to send to the next instance the the table.


- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    
    NSArray* myObjectArray;
    UITableView* myTable;
    
    if ([segue.identifier isEqualToString:@"tableLoopBack"]) {
        SubClassedTable* nextSct = segue.destinationViewController;
        UITableViewCell* myCell = (UITableViewCell*)sender;
        NSIndexPath* idx = [myTable indexPathForCell:myCell];
        nextSct.myObject = [self.myObjectArray objectAtIndex:idx.row];
    }
}

6. In the TableDataSourceDelegate have it decide how to propagate your tables data array based now on the defined and set value of "myObject". This is very different for everyones data types.

An example, using Core Data and have say AMusicLib > Artists > Albums > Songs > Info

The myObject would ideally be of type NSManagedObject* so that you could be any of the above. When the dataDelegates are called like tableview:numberOfRowsInSection: you would want to implement something like the following to handle the many layers.

- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if ([self.myObject isKindOfClass:[Music class]]) {
        Music* myMusicLib = self.myObject;
        return [myMusicLib.artists count];
    } else if ([self.myObject isKindOfClass:[Artist class]]) {
        Artist* myArtist = self.myObject;
        return [myArtist.albums count];
    } else if ([self.myObject isKindOfClass:[Album class]]) {
        Album* myAlbum = self.myObject;
        return [myAlbum.songs count];
    }
}

Now in the implementation of the tableview:cellForRowAtIndexPath: use a similar kindOf conditional so that the cells properties can feed from the difference of the objects at each layer.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellID = @"musicCell";
    UITableViewCell* newCell = [tableView dequeueReusableCellWithIdentifier:cellID];

    if ([self.myObject isKindOfClass:[Music class]]) {
        Music* myMusicLib = self.myObject;
        Artist* aArtist = [[myMusicLib.artists allObjects] objectAtIndex:indexPath.row];
        newCell.textLabel.text = aArtist.name;
    } else if ([self.myObject isKindOfClass:[Artist class]]) {
        Artist* myArtist = self.myObject;
        Album* aAlbum = [[myArtist.albums allObjects] objectAtIndex:indexPath.row];
        newCell.textLabel.text = aAlbum.title;
        newCell.imageView.image = [UIImage imageNamed:aAlbum.art];
    } else if ([self.myObject isKindOfClass:[Album class]]) {
        Album* myAlbum = self.myObject;
        Song* aSong = [[myAlbum.songs allObjects] objectAtIndex:indexPath.row];
        newCell.textLabel.text = aSong.name;
        NSDate* aTime = [NSDate dateWithTimeIntervalSince1970:[aSong.length intValue]];
        NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
        [dateFormatter setDateFormat:@"m:ss"];
        newCell.detailTextLabel.text = [dateFormatter stringFromDate:aTime];
    }
    return newCell;
}
And finally because earlier we used a property self.myObjectArray, this property could have been a consistent type simple array, but because I walked you through handing down different managedObjects this myObjectArray getter would also need to return an NSArray from the different managedObject's intended NSSet. Same as just above, just return [myArtist.albums allObjects] or [myAlbum.songs allSongs] and so on for the prepareForSegue can hand off the right object to the next controller.


Drill Down TableViews - Options

The above is simple but can be limiting. The segue always pushes down to another of the same table view. Now you want options, lets go this why, or that.

1. Don't use the push outlet on the cell prototype. This is whats limiting the control of where we go.

2. Do Implement the tableView:didSelectRowAtIndexPath: this is where we will decide what segue we take, Or none.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    if (!sec) {
        Section* aSect = [[self.sectionFetchedController fetchedObjects] objectAtIndex:indexPath.row];
        [self performSegueWithIdentifier:@"tableLoopBack" sender:aSect];
    } else {
        Clip* aClip = [self.clipFetchedController objectAtIndexPath:indexPath];
        [[VideoPlayerController Player] setMoviePlayerForClip:aClip];
    }
}

What we are effectively doing here is getting the option to loop into another table, or stay and do something else like load a video in a modal MPMoviePlayerController. Or even call another segue that goes to detail view.

3. Continue to make use of the prepareForSegue method

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"tableLoopBack"]) {
        VideoListController* vlc = segue.destinationViewController;
        Section* mySec = (Section*)sender;
        vlc.title = mySec.title;
        vlc.sec = mySec;
    }
}

This is so data is still passed to the new instance of this controller for drill down purposes, even if not all cells drill.

4. And now the hack, if in step 1 we do not link the cell to push to the this viewController, we still need to define a segue and name it "tableLoopBack".
Add a UIBarButtonItem to the viewController and set the push outlet on it to loopback, identify that new segue connection as "tableLoopBack" and you have access to it when calling performSegue.

5. Add another UIBarButtonItem, and link it to another ViewController like one that layout details level. Identify that segue as another name like "detailSegue" and performSegue from didSelectRowAtIndexPath: where you can pass down the object full of details right into the sender, which will also be handed through the prepareForSegue, so make sure you hand it down again to the desired property of the new destinationViewController.


Helpful? Vote me up: Stackoverflow.com

Sunday, December 4, 2011

Cut the fat, NSData diet

Fat XML
I'm a long time user of the XML format for transferring data between client and host. It's human readable, can be edited by hand easily in most cases, and it conveys objects and nesting well. So in the past I was often designing a object scheme and custom parsing loops that solved the issues that faced constantly updating applications and site I've been in charge of through out my flash career. The problem I was always concerned with when designing the scheme is how much data was just being taken up by open tags, close tags, and parameter defines. This became exceptionally a problem when working for the company responsible for "MMO crack". I came on board to find that they where converting C objects out to XML. Now I wasn't programming in C yet myself and had lacking experience there, all my objects where far more dynamic in action script. But when I was tasked to parse open some game data I was shocked to see my work be brought to it's knees when getting the users character roster and sub inventory arrays that were compiled into XML Data and downloaded to the Flash client. This may seem to make sense you need to know this information in order to play that game. But for a new character that had no team members, and starter inventory, the XML data surely didn't need to be 2-3mb to tell me I'm a noob. It turned out that when the  object structures are being defined, the play character with some properties, has a max inventory lets say 30 items, and a roster of teammates with a max of say 30, each with their own properties and inventory of 30. So in memory this struct was created with these arrays of array defined. And even though the inventory had maybe two items, and the team roster empty, the space for this storage had been allocated. This left the company's choice XML writer with the task of creating a node tree for every allocation, even if the value was null. Not only was the XML nodes verbose, the property attributes fully spelled out, but every download came in like a template worksheet to be filled in and a later time.

Average JSON
I had always made it a conscious effort to slim down the data that was being sent back and forth in applications I designed, even though this was often not my choice when working at large companies. JSON came along in the midst of all this work and I found it a great way to slim down a lot of the parseable data. A few iOS projects that I started on in 08' I was doing a lot of tracking multiplayer turns moves on a PHP/SQL backend. I was comfortable using JSON and NSDictionaries to convert the data because I could understand how it was being handled on both places. But I did alway prefer to have my classes with their instance variables. I felt like KVO in NSDictionaries was just a long way to get at values in code and made for more lines. I also want to avoid waiting and loading screens, so I separated many requests into small object calls, and take it even further I got on a kick where I would make property names only one or two characters long. If the property name was 'int character_id = 500;' I would hold the value in a temporary NSDictionary to encode the property to {c:500}. Every little bit helps I figure.

Thin Binary Data
This year I have been getting more ambitious about using some Multiplayer GameCenter features, wether it be live games, or turn based games. Now the out of the box features of encoding NSDictionaries to a compatible NSData binary I think leave you somewhere in-between the first to parts of this post. Where you can define small objects maps, but this its coded into a XML like string before its coded to binary. Game data needs to be condense to make a quick round trip in peer to peer or to fit in a maximum size allowed for turn based storage. I've come full circle now to better understand C structs and have come to some pros and cons about using them to transmit game data. What really helped me prepare for the use of C structs as NSData was the GKTank sample. Now if your not entirely sure whats going on in this sample like I once was, then hopefully I can help you understand it.
First you must be comfortable using C structs to define your properties. If you are very used to the way NSObjects and Class in Obj-C act for you when you retain and release, you have to be aware of when you assign a struct you are making copies and may be dealing less in pointers. Lets talk about GKTank tankInfo:


typedef struct {
CGPoint tankPreviousPosition;
CGPoint tankPosition;
float tankRotation;
float tankDirection;
} tankInfo;


This struct has four properties, but two are also structs. CGPoint is a C struct that just contains two sub float values. And understanding how this all really boils down in memory helps understand how much data your saving or sending. A float is 4 bytes, this example of struct ultimately consists of 6 floats. 4*6 = 24 bytes to send this struct. Just to compare that point here, the resources out there tell you normally would want to run your NSDictionary through a NSKeyedArchiver which can write to a NSData. An example of this:


    NSDictionary* tank = [NSDictionary dictionaryWithObjectsAndKeys:
                         [NSNumber numberWithFloat:32.f], @"oldx",
                         [NSNumber numberWithFloat:32.f], @"oldy",
                         [NSNumber numberWithFloat:32.f], @"newx",
                         [NSNumber numberWithFloat:32.f], @"newy",
                         [NSNumber numberWithFloat:32.f], @"rot",
                         [NSNumber numberWithFloat:32.f], @"dir", nil];
    NSMutableData *data = [[NSMutableData alloc]init];
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data];
    [archiver encodeObject:tank forKey:@"theTank"];
    [archiver finishEncoding];


Same six float values, for a total of 436 bytes to hold the values that map the key values. So how do we get the struct into a NSData object so the rest of the Objective-C framework can handle it. NSData has a handy way of making a copy of the binary data of the structure provided we can tell NSData how many bytes the object is.


tankInfo mytank = tankInfo();
    mytank.tankPreviousPosition = CGPointMake(32, 32);
    mytank.tankPosition = CGPointMake(32, 32);
    mytank.tankRotation = 32;
    mytank.tankDirection = 32;
    NSData *data = [NSData dataWithBytes:&mytank length:sizeof(tankInfo)];
[myGKSession sendData:data toPeers:peers];



This method Data with Bytes lets us send in the pointer argument of our struct which is giving it the first byte to start from. The length lets the NSData know how far in memory to copy, so we need to know how many bytes our struct is. We did the math earlier and could enter 24 for the length. But C offers a function sizeof() which looks up the definition table of the struct type and returns it for us. Whats great about a struct is when allocated it's properties are lined up in memory so that each of the 6 floats are stacked back to back. How it is aligned is not saved in the memory block with the 6 values but the structs definition itself acts as a parser/de-parser for the memory. Even if the floats are back to back, the definition is the road map to know that the first and second floats are part of the first property, the third and forth floats are the second property, etc. When you get this NSData on the other side it needs to be converted back into a structure so it's easy to access again.


- (void)receiveData:(NSData *)data fromPeer:(NSString *)peer ... {
    tankInfo recievedTank;
    if ([data length]) [data getBytes:&recievedTank length:sizeof(tankInfo)];
}

When we create the instance of tankInfo the memory for 24 bytes has been made available but like is random data or set to null. Like before this now copies the bytes into the pointer location which has already been allocated the line before and the data that just came in can now be accessed and changes by accessing the struct properties and all we sent over the wire was 24 bytes (give or take any gamecenter overhead).

With my mind well wrapped around this C struct concept I can wisely get a lot of game data send over the wire quickly, or stored on Apple servers when making a turn based game. Apple has set a turn based miscellaneous data limit to 4KB. Now for some these seem unreasonable, but those are often the ones I see trying to KeyArchive encode a big Dictionary and it makes sense that they are hitting the limit quick. I've headed into planning my game data storage and for the most part have not come close to needing to worry about how much binary data I need to get my games conveyed. up to 4 players, up to 70 turns, in under 880bytes.

The Con
I know I'm picking a threads here, but now I've come back to the issue I once though obsessive when working for the MMO. I'd love to hear input there from any others that deal in structure binary. I've designed my game data struct to handle these 4 players and the 70 turns possible to complete a game. To define this maximum possible amount of turns I an array of turn structs. This again cause the data to be allocated, and empty early on. An average game will be complete in 60 turns, and as the turns progress the bytes in the turns array are being written to something other Null, but in order to know the size of a matchData struct the turns need to be defined with 70 elements. Only fortunately is this set of struct small and the whole combo comes to 880bytes. But thats 880 on turn 1, 2 .. 54 etc. If later I take on any larger game storages I may come back to talk about how to sub parse the first few bytes in the NSData to help predefine the amount of elements that should be expected, making the struct more variable.


struct matchData {
    unsigned int seed;
    unsigned char playerCount;
    unsigned char turnIndex;
    playerData players[4];
    turnData turns[70];
    
    matchData() : turnIndex(0), playerCount(2) {}
};