Friday, April 4, 2014

Locate deleted files in git

Problem

You want to undelete the file you removed from git long ago but don't remember the path or the actual file name.

Solution

Use git log to search for deleted file.  Then use another git log command to find all checkins related to this file.

Example

git log --diff-filter=D --summary | grep delete >/tmp/out.txt

Search for the full paths of the files you want to recover from /tmp/out.txt

git log --all -- path/to/file

It will show you all commits that touched the file.

Friday, February 21, 2014

Preventing UIWebView to detect & style phone numbers in a web page

Problem: iOS7's UIWebView incorrectly detects and turned numbers into links

Our app shows webpage contents in UIWebViews and after we switched to XCode5/iOS7SDK, we started seeing some numbers in webpages turning into clickable links that pointed to invalid telephone numbers.

After looking inside the HTML body, I found that iOS7 UIWebView injected an tag around the number inside the

Mobile Safari
<div class="coordinate" style="left: 0.5px; position: absolute; top: 130px;">
0</div>
<div class="coordinate" style="left: 25px; position: absolute; top: 130px;">
5</div>
...



UIWebView
<div class="coordinate" style="left: 25.5px; position: absolute; top: 130px;">
<a href="tel:0510152025" x-apple-data-detectors-result="14" x-apple-data-detectors-type="telephone" x-apple-data-detectors="true">5</a></div>
...


It looks like iOS7 UIWebView tries to be smart about data format detection and saw that there were a series of numbers that it thought could be a telephone number.  However, in this case it's not.

Solution: Disable the data format detection

There are 2 ways to fix this.


  1. If you own the web page and can modify the HTML, add this meta tag to disable telephone number detection.
    <meta name = "format-detection" content = "telephone=no">
  2. If you don't have access to the web page, you can programmatically disable the detection from UIWebView instance.

    self.webView.dataDetectorTypes = UIDataDetectorTypeNone;


The UIDataDetectorTypeNone will not detect any telephone number, link, address, calendar event.  If you want UIWebView to try detect some of these, you may have to use bit-or UIDataDetectorTypes enum values defined in UIKit's UIDataDetectors.h.

Thursday, February 13, 2014

[iOS] Fix for GoogleAnalytics 3.0.3a referencing ASIdentifierManager's advertisingIdentifer

Updated: Google updated the GA library to 3.0.3c and it no longer refers to ASIdentifierManager in AdSupport.framework.  This allows apps to continue using -ObjC without -force_load.

Since early Feb 2014, Apple started rejecting app submissions that linked to AdSupport.framework but did not display advertising (see Program License Agreement at the end of this blog) to prevent potential abuse of this unique identifier.  See news from TechCrunch.

The problem with this is that many of analytics tools use this identifier to track app activities so any apps that included Google Analytics (up to 3.0.3), TestFlight (up to 2.1.x), Flurry and others will be rejected even though the apps themselves do not use this framework.

Most of the analytics vendors quickly updated their libraries to remove references to the ASIdentifierManager's advertisingIdentifier.  So upgrade to the latest version would solve the problem.
However, as of now, GoogleAnalytics' latest version (3.0.3a) requires a linker flag that would break many apps.

Important: If you are currently using the Google Analytics Services SDK but are not using Google Tag Manager, we recommend using the -force_load library_name linker flag instead of the -ObjC flag. The -ObjC flag causes all libraries not to be dead-stripped; by using -force_load library_name to selectively turn off dead-stripping only for particular object files, Google Tag Manager and other unnecessary libraries will be dead-stripped, which may significantly reduce the size of your binary.
If you don't use GTM in GA, this instruction will allow you to remove AdSupport.framework from the Link Binary With Libraries section.  Removing -ObjC and replacing with the -force_load on GA static library will no longer require AdSupport.framework.  This is problematic for apps that use other static libraries and it's a default flag if you use CocoaPods.  Removing the -ObjC will prevent the linker to load all symbols from static libraries.  If other libraries have category extensions, they will not be linked into the binary and the app will crash from runtime exceptions.

Short term fix

Until Google provides us with a better fix that works with -ObjC, your only option is to add -force_load to all static libraries that have category extensions.  In my app, there are 3, RegexKitLite, CorePlot and GA.

If you use CocoaPods, make sure that your Podfile contains the latest GA version.

pod 'GoogleAnalytics-iOS-SDK', '3.0.3a'
Then run 'pod install'.

If you do not use CocoaPods, it may be a good time to use one. :)  Or you can manually download the latest Google Analytics iOS SDK from the website.

After this, launch Xcode and go to your project target in TARGETS section.  You will do two things.

1. Ensure that your rejected app has references to advertisingIdentifier by unpacking the ipa into a Payload folder.
  1.1 Use a grep: "grep -r advertisingIdentifier ." Notice the dot at the end of the command.  It will show a match in the 
  1.2 Use nm tool: "nm your_app.app/your_app >output.txt" Replace your_app with your actual ipa file name.  Then search for advertisingIdentifier in output.txt.

2. Remove AdSupport.framework from Link Binary With Libraries in the Build Phases tab.

3. Go to Build Settings tab and search for Other Linker Flags.  Double click the target value and remove -ObjC and replace it with:

-Wl,-force_load,$(BUILT_PRODUCTS_DIR)/libPods-GoogleAnalytics-iOS-SDK.a

Note: If you do not use CocoaPods, you will  have to find the GA library .a file in the BUILT_PRODUCTS_DIR which is normally in your ~/Library/Developer/Xcode/DerivedData/your_project_name/Build/Products.

4. The link step should now pass but the app may crash if other 3rd party static libraries use category extensions.  Test thoroughly and if the app crashes, note the 3rd party class/method that causes the crash, find which library contains the class/method.  Then add -force_load for those libraries.  In my app, they are CorePlot and RegexKitLite so I added two more linker flag.

-Wl,-force_load,$(BUILT_PRODUCTS_DIR)/libPods-RegexKitLite.a
-Wl,-force_load,$(BUILT_PRODUCTS_DIR)/libPods-CorePlot.a

5. Rebuild, re-test.  Once you are sure that the app runs fine.  You can build the final ipa file and search for any references to advertisingIdentifer by unpacking the ipa file and repeat step 0.

grep -r advertisingIdentifier .

If you don't see any match, your binary is clean and ready for a resubmission.  Good luck!



PLA 3.3.12

We found your app uses the iOS Advertising Identifier but does not include ad functionality. This does not comply with the terms of the iOS Developer Program License Agreement, as required by the App Store Review Guidelines.

Specifically, section 3.3.12 of the iOS Developer Program License Agreement states:

"You and Your Applications (and any third party with whom you have contracted to serve advertising) may use the Advertising Identifier, and any information obtained through the use of the Advertising Identifier, only for the purpose of serving advertising. If a user resets the Advertising Identifier, then You agree not to combine, correlate, link or otherwise associate, either directly or indirectly, the prior Advertising Identifier and any derived information with the reset Advertising Identifier."

Please check your code - including any third-party libraries - to remove any instances of:

class: ASIdentifierManager
selector: advertisingIdentifier
framework: AdSupport.framework

If you are planning to incorporate ads in a future version, please remove the Advertising Identifier from your app until you have included ad functionality.


[iOS] How to determine whether a static library (.a) has 64-bit slice

If you build a 64-bit version of your iOS app, be sure that any 3rd party static libraries the app link must have 64-bit slice.  Otherwise your app will still require iOS 32-bit runtime support.

How to determine whether a static library (.a) has 64-bit slice?  It's actually easy.  Just use lipo -info command but the trick is to run it under the xcrun as follows:

$ xcrun -sdk iphoneos lipo -info Pods/GoogleAnalytics-iOS-SDK/libGoogleAnalyticsServices.a

Architectures in the fat file: Pods/GoogleAnalytics-iOS-SDK/libGoogleAnalyticsServices.a are: armv7 armv7s i386 x86_64 arm64

If the result contains arm64, the static library was built with a 64-bit version (A7 chip in iPhone 5S+ and iPad Air+).  In addition, i386 and x86_64 are slices for simulator while arm7 and arm7s are for up to iPhone 4S's chips and iPhone5's A6 chip.

Monday, January 13, 2014

Migrate code to support 64-bit in iOS7

As Apple requires all app submissions to use XCode5 with iOS7 SDK after February 1st, 2014, it becomes important to make sure that your codebase is compatible with iOS7 SDK, especially the 64-bit support since the primitive types change from 32 to 64 bits.

This means that NSInteger/NSUInteger are long instead of int.  CGFloat is double, not float.

There are many possible compiler warnings and here are the resolutions.

Case 1: Implicit conversion loses integer precision: 'long' to 'int'

Example:

    NSInteger row = ...;

    int rowWithLineCount = (rows - 1) / 5// compiler warning

Solution:
    NSInteger rowWithLineCount = (rows - 1) / 5 ;

Note: Use NSUInteger if the right hand expression is evaluated to unsigned integer.

Case 2: Implicit conversion loses integer precision: 'NSInteger' (aka 'long') to SomeEnum'

Example:

  typedef enum {
    ...
  } SomeEnum;

  NSInteger someValue;
  SomeEnum somefield = someValue; // compiler warning

Solution: Use NS_ENUM

  typedef NS_ENUM(NSInteger, SomeEnum) {
    ...
  };

Case 3: Implicit conversion loses integer precision: 'SomeEnum' (aka 'enum SomeEnum') to int'

Example:

  typedef NS_OPTIONS(NSUInteger, SomeEnum) {
    ...
  }
  SomeEnum somefield;

  NSNumber *number = [NSNumber numberWithInt:self.someField]; // compiler warning

Solution:

  NSNumber *number = [NSNumber numberWithLong:self.someField];

Case 4: Values of type 'NSInteger' should not be used as format arguments; add an explicit cast to 'long' instead

Example:

NSInteger statusCode = ...;

NSLog(@"Failed response code:%d", statusCode); // compiler warning

Solution:

NSLog(@"Failed response code:%ld", (long)statusCode);

Note: This applies to %lu string pattern and (unsigned long) casting for NSUInteger as well.

Case 5: Replace abs((NSInteger)value) with labs()