Archive for the ‘iPhone’ Category

Identifying Hybrid iOS Apps from binaries

April 23, 2015

Recently I had the opportunity to engage in a bit of a research project to examine commonly used app technologies within a specific industry vertical.   It was useful for this exercise to try to coorelate app store ratings to the underlying technologies which were used to the develop the app, specifically with an eye towards hybrid apps, and how they compare to fully native, and Ahead of Time compiled apps. (Xamarin)

It turns out that many example customers with hybrid providers such as Kony or PhoneGap supply are no longer on the platform, at least for the industry vertical I examined.

In any case I wanted to document the process, as I’m sure it could be automated, and it’s always fun to poke around in large corporations apps to see what can be found.   (Occasionally some pretty *interesting* decisions.)

1. From iTunes on your Mac, Download the app in question.

Screen Shot 2015-04-22 at 10.01.16 PMScreen Shot 2015-04-22 at 9.56.15 PM

2. Open your home directory in the finder, open the Music folder, then Mobile Applications and find the new ipa file. (this is for iTunes 12.1…    other versions may have slightly different folder structures.

Screen Shot 2015-04-22 at 9.58.55 PM

3. Rename the .ipa extension to .zip.

Screen Shot 2015-04-22 at 9.59.14 PM

4. Expand the zip file, then Navigated into the Payload directory, right click on the file inside and then Choose “Show Package Contents”  – Sometimes you can learn something about the app just from the File name in the Payload…   for examples some apps showed Mobiliti as the title which turned out to indicate that this was the Saas which had generated the app.

Screen Shot 2015-04-22 at 9.59.58 PM

5. The tell tale sign (in case you couldn’t tell from your app experience is a html or www folder inside the package: )

Screen Shot 2015-04-22 at 10.00.36 PM

In this case we can see that phone gap was used, as well as google analytics, and a bunch of other javascript frameworks to help drive the HTML in the hybrid app.

Another Tell seems to be lack of .xib files in the package.    Lots of .xib files generally means full Native.     Still working on Xamarin detection.

iOS apps with initial Core Data database image

March 12, 2015

Frequently, it’s useful to ship an iOS app with a preconfigured CoreData database, so that on initial launch the data doesn’t need to be retrieved from a webservice before presenting it to the user / or immediately loaded from a plist shipping with the file.

This used to be easy pre iOS 7.0:   Launch in the simulator and ensure you’ve populated the datastore with the data you want to chip.   Then find the sqlite3 datafile on your mac.   That’s easily done with a breakpoint in the persistentStoreCorrdinator:

FindingTheDbImage

Then copy it into your project

BINGOSABI:bingosabi$ cd /Users/bingosabi/Library/Developer/CoreSimulator/Devices/
7FD9730C-F002-414D-9F72-8D6BAA750A3B/data/Containers/Data/Application/0A3AB0EE-
D557-4FE7-9683-3C337C64A7E9/Documents/db.sqlite
BINGOSABI:Documents$ cp db.sqlite ~/Projects/ExampleApp/Resources

 

In iOS 7.0 however, but Apple updated the shipping version of sqlite3 which enabled a separate journaling file.   Now a database has three files.

BINGOSABI:Documents bingosabi$ ls
db.sqlite	db.sqlite-shm	db.sqlite-wal

So in order to ship a populated databases we have to do a few extra steps.

sqlite> pragma journal_mode=off;
off
sqlite> vacuum
sqlite> .exit

Copy ONLYthe vacuumed db.sqlite to the Xcode project and include it.

BINGOSABI:bingosabi$ cd /Users/bingosabi/Library/Developer/CoreSimulator/Devices/
7FD9730C-F002-414D-9F72-8D6BAA750A3B/data/Containers/Data/Application/0A3AB0EE-
D557-4FE7-9683-3C337C64A7E9/Documents/db.sqlite
BINGOSABI:Documents$ cp db.sqlite ~/Projects/ExampleApp/Resources

If you used the default template for a Core Data app, then replace the default persistentStoreCorrdinator with the following code: (Note that you have to always touch that anyways if you’re submitting to the store due to the abort(); poison pill which Apple has in the template. Not removing the abort(); is a rejection-able offense.

So replace this:

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    // The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it.
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }
    
    // Create the coordinator and store
    
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"db.sqlite"];
    NSError *error = nil;
    NSString *failureReason = @"There was an error creating or loading the application's saved data.";
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        // Report any error we got.
        NSMutableDictionary *dict = [NSMutableDictionary dictionary];
        dict[NSLocalizedDescriptionKey] = @"Failed to initialize the application's saved data";
        dict[NSLocalizedFailureReasonErrorKey] = failureReason;
        dict[NSUnderlyingErrorKey] = error;
        error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
        // Replace this with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();   //POISON PILL
    }
    
    return _persistentStoreCoordinator;
}

with this:

- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
    //This is required for cached data lest Apple reject for immediately writing data to Documents which isn't user data and has default iCloud backup attribute.
    NSError *error = nil;
    BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
                                  forKey: NSURLIsExcludedFromBackupKey error: &error];
    if(!success){
        NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
    }
    return success;
}

- (void) copyDatabaseFromBundle:(NSURL *)storeURL {
    NSError *error = nil;
    NSString *databaseName = [[storeURL lastPathComponent] stringByDeletingPathExtension];
    NSString *databaseExtension = [storeURL pathExtension];
    NSURL *shippingDatabaseImage = [[NSBundle mainBundle] URLForResource:databaseName withExtension:databaseExtension];
    
    NSURL * toURL = [[[self applicationDocumentsDirectory] URLByAppendingPathComponent:databaseName] URLByAppendingPathExtension:databaseExtension];
    
    [[NSFileManager defaultManager] copyItemAtURL:shippingDatabaseImage toURL:toURL  error:&error];
    
    [self addSkipBackupAttributeToItemAtURL:toURL];
}

- (void) deleteDatabase:(NSURL *)storeURL {
    //Delete all the write ahead log files. 
    [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];
    [[NSFileManager defaultManager] removeItemAtURL:[NSURL URLWithString:[storeURL.absoluteString stringByAppendingString:@"-wal"]] error:nil];
    [[NSFileManager defaultManager] removeItemAtURL:[NSURL URLWithString:[storeURL.absoluteString stringByAppendingString:@"-shm"]] error:nil];
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (!_persistentStoreCoordinator) {
        NSManagedObjectModel *model = [self managedObjectModel];
        
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            // Create the coordinator and store
            _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
            NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"db.sqlite"];
            NSError *error = nil;
            NSString *failureReason = @"There was an error creating or loading the application's saved data.";
            
            if ([storeURL checkResourceIsReachableAndReturnError:&error] == NO) {
                [self copyDatabaseFromBundle:storeURL];
            }
            
            //Handle lightweight version migration
            NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                                     [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                                     [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
            
            if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
                
                //Just Start Over.
                [self deleteDatabase:storeURL];
                
                if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
                    
                    // Report any error we got.
                    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
                    dict[NSLocalizedDescriptionKey] = @"Failed to initialize the application's saved data";
                    dict[NSLocalizedFailureReasonErrorKey] = failureReason;
                    dict[NSUnderlyingErrorKey] = error;
                    error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
                    
                    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
                }
            }
        });
        
    }
    
    return _persistentStoreCoordinator;
}

C’est tout!

iOS 8 Suggested Apps

October 15, 2014

New in iOS 8 are lock-screen suggested apps on iOS 8.    There has been much speculation on how there work, but no definitive confirmation from Apple, even in their forums.   I now have a request from a client to leverage these, which is difficult given the typical opacity from Apple.      It seems here is what is known:

1) Based on location (DUH)

2) Installed apps use a different suggestion mechanism than apps which aren’t installed.

3) Installed apps can pay attention to app established geofences, etc.  And if you’re within one of their registered geofences, (or an iBeacon range) then an app will appear on the lockscreen in the lower left.

4) Apps that haven’t been installed rely on some undisclosed Apple Algorithm, likely related to their correlation of an App, and their Maps data for that’s businesses locations.  There doesn’t appear to be anyway to influence Apple on this currently through the app submission process… However I have a few levers to pull to ferret this out.  This is the use case which I think most App developers are interest in, as it’s a novel way to promote an app apart from the chaos that is the app store.

5) Yes they can be turned off in Settings->iTunes & App Store->My Apps / App Store…     annoying that most the posts are on how to disable the feature, rather than how to leverage them.

6) Feature is enabled by default.

Will test this out in the next few days and report back.

Xcode Shortcuts FTW!!!

December 30, 2013

Best shortcut ever in Xcode:   ctrl-command Up Arrow   (Toggles to .h or .m file in editor)    Also Awesome… add Option into the mix to turn the secondary editor back into “Counterpart” mode.    So…    ctrl-option-command-uparrow

WWDC 2013 Wishlist

April 25, 2013

WWDC

Apple’s platform has gotten…   well..   a bit stale.    Here’s my list of things that Apple could announce at WWDC which would reinvigorate the iOS platform for me personally.

Software

  • Homescreen widgets SDK
  • Make Siri actually understand me more than 20% of the time (being generous)
  • Siri Integration for apps (Assuming above gets fixed)
  • GameCenter Chat support.
  • OCR SDK that doesn’t suck for input. – Tesseract is fickle.  The tech is out there… make it mainstream for input.
  • Fix iCloud & Open it up to other platforms   Or at least a Web Platform.
  • UI Theming / Skin Support.
  • Open up Spolight API so app content can be searchable in Search homescreen.
  • Customizable Spotlight Search Options.   Why can I still not search the appstore from Spotlight?
  • iCloud Repsonsive Web Support
  • iCloud Web app platform

Hardware

Ok.   WWDC isn’t really ever about Hardware Anymore, but when new bizarre things get added to hardware it stimulates the app market for new and interesting mash ups and uses that people hadn’t thought of before.

  • More Phone Sensors (Track Eye Movement, Near Field Communications, Temperature)
  • Failing more sensors… Peripherals with more sensors (iWatch, Glasses)
  • Peripheral Platform and SDK in support of 3rd party wirelessly linked gadgets.
  • Loosen up on the MFI (Made for iPhone) program.     – Currently this just isn’t open to anyone without retained legal council.  Period.
  • Camera Improvements.  UV InfraRed Camera sensitivity  Optical Zoom (fluid lenses?)

Core Data Model Mapping

July 2, 2012

Here is the basic process for migrating from one Data Model to a bother in Core Data between versions.   When you need to drastically change the ERD.    The original question this is in response to is here:

http://stackoverflow.com/questions/11174773/migrating-a-many-to-many-relationship-to-a-join-table-in-core-data

1. Create a versioned copy of the Data Model. (Select the Model, then Editor->Add Model Version)

2. Make your changes to the new copy of the data model

3. Mark the copy of the new data model as the current version. (Click the top level xcdatamodel item, then in the file inspector set the “Current” entry under “Versioned Data Model” section to the new data model you created in step 1.

4. Update your model objects to add the RecipeIngredient entity. Also replace the ingredients and recipes relationships on Recipe and Ingredient entities with new relationships you created in step 2 to the RecipeIngredient Entity. (Both entities get this relation added. I called mine recipeIngredients) Obviously wherever you create the relation from ingredient to recipe in the old code, you’ll now need to create a RecipeIngredient object.. but that’s beyond the scope of this answer.

5. Add a new Mapping between the models (File->New File…->(Core Data section)->Mapping Model. This will auto-generate several mappings for you. RecipeToRecipe, IngredientToIngredient and RecipeIngredient.

6. Delete the RecipeIngredient Mapping. Also delete the recipeIngredient relation mappings it gives you for RecipeToRecipe and IngredientToRecipe (or whatever you called them in step 2).

7. Drag the RecipeToRecipe Mapping to be last in the list of Mapping Rules. (This is **important** so that we’re sure the Ingredients are migrated before the Recipes so that we can link them up when we’re migrating recipes.) The migration will go in order of the migration rule list.

8. Set a Custom Policy for the RecipeToRecipe mapping “DDCDRecipeMigrationPolicy” (This will override the automatic migration of the Recipes objects and give us a hook where we can perform the mapping logic.

9. Create DDCDRecipeMigrationPolicy by subclassing NSEntityMigrationPolicy for Recipes to override createDestinationInstancesForSourceInstance (See Code Below). This will be called once for Each Recipe, which will let us create the Recipe object, and also the related RecipeIngredient objects which will link it to Ingredient. We’ll just let Ingredient be auto migrated by the mapping rule that Xcode auto create for us in step 5.

10. Wherever you create your persistent object store (probably AppDelegate), ensure you set the user dictionary to auto-migrate the data model:

if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType

configuration:nil
URL:storeURL
options:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, nil]
error:&error])
{
}

**Subclass NSEntityMigrationPolicy for Recipes**

#import <CoreData/CoreData.h>

@interface DDCDRecipeMigrationPolicy : NSEntityMigrationPolicy
@end

**Override createDestinationInstancesForSourceInstance in DDCDRecipeMigrationPolicy.m **

<!– language: lang-c –>

– (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance entityMapping:(NSEntityMapping *)mapping manager:(NSMigrationManager *)manager error:(NSError **)error
{

NSLog(@”createDestinationInstancesForSourceInstance : %@”, sInstance.entity.name);

//We have to create the recipe since we overrode this method.
//It’s called once for each Recipe.
NSManagedObject *newRecipe = [NSEntityDescription insertNewObjectForEntityForName:@”Recipe” inManagedObjectContext:[manager destinationContext]];
[newRecipe setValue:[sInstance valueForKey:@”name”] forKey:@”name”];
[newRecipe setValue:[sInstance valueForKey:@”overview”] forKey:@”overview”];
[newRecipe setValue:[sInstance valueForKey:@”instructions”] forKey:@”instructions”];

for (NSManagedObject *oldIngredient in (NSSet *) [sInstance valueForKey:@”ingredients”])
{
NSFetchRequest *fetchByIngredientName = [NSFetchRequest fetchRequestWithEntityName:@”Ingredient”];
fetchByIngredientName.predicate = [NSPredicate predicateWithFormat:@”name = %@”,[oldIngredient valueForKey:@”name”]];

//Find the Ingredient in the new Datamodel. NOTE!!! This only works if this is the second entity migrated.
NSArray *newIngredientArray = [[manager destinationContext] executeFetchRequest:fetchByIngredientName error:error];

if (newIngredientArray.count == 1)
{
//Create an intersection record.
NSManagedObject *newIngredient = [newIngredientArray objectAtIndex:0];
NSManagedObject *newRecipeIngredient = [NSEntityDescription insertNewObjectForEntityForName:@”RecipeIngredient” inManagedObjectContext:[manager destinationContext]];
[newRecipeIngredient setValue:newIngredient forKey:@”ingredient”];
[newRecipeIngredient setValue:newRecipe forKey:@”recipe”];
NSLog(@”Adding migrated Ingredient : %@ to New Recipe %@”, [newIngredient valueForKey:@”name”], [newRecipe valueForKey:@”name”]);
}

}

return YES;

}

 

This should look like this in Xcode when you’re done:

Also note that the Xcode Core Data model mapping stuff is a bit flaky and occasionally needs a “clean”, good Xcode rester, simulator bounce or all of the above get it working.

Apple’s iPhone Unit Tests Less that Stellar

February 9, 2011

It’s Like jUnit, but even less intuitive.    It’s as if a first year intern was in charge of integrating OCTest into XCode.    It sucks because:

1) It showed up late.   WAY LATE.   What percentage of app store apps actually have unit tests, since most were out before Unit Testing was available?

2) It uses a Shell script during the build process to call it’s executable code.  That’s Hacky.

3) No stepping through the test Code because of 2.

4) Build Output requires 2 keystrokes and 2 clicks to get to because of 2.

5) Crashes in Test don’t give you a stack trace because of 2.

6) Build = Run.   Huh?    Yes clicking build does not just compile, but also RUNS the code in the test.   Very intuitive. NOT.

7) Build Errors do not stop said shell script from being run. Perhaps had they not hijacked the Build phase, this wouldn’t be an issue but they did.

8) Default instructions Say nothing about linking requirements, to an existing project.   They just have useless asserts on Foundation methods. (Conveniently already Linked.)   Methinks they’re using the Google Framework internally, but can’t redistribute that for obvious reasons.  (Like admitting that testing was an after thought.)

9)  Perhaps it’s time to checkout Google’s Test Toolkit.     Could it suck more?   Probably not.

10) Test rig ‘${TEST_RIG}’ exited abnormally with code ${TEST_RIG_RESULT} (it may have crashed).   how about telling me WHERE it crashed?

How can Apple be so considerate of the user and so inconsiderate of the developer?    They should license VSTFS.  Developer Environments is about the only thing that Microsoft gets right.

Where are you iPhone Simulator? OH, There You Are!

June 22, 2009

Problem: The iPhone SDK simulator has a habit of losing the simulator window.   This is very annoying when you work with multiple monitors, as the simulator screen can get “lost” or go missing when you unplug the external monitor if the window is positioned in the external display at coordinates that would be completely hidden in the smaller display.    If you relaunch the simulator, it will reposition it on the screen, however, it occasionally leaves it mostly off screen…  It’s actually there… it’s just hiding mostly off screen save 4-5 pixels.

Solution:

  1. Quit and Relaunch the simulator.
  2. Hit the Exposé key on the mac to find the missing iPhone Simulator Window.    This will at least let you find the window, and watch where it animates back out two when you leave Exposé so you can find the 4 pixel swath on the screen and drag it screaming back onto the window.

Filed Bug with Apple…    Which they’ll ignore like they normally do.

    Simulator Window in Exposé

    Simulator Window in Exposé

    See the Simulator Sliver?

    See the Simulator Sliver?