Archive for the ‘Useful Techy Bits’ 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.

Advertisements

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

Decompiling Android APKs

January 28, 2013

So last week needed to take a look at an existing app in the google play store to see if it was generated using one of the Mobile Enterprise Application Platforms (Appcellerator, / PhoneGap, etc.)  tools.   (Example below is actually one of my own apps…  just for demonstration purposes.)    Figured I’d add it to the blog so all the steps are in one place.   The other mentions I found on how to do it were scattered about.

  1.  Get the APK you want to decompile off of your android device:   Connecting the device to your computer and launching the DDMS in Eclipse won’t actually show you the contents of /data where installed apks and apps are installed unless you root your device.    That seemed like a pain… so instead, I found this:
    1. Install app sender on the device:  AppSender 2.0 (Share APK)
    2. Ensure that  the device ISN’T connected to your computer.   (AppSender thinks it needs the SD card in order to run, and this is unmounted when plugged in on USB. (at least with Moto Droid Razr max 2.3.6 and Mac OS X 10.7.5)
    3. Find the app you want to get off the phone in the list that AppSender presents and tap it.
    4. Tap “Email Attachment”     device-2013-01-28-110728
    5. Send it to yourself using the Mail Intent of your choice.
    6. Save the attached APK file off somwhere on your computer.
  2. Download and install dex2Jar  on your computer:   http://code.google.com/p/dex2jar/downloads/list
  3. Run dex2Jar on the APK:     (This will get you as far as class files on your computer.)
    $ ./dex2jar.sh ../com.tasteseller-1.apk
    this cmd is deprecated, use the d2j-dex2jar if possible
    dex2jar version: translator-0.0.9.12
    dex2jar ../com.tasteseller-1.apk -> ../com.tasteseller-1_dex2jar.jar
    Done.
  4. Download JD-GUI  and Install on your computer. (Java Decompiler) http://java.decompiler.free.fr/?q=jdgui
  5. Run JD-GUI…     Open the jar file:  File->Open File… (select the jar file from above:)
  6. Browse the Code:   (obviosuly if the original APK was obfuscated the results here last step may be totally illegible…   but happily it did just fine on the app I needed to check out.)
    Screen Shot 2013-01-28 at 11.34.20 AM
  7. There is no Step 7.   That was everything!

The follow up article:     How to obfuscate Android Code so that this isn’t doable.

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.

Excel Cell Double Click Shenanigans

April 1, 2010

For some inane reason last fall, I bought the latest version of Microsoft Office for OS X.   (2008 is it? )    Anyways, I was disappointed by iWork’s flakiness in Numbers (Apple’s spreadsheet) and Pages (import from Word is lossy), so I thought I’d pony up and buy the office “standard” used world round.

Now it’s like I’m back in 1990 running the spreadsheet on the Mac SE.    Seriously.    It feels like all UI development stopped on Excel decades ago.    The interluding years have been devoted to developing lots of features I don’t need, and window dressing it up to make it look like maybe it was updated for Aqua circa 2001.

I don’t think any convincing thought has gone into this program since the development of the laptop trackpad. Case and point: the most annoying thing in Excel seems to be that double clicking on a cell to edit it half the time scrolls the spreadsheet unpredictably.     After a few weeks of annoyance, and many unsuccessful searches on the web, I finally intuited the Google search terms to find others griping about the issue, and then a comment on the post with the fix.
That post is here:  Betalogue

Thanks the images and the fix in the comments…    (can’t get the cursor in the mac screen shots anymore.)

Contrary to my whining so far this post is more about the fix than the gripe.

HONEST.      So the thing I didn’t notice initially was that that is that the cursor changes from the Excel “+” cursor to the Excel “Hand Cursor” immediately before the offending scroll to the bottom of my thousand row spreadsheet.

Archaic Excel Plus Cursor

Archaic Hand Cursor
Aside from the fact that the Cursors are the exact bitmaps from 1990, the main problem here is that you get about 5 pixels in the middle which can be used to double click the cell.     Double clicking on the Macbook trackpad invariably skips to the bottom of the column, because the act of clicking moves the pointer enough to cause the cursor to change.
So to fix:
Excel->Preferences
Search on “Drag”  this will highlight the “Edit” tab
Uncheck “Enable fill handle and cell drag and drop”  (OBVIOUS! NOT!)

The Offending Setting

Now it’s nearly usable again.

Now If I could just make =a+b realize I’m trying to do math without having to set the column format to number, then edit and click return again.

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?