Saturday, October 29, 2016

ios swift try, try?, try!, defer and guard

do-try-catch block
Unlike other language, the basic usage of try-catch in swift is do-try-catch block as below, the do block will only run once, so do not mix it with do-while block.

  1. var vendingMachine = VendingMachine()
  2. vendingMachine.coinsDeposited = 8
  3. do {
  4. try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
  5. } catch VendingMachineError.invalidSelection {
  6. print("Invalid Selection.")
  7. } catch VendingMachineError.outOfStock {
  8. print("Out of Stock.")
  9. } catch VendingMachineError.insufficientFunds(let coinsNeeded) {
  10. print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
  11. }
  
 try?
     As there are many cases, the operation's result is setting some value to a variable as below:

  1. let y: Int?
  2. do {
  3. y = try someThrowingFunction()
  4. } catch {
  5. y = nil
  6. }

  1. func someThrowingFunction() throws -> Int {
  2. // ...
  3. }
 
So swift introduce a simple syntax for it as try?, the above code is same as below
  1. let x = try? someThrowingFunction()


try!
When calling a throwable method, sometimes we know it will definitely not throw any exception, if so, it can be represented by try! as shown below. If, by any chance, the method does throw exception, then an runtime error will happen and terminate the app.

let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")


defer
In other language, final clause is used to execute the clean up code when leaving try-catch block. In swift, the similar function is handled by defer statement. Although defer can be used without do-try-catch block. As long as the control leaves the block where defer is defined, then the defer clause will be executed.

  1. func processFile(filename: String) throws {
  2. if exists(filename) {
  3. let file = open(filename)
  4. defer {
  5. close(file)
  6. }
  7. while let line = try file.readline() {
  8. // Work with the file.
  9. }
  10. // close(file) is called here, at the end of the scope.
  11. }
  12. }

guard

Usually at the beginning of a function, pass-in parameters are validated, and if the validation failed, the function just returns.
Use "guard" instead of "if" in case when there is a good chance the condition return true. Unlike if, guard can only have an else clause to handle negative result. Usually the else clause will return from current method execution.
Different from if let statement, the guard let variable is available after the guard clause.

guard let myValue = myOptionalValue else {
return
}
print(myValue)

catch
When catching a particular type, enum or String in swift catch block, both is or as pattern match can be used

enum errorCode : Error {
    case NoModelURL
    case NoModel
}

extension String : Error {
}

Using is pattern match sample
    do {
            let mainContext = try createMainContext()
            print("main context is \(mainContext)")
            
            let firstVC = getFirstViewController()
            firstVC.managedObjectContext = mainContext
    }
    catch errorCode.NoModel {
            print("get no model error")
        }
    catch is errorCode {
            print("Get no model url error")
        }
    catch is String {
            print("string error happened")
        }
    catch {
            print("get error \(error)")
        }

Using as pattern match sample
    do {
            let mainContext = try createMainContext()
            print("main context is \(mainContext)")
            
            let firstVC = getFirstViewController()
            firstVC.managedObjectContext = mainContext
        }
        catch errorCode.NoModel {
                print("get no model error")
        }
        catch let errCode as errorCode {
            print("Get no model url error \(errCode)")
        }
        catch let errStr as String {
            print("Get no model url error \(errStr)")
        }
        catch let e {
            print("get error \(e)")
        }
using as pattern match is better, as it can access the Error object inside the catch handler block.



Thursday, October 27, 2016

ios xcode Launch Image size and mapped name in app bundle

When creating ios project from xCode, the launchImage file name set in image asset will be automatically renamed by xcode before the image resource is added into the app bundle.

It is good to know when setting a launch image resource in xcode, what name will be mapped into the app bundle.

As ios 5, 6 is hardly used anymore, so they are not included in the below list:


iphone portrait ios 8,9
Retina HD 5.5":
Imagesize: 1242x2208
Mapped bundle name: LaunchImage-800-Portrait-736h@3x.png

Retina HD 4.7" :
Imagesize:750x1334
Mapped bundle name: LaunchImage-800-667h@2x.png

iphone landscape ios 8,9
Retina HD 5.5"  :
imagesize: 2208x1242
Mapped bundle name: LaunchImage-800-Landscape-736h@3x.png

iphone portait iOS 7-9
2x :       
image size: 640x960
Mapped bundle name:  LaunchImage-700@2x.png

Retain 4 : 
imagesize: 640x1136
Mapped bundle name: LaunchImage-700-568h@2x.png

ipad portrait ios 7-9

1x : 
image size: 768x1024
Mapped bundle name: LaunchImage-700-Portrait~ipad.png

2x : 
image size: 1536x2048
Mapped bundle name: LaunchImage-700-Portrait@2x~ipad.png

ipad Landscrape ios 7-9
1x : 
image size: 1024x768
mapped bundle name: LaunchImage-700-Landscape~ipad.png

2x : 
imagesize: 2048x1536
mapped bundle name: LaunchImage-700-Landscape@2x~ipad.png


Tuesday, September 27, 2016

iOS 10 and xcode 8 tip

1.  xocode shows "development cannot be enabled while your device is locked, Please unlock your device and reattach. (0xE80000E2).

If your ios device is already unlocked and connected to mac and still get the error from xcode 8 after upgrading to ios 10, then the mac is not trusted by the device.
To fix it, first disconnect device to mac and then go to ios settings app, and open general->reset->Reset Location & Privacy.
Then connect device to mac and when prompted, set select trust the mac


2. Desymbolize the crash log using external symbol file
It has been much better to use xcode 8 to desymbolize the crash log. Just download the symbol file at any place, for example, the download folder and it seems xcode will do a full disk search to find the right symbol file.
Then from the xocde view device log screen, select the crash log and right click and select "re-symbolicate log", then it will update the symbolized file in the right panel.


3. in swift 2, raw NSData bytes can be converted to UnsafeMutablePoint<> directly as below sample

let derivedKey = NSMutableData(length: kCCKeySizeAES128);
UnsafeMutablePointer<UInt8>(derivedKey!.mutableBytes

However, in swift 3, that will cause an build error of "Cannot invoke initializer for type 'UnsafeMutablePointer<UInt8>' with an argument list of type '(UnsafeMutableRawPointer)'.
To fix it, just need to call assumeingMemoryBound method as below
let derivedKey = NSMutableData(length: kCCKeySizeAES128);
UnsafeMutablePointer<UInt8>(derivedKey!.mutableBytes.assumingMemoryBound(to:UInt8.self)),


4. avoid result unused warning for swift 3
in swift 2, calling a function without using its return value is normal. But it will generate a result unused warning in swift 3. To avoid the compile warning, just set the return value to _ as shown below

 _ = (UIApplication.shared.delegate as! AppDelegate).passwordManager.getPasswordItemList(currentCategory)

Monday, September 26, 2016

Steps to run xcode 8 side by side with xcode 7

The below steps can be used to install xcode 8 side by side with xcode 7

1. open Finder and go to Application folder, right clicking on Xcode, and select "duplicate", once it is done, rename "Xcode Copy" to "Xcode7"
2. Somehow the xcode 8 does not appear in the App Store update list. So you need manually to get it. Just open App Store app, and searching for "xcode",  then getting the Xcode 8 from there.
3. once the the XCode 8 installed, restarting the MAC.
4. By default, when starting xcode, xcode 8 will be selected. If you want to use xcode 7, just open Finder, and go to Application folder to start xCode7 from there.
5. Note, if you also use command line tools to build xcode project, you will need to switch the xcode path to the right version to do that by running the below command from terminal

xcode-select --print-path
//show the current selected xcode command line tool path


sudo xcode-select -switch /Applications/Xcode7.app 
//switch command line tool to xcode 7

sudo xcode-select -switch /Applications/Xcode.app 
//switch command line tool to xcode 8

Friday, September 23, 2016

Kerberos and NTLM authentication for ios device

Kerberos and NTLM are two authentication mechanisms used by Windows.

NTLM is Windows only, and Kerberos is RFC standard (RFC 1510), so it can be used by other platforms. NTLM is the default Windows authentication method before Windows 2000. As Kerberos has few enhancement over NTLM, so since Windows 2000, the windows client starts to use Kerberos as the preferred authentication protocol, although NTLM is still supported.

NTLM is supported by ios since iOS 2.0 (NSURLAuthenticationMethodNTLM)

Kerberos is supported by ios since iOS 7 with enterprise single sign on support.

For NTLM authentication, when client sends a request to server, if the server requires the NTLM authentication, the server will send 401 http status code to client. To handle it, the client will prompt user to input username and password, and then send back the credential information to server. Once the server gets the information it will talk to Windows Domain Controller to verify the user credential, and once verified a session cookie will return to client to indicate the authentication is succeeded. The key point is for NTLM, the web server will communicate with KDC using client provided credential to get a login token, so ios client does not need to know anything about the login secure server (KDC).

From the ios device side perspective, handling NTLM by application is no difference from handling regular 401 basic authentication. Once the challenge is received for NSURLAuthenticationMethodNTLM, then get the credential from user and send to server for authentication.

For Kerberos, the authentication is configured and handled on device level instead of application level, so once kerberos authentication is succeed, all applications installed on the device can share the authentication session with Single Sign On, so there is no need to authenticate each application separately. As for kerberos authentication, the device will directly communicate with KDC to get a login session, so the device must be configured for kerberos authentication to get the KDC information.

The main difference for Kerberos authentication is,  the device will first contact domain controller using the username and password to get a ticket from KDC (Key Distribution Center), and once the ticket is available, any application installed on the device and configured to use Kerberos can use the ticket to authenticate itself when accessing a web resource, the resource server will communicate with domain controller to verify the client side ticket. So unlike NTLM, ios application implementation are transparent to Kerberos authentication and do not need to have any logic to retrieve the user credential and send it to KDC, all it needed is to include the app id in the kerberos device profile's AppIdentifierMatches field.

Monday, September 19, 2016

Use ios cordova to load/block external content url with whitelist

For ios cordova project, since Cordova 6.0.0, (Cordova iOs 4.0.0), the function of cordova whiteplist plugin is merged into cordova core library in the source file of CDVWhiteList.m and CDVIntentAndNavigationFilter.m, so the there is no need to explicitly add the whitelist plugin into the ios cordova project.

One breaking function is when setting an external url, like https://www.google.com in config.xml's content element as below, it will not work,

    <content src="httsp://www.google.com" />

The reason is the top level url is only allow to file:// url, and it has be explicitly allowed in order to load the url. To solve the issue, add the below line to allow navigate to any url
    <allow-navigation href="*" />


The whitelist related tags in config.xml are listed below:
1.<allow-navigation> allow root webview to navigate to a particular url

2. <allow-intent> allow opening an url in inappbrowser without giving permission to load cordova plugin on the url

3. <access> allow which url the javascrpit xhr reqeust can be made to it.

In addition, the index.html created with default cordova project also have CSP (content security policy), which may limit what is able to run on the cordova page, The below is a good tutorial for the related information.
http://go.microsoft.com/fwlink/?LinkID=617697



Friday, September 16, 2016

ios auto layout settings with Hugging and Compression Resistance

Minimal auto layout settings:

For any ios view, the minimal auto layout setting is top (y) and leading space. Once these two values are decided, the auto layout can put the view in the right place.
The width and height will be decided at run time based on the view's intrinsic value. For example, if the view is UILabel, the width and height will be decided based on the label text's content along with the font selected for the label.


Optional width and height constraints:
Sometimes, it is better to fix the view's width or height regardless of the view's intrinsic content.
For example, you may want the label always use first 1/3 width of the line to align all labels on a screen. To do so, you add a proportional equal width constraint with multiplier of 0.3. Or define both leading and trailing space to decide the width based on superview's width.

Content Hugging and Compression Resistance priority 
When the width and height of the view is decided by intrinsic value, instead of fixed by constraints, then at runtime, the multiple constraints may conflict. For example, in the below


There are a leading space constraint between green label to superview, and horizontal space constrait between green label and purple label, and a trailing space constraint between purple label to superview. This works fine at design time, but at runtime, if the actual viewcontroller's width is larger or smaller than the design time viewcontroller's width, then the label view's width has to be changed to satisfy the leading and trailing space constraints. The issue is which label view should change its width?

The xcode interface build has two additional properties Hugging and Compression Resistance to control which view should shrink or expand to meet the runtime constraints requirement.

Both properties have a value of 0 to 1000, the higher the value, the higher the priority. When the size needs to be adjusted at runtime, the view who has lower hugging property value will expand its size to satisfy the constraints requirement, while the view having a higher hugging property value will keep closely hugging itself to its current size. For example, if we give green label' hugging property to 200, and purple label's hugging property to 100, then when running it on a device with bigger width, then the purple label (who has a low hugging value of 100) will grow the width to satisfy the width constraints


Similarly compression resistance property is used to decide if there are not enough width space, which view needs to shrink. The view has a low compression resistance value will shrink first. In this case, the purple label (who has low resistance property of 100) will shrink itself when running on a narrow iphone device as below. While the green label keeps its current width, who has a compression resistance property of 200.