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""-")