Monday, July 27, 2015

Using CFNETWORK_DIAGNOSTICS for network log for iOS

CFNetwork has built-in support to log network activity for iOS device by setting the environment variable CFNETWORK_DIAGNOSTICS.

The CFNETWORK_DIAGNOSTICS can be set to the following values:

    1: Enables internal CFNetwork event and url logging

    2: Adds information about http header and how CFNetwork decides to make and re-use TCP sockets

    3: Adds decrypted bytes in and out of network connection.

Following the below steps to use this feature
1. Enable the feature with one of the following options

   option 1: call setenv method in xcode project's main method
   int main(int argc, char * argv[]) {
       setenv("CFNETWORK_DIAGNOSTICS", "3", 1);
          @autoreleasepool {
               return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
       }
  }

  option 2: set the environment var in project scheme as 


2. build and run the project on device, make the server request to be tested

3. from xcode device tab, export the application container. Then show the package content of the downloaded container from Finder, and the log file will be in the folder of
/AppData/Library/Logs/CrashReporter.

  


Wednesday, July 1, 2015

SAP UI5: bootstrap with sap-ui-core.js and debug tip

sap-ui-core.js
SAP UI5 is started by loading the bootstrap js file sap-ui-core.js. The UI5 document has a tutorial to create a simple app at
https://openui5.hana.ondemand.com/#docs/guide/5b9a170d9f6a4d5784e8ab2ded3d8517.html

<script src="resources/sap-ui-core.js" id="sap-ui-bootstrap" data-sap-ui-libs="sap.m" data-sap-ui-theme="sap_bluecrystal"</script>

The important thing is data-sap-ui-libs, which tells sap-ui-core.js to load additional UI5 libraries to be used by the application. for example, if you want to create a button from sap.ui.commons library (not sap.m library) using the below code, but does not include "sap.ui.commons" in the library collection, then the call will fail:
btn = new sap.ui.commons.Button(...)
Internally, sap-ui-core.js just calls jquery.sap.require() to load each library specified in this property.

However,  UI5 document does not clearly indicate how it organizes the UI5 library or give a clarr library list for the library information

Debug tips:
0. on Windows, click CTRL+SHIFT+ALT+S to open UI5 diagnostic window

1. As ui5 loads view.js at runtime by xhr request, instead of using the <script> tag, so it cannot attach desktop browser as debugger to those js file. in order to debug the js view code, one option is pre-loading the js file explicitly in html file, and then the file will be ready for debugging as regular js file. In this case, the xhr loader will check and skip the already loaded file.

2. Changing sap-ui-core.js to sap-ui-core-dbg.js in bootstrap script element or setting parameter "sap-ui-debug=true" in the query string will load debug version of sap-ui-core-dbg.js. The first method is recommended as it can be used to debug sap-ui-core functions. it can also be set at bootstrap script tag as
sap-ui-debug="true"
Note, you can also directly load sap-ui-core-dbg.js in bootstrap ui5 script, as that will show the parameter name properly in javascript debugger variable window.

3. Set log level to debug in configuration object to get detailed log
<script src="resources/sap-ui-core.js" id="sap-ui-bootstrap" data-sap-ui-libs="sap.m" data-sap-ui-theme="sap_bluecrystal" data-sap-ui-config="logLevel:'DEBUG'"</script>

4. By default, all ui5 libraries are loaded synchronously, to load them asynchronously, set  data-sap-ui-preload="sync" in bootstrap script, and then listen for UI5 core's attachInitEvent

<script src="resources/sap-ui-core-dbg.js"
id="sap-ui-bootstrap"
data-sap-ui-config="theme:'sap_bluecrystal',
                    libs:'sap.m',
                    logLevel:'DEBUG',
                    preload:'async'">
</script>
<!-- only load the mobile lib "sap.m" and the "sap_bluecrystal" theme -->

5. click  CTRL + ALT + SHIFT + P to show UI5 technical information dialog box, including setting debug source, and check ui5 version. CTRL + ALT + SHIFT + S to show diagnostic dialog, to see UI5 tree control, property window and binding

Key methods
The bootstrap process uses few key methods in sap-ui-core.js:

sap.ui.define 
Defines a Javascript module with its name, its dependencies and a module value or factory.

sap.ui.require
Resolves one or more module dependencies synchronous (for single parameter) or asynchronously (for callback method)

sap.ui.getCore
Retrieve the SAPUI5 Core instance for the current window.


Get UI5 Version

To check UI5 version from ABAP system, send request to 

http://<HOST>:<port>/sap/public/bc/ui5_ui5/ 
for example:
https://ldai1uia.wdf.sap.corp:44300/sap/public/bc/ui5_ui5 
or 
https://ldai1uia.wdf.sap.corp:44300/sap/public/bc/ui5_ui5/index.html

Sunday, June 14, 2015

iOS RunLoop, NSURLConnection, NSURLSession

iOS provides several ways to execute task in background thread,  like operation queue or Grand Central Dispatch, usually it is not required to fully understand ios RunLoop to make the function work, but in more complex cases, understanding RunLoop is necessary to make things work as expected.

iOS runloop is similar to Windows EventLoop, its main purpose is keeping the thread from exit when it is still needed, but only actively handle the events when the events are arrived.

For the main thread, the ios system will create a RunLoop automatically after starting the app, so the application can keep running, you can attach a runloop observer to the MainRunLoop from AppDidFinishLoadWithOption to verify it using the below method. Whenever any touch event happens on the screen, the Runloop Observer will be noticed for the event.


+ (void) addRunloopObserver:(NSRunLoop*)runloop {
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopAllActivities, YES, 0,
            ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity){
                                                                       static unsigned long count = 0;
                                                                       NSString* strAct;
                                                                       if (activity == kCFRunLoopEntry){
                                                                           strAct = @"kCFRunLoopEntry";
                                                                       }
                                                                       else if ( activity == kCFRunLoopBeforeTimers){
                                                                           strAct =@"kCFRunLoopBeforeTimers";
                                                                       }
                                                                       else if ( activity == kCFRunLoopBeforeSources ){
                                                                           strAct =@"kCFRunLoopBeforeSources";
                                                                       }
                                                                       else if (activity ==  kCFRunLoopBeforeWaiting  ) {
                                                                           strAct =@"kCFRunLoopBeforeWaiting";
                                                                       }
                                                                       else if (activity == kCFRunLoopAfterWaiting){
                                                                           strAct =@"kCFRunLoopAfterWaiting";
                                                                       }
                                                                       else if (activity == kCFRunLoopExit){
                                                                           strAct =@"kCFRunLoopExit";
                                                                       }
                                                                       else {
                                                                           strAct =@"unknown";
                                                                       }
                                                                                                                                            
                                                                       NSLog(@"activity %lu: %@", ++count, strAct);
                                                                   });
CFRunLoopRef loop = [runloop getCFRunLoop];
CFRunLoopAddObserver(loop, observer, kCFRunLoopCommonModes);

}

But if other cases related to the background thread, you will need to start the runloop listener by yourself if you need to keep the thread alive to handle the runLoop event. 

Note when you call [NSRunLoop currentRunLoop], it will create and return a RunLoop object if not existing, so have a RunLoop object for the current thread does not mean the RunLoop has been started to listen for the RunLoop event, and or when or under what condition the RunLoop will stop listening the RunLoop event, and exit the thread method. 

Pay attention when starting while loop using NSRunloop's Run:untilData or beforeDate method, there must be at least one event source already scheduled into the runloop, and the event source's runmode must include the currnt runloop's runmode, this will make the runloop method to believe it need to wait for something form the event source. Otherwise, the runloop sees there is no any event source is scheduled, and it will immediately exit and enter the Run method again to create a dead loop. Add a NSTimer or NSUrlConnection will make the runloop wait for the signal of the event. A good practice is adding the event source in NSRunLoopCommonModes, and start the RunLoop's run method in NSDefaultRunLoopMode

NSThread:
If the background task is started by NSThread's InitWithTask method, then the RunLoop listener is not started. For example, if you start a NSTimer and add it to the new thread's current RunLoop, the timer event will never fires, as the thread method will immediately exit. In order to receive the Timer event, the thread method has to start a while loop to keep listen the runloop events and also prevent the thread from exiting, until all the thread works are finished. 

  while (shouldKeepRunning){
        [loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        NSLog(@"NSLoop returned : %@", [NSDate date]);
    }

Similarly, if you starts a NSURLConnection and schedule it in the thread's runLoop as below, then the connection data delegate method will never be called as the thread already exits without holding by the Runloop's while loop. Add the above while loop in the thread method will get the response. Once the connectionDidFinishLoading is called the nsUrlConnection will remove itself from the RunLoop event source automatically.
    conn = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
    [conn scheduleInRunLoop:threadRunloop forMode:NSRunLoopCommonModes];
    [conn start];


NSOperationQueue
NSOperationQueue for async operation is similar to NSThread, if an operation is scheduled in a background thread by NSOperationQueue, then the NSRunLoop is not started in the worker thread.  For exmple, the NSTimer added into the operation block thread's RunLoop will not get its timer event fired.

NSURLConnection uses NSOperationQueue as alternative for RunLoop. There are two different ways to use it for NSUrlConnection. When NSURLConnection sendAsynchronousRequest method is used, it uses a block to get the response, but the method does not take a delegate parameter, so the caller cannot handle authentication or connection status event. . 
+ (void)sendAsynchronousRequest:(NSURLRequest*) request     queue:(NSOperationQueue*) queue   completionHandler:);

In order to handle authentication request and other connection delegate event with NSOperation for NSURLConnection, setDelegateQueue method is needed. The connection delegate method will be called each time on a thread method, and there is no need to use RunLoop to drive the connection event.


GCD (Grant Central Dispatch)
GCD has the same behavior as NSOperationQueue when handle async dispatch, actually NSOperation uses GCD internally, so it is expected to have the same behavior as NSOperationQueue.

However, the dispatch main queue is unique, as it needs to communicate with the existing main thread. As the thread already exists and is running, the only way to let the thread executes a block or method is injecting an event into its NSRunLoop. That is why handling the main queue or main NSOperationQueue, the task is posted into the application's main RunLoop to be executed, the task is added with mode of NSRunLoopCommonModes.


NSURLSession
Unlike NSURLConnection, NUURLSession no longer gives developer the option to use a RunLoop to handle authentication and delegate event. So developers do not need to worry about whether the RunLoop has been created and started, whether the Runloop modes match the event source, whether the RunLoop thread has exited. That is a major difference between NSURLConnection and NSURLSession. Code using NSURLConnection scheduleInRunLoop method definitely should be updated to use NSOperationQueue to handle the delegate event.

NSObject
Most [NSObject performSelector ...] posts the selector into the thread's NSRunLoop, so the target thread needs to have a NSRunLoop already started and listen for event source to work. Those methods can be used when you need to communicate with an already started long last thread, the common way to do so is the target thread starts an NSRunLoop, and caller can posting a event into its RunLoop listener to run a task, besides the application's main thread, another example is UIWebView's NSURLProtocol client thread.

[NSObject performSelectorInBackground...] method is different from other performSelector method, it just creates an background thread and runs the selector without a NSRunLoop in the target thread.

Sunday, June 7, 2015

What is tint color in ios

Usually, ui control uses color and background color to define colors used to show the control. But for buttonor other clickable control, it may be helpful to use the same color schema to indicate whether they are clickable or not at the current screen. That is what tint color property is used in iOS UIView and sub controls.

iOS 7 has introduced the tintColor property on UIView . It is used to visually indicate which controls on the screen are active or have an action associated with them. For example, bar button items and tab bar items use tint color by default. If a view control does not have an explicit tint color, it inherits its superview’s tint color, which will indicated by the Default value for the color property shown in the Main Storyboard.

Note the tint color should only applied to the clickable controls when they are enabled, so if the control is disabled,  it should use a different color to show the effect. Button control has a shadow property to do so, but segment control or slide control still show the tint color when they are disabled, it is better to change the color to a different one to give user a better experience. In addition, if a control is not clickable, like a text field, or a title bar, then it should not show with the same color used by the tint color on the screen to avoid confusion.

Friday, June 5, 2015

When external parameter name is needed to call swift functions

In the following cases, the parameter name is required when calling a swift function

1. when defining a regular function without external parameter name, the caller does not specify the first parameter's name, but need to specify all following parameter's name
    func join0(string: String, toString: String, withJoiner: String) -> String {
        return string + toString + withJoiner

    }

    join0("hello", toString: "world", withJoiner: ", ")


2. if the function's first as well as other parameters have external names, (internal name and external name are indicated by a space),
    func join1(string s1: String, toString s2: String, withJoiner: String) -> String {
        return s1 + withJoiner + s2
    }
   
    join1(string: "hello", toString: "world", withJoiner: ", ")

3. if the function's first parameters starts with '#', it indicates internal name and external name are same
    func join2(#stringStringtoStringStringwithJoinerString) -> String {
        return s1 + joiner + s2
    }
   
    join1(string"hello"toString"world"withJoiner", ")
 
4. if the function's first parameters has default value, the caller must specify the parameter name to call it
    func join3(stringString ="abc"toStringStringwithJoinerString) -> String {
        return s1 + joiner + s2
    }
   
    join3(string: "hello", toString: "world", withJoiner: ", ") 

 5.In any case, if the parameter's external name is '_', then caller does not need specify the parameter's name,

    func join4( _ string: String = "abc", _ toString: String, _ withJoiner: String) -> String {
        return string + withJoiner + toString
    }
    
       join4("hello", "world", ", ")

    Sometimes, if a parameter has a default value, but does not want to have an external name, then, explicitly specify '_' as external parameter name as shown below:
  • func join(s1Strings2String, _ joinerString = " ") -> String {
  • return s1 + joiner + s2}
  • join("hello""world""-")

Saturday, May 30, 2015

Swift syntax for closure and generic alertbox function

Closure in swift is corresponding to block in Objective C, but its syntax is quite different from block in objective C, or other languages. One thing to notice is closure syntax in swift is quite similar to function definition, but without parameter name.

For example, the beginBackgroundTaskWithName defined in UIApplication class has closure parameter handler as shown below, which takes no parameter () and return Void. In addition, the closure can be nil indicated by the optional type flag ?.

func beginBackgroundTaskWithName(_ taskName: String?,
               expirationHandler handler: (() -> Void)?) -> UIBackgroundTaskIdentifier

A sample code to call the method may looks like:
    var bgTask :UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
     bgTask = application.beginBackgroundTaskWithName("logout", expirationHandler: { () in
            ...
            application.endBackgroundTask(bgTask)
            bgTask = UIBackgroundTaskInvalid;
     });

Now, let take UIAlertAction as a sample to create a generic alertbox. The constructor of UIAlertAction takes a closure parameter handler, which takes a UIAlertAction parameter and return Void. the handler is not optional indicated by !
UIAlertAction(title title: Stringstyle style: UIAlertActionStylehandler handler: ((UIAlertAction!) -> Void )!)

Let see how to create a generic alertbox that takes a title, message, buttonTitle and handler parameters, and once ok is clicked, it will call the handler parameter passed to it

func alert(title: String, message: String, buttonTitle: String, handler:((UIAlertAction!) -> Void )!){             var alertDlg : UIAlertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
    
        var okAction : UIAlertAction = UIAlertAction(title: buttonTitle, style: UIAlertActionStyle.Default, handler:handler);
                    
         alertDlg.addAction(okAction)
         self.presentViewController(alertDlg, animated: false, completion: nil)
 }

A sample code to call the method looks like
self.alert("Warning", message: "The passcode is not correct, please try again", buttonTitle: "Ok",            handler: {(alert) in    println("ok pressed")})


Thursday, May 14, 2015

Debug the first line of js code when page loads

Similar to debug cordova app when app starts, the similar approach can also be used to debug general javascript html page. 

Assume when page starts, it runs the below method, and you need to attach debugger on it.
       
var startFunc = function(){
           console.log("my start func");
}

startFunc();


Then adding the delayedStartForDebug method and also call  delayedStartForDebug(startFunc); will give you enough time to attach the debugger

var startFunc = function(){
           console.log("my start func");
}

var delayedStartForDebug = function(func) {
        var originThis = this;
        var bWait = true;
        var bDone =false;
        var i = 0
        var timer = setInterval(
                function(){
                    if (bWait && i < 10 ){
                        i++;
                        console.log("set bWait to false from console window to continue");
                    }
                    else{
                        if (!bDone){
                            bDone = true;
                            clearInterval(timer);
                            func.apply(originThis, arguments);
                        }
                    }
             }, 3000);
   }
   
   //startFunc();

   delayedStartForDebug(startFunc);