Chủ Nhật, 20 tháng 12, 2015

IOS - Giới thiệu về lập trình Concurrency

Lập trình Concurrency được hiểu là lập trình đa tác vụ được thực thi cùng lúc. Với các thế hệ CPU mới đa nhân như hiện nay thì lập trình Concurrency đang được tận dụng để làm tăng hiệu quả của chương trình. Trong các HĐH như OS X hay IOS, hầu hết các chương trình được chạy ngầm định và các tác vụ của chúng được thực thi không đòi hỏi sự liên tục. Nếu trong một ứng dụng chỉ cần 1 tác vụ chạy thường trú và chỉ chiếm giữ 1 phần nhỏ trong các core của CPU, thì các tài nguyên còn lại sẽ bị lãng phí vô ích.

Trước đây, khi giới thiệu Concurrency là nghĩ ngay đến tạo một hay nhiều Thread. Nhưng Thread chỉ là một công cụ xử lý tác vụ thủ công cấp thấp. Do vậy, lập trình trực tiếp trên Thread đôi khi đầy rủi ro, khó khăn thậm chí là bất khả thi.

Các HĐH OS X hay IOS đã tích hợp các cách tiếp cận bất đồng bộ để xử lý các task đồng thời. Thay vì cách xử lý truyền thống trực tiếp trên Thread, các ứng dụng chỉ cần khai báo các task cụ thể cần được xử lý và đề hệ thống tự xử lý lấy. Nhờ vậy nâng cao khả năng phát triển mở rộng của ứng dụng, giảm độ phức tạp trong xử lý code cho lập trình viên và cải thiện sự hiệu quả của các mô hình lập trình.

Trong lập trình Concurrency, cơ bản có 3 cơ chế xử lý task bất đồng bộ:

- Dispatch Queues
- Operations Queues
- Dispatch Sources 

Thứ Sáu, 18 tháng 12, 2015

IOS - Hướng dẫn về Operation Queues trong lập trình Concurrency

Operations Queues xắp xếp thứ tự task được đưa vào xử lý theo các nguyên tắc mà một trong số đó là task nào phụ thuộc vào sự hoàn tất của task khác sẽ được xử lý sau. Điểm quan trọng là 1 task khi được thêm vào Operation queue sẽ được thực thi ngay và không bị xóa khỏi queue cho đến khi nó hoàn tất (kể cả khi task đó bị hủy). 


Thay vì sử dụng lớp NSOperation - thực tế là một lớp cơ sở trừu tượng - Apple đã cung cấp 2 lớp con để dùng trong Operation Queues: NSInvocationOperation và NSBlockOperation. Chúng ta sẽ tìm hiểu cụ thể hơn cách sử dụng lớp NSInvocationOperation qua ví dụ sau:

Download source sau để chạy thử : ThreadTestApp

Đầu tiên chúng ta xem xét phần UI gồm các Label và Button trong file Main.storyboard và file ViewController.h khai báo các properties hay phương thức cho các UI này: 
Hình chú thích khai báo của các properties của UILabel hay phương thức của UIButton trong Objective-C
Bây giờ, chúng ta tập trung vào phần code trong file ViewController.m. Trong hàm viewDidLoad có khởi tạo instance cho NSOperationQueue. Tiếp đến chúng ta tạo 2 đối tượng của NSInvocationOperation lần lượt cho các task counterTask và colorRotatorTask, rồi gọi hàm addOperation để đưa lần lượt các operation vào operationQueue:
- (void)viewDidLoad {
    [super viewDidLoad];
//    create a new NSOperationQueue Instance.
    operationQueue = [NSOperationQueue new];
//    create a new NSOperationObject using NSInvocationOperation subclass.
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(counterTask) object:nil];
    [operationQueue addOperation:operation];
//    [operation release];
    
//    the same as story above, just tell here to execute the colorRotatorTak method.
    operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(colorRotatorTask) object:nil];
    [operationQueue addOperation:operation];
}

- counterTask và colorRotatorTask là các phương thức để setText cho các UILabel:

-(void)counterTask{
    // Make a BIG loop and every 100 steps let it update the label1 UILabel with the counter's value.
    for (int i=0; i < 2000000; i++) {
        if (i % 100 == 0) {
            // Notice that we use the performSelectorOnMainThread method here instead of setting the label's value directly.
            // We do that to let the main thread to take care of showing the text on the label
            // and to avoid display problems due to the loop speed.
            [_label1 performSelectorOnMainThread:@selector(setText:)
                                     withObject:[NSString stringWithFormat:@"%d", i]
                                  waitUntilDone:YES];
//            [NSThread sleepForTimeInterval:0.4];
        }
        
    }
    
    // When the loop gets finished then just display a message.
    [_label1 performSelectorOnMainThread:@selector(setText:) withObject:@"Thread #1 has finished." waitUntilDone:NO];
}
-(void)colorRotatorTask{
    // We need a custom color to work with.
    UIColor *customColor;
    
    // Run a loop with 500 iterations.
    for (int i=0; i < 500; i++) {
        // Create three float random numbers with values from 0.0 to 1.0.
        float redColorValue = (arc4random() % 100) * 1.0 / 100;
        float greenColorValue = (arc4random() % 100) * 1.0 / 100;
        float blueColorValue = (arc4random() % 100) * 1.0 / 100;
        
        // Create our custom color. Keep the alpha value to 1.0.
        customColor = [UIColor colorWithRed:redColorValue green:greenColorValue blue:blueColorValue alpha:1.0];
        
        [_label1 performSelectorOnMainThread:@selector(setText:) withObject:[NSString stringWithFormat:@"Thread #2 with number %d has just finished.", i]  waitUntilDone:NO];
        
        NSLog(@"%@", [_label1 text]);
        
        // Change the label2 UILabel's background color.
        [_label2 performSelectorOnMainThread:@selector(setBackgroundColor:) withObject:customColor waitUntilDone:YES];
        
        // Set the r, g, b and iteration number values on label3.
        [_label3 performSelectorOnMainThread:@selector(setText:)
                                 withObject:[NSString stringWithFormat:@"Red: %.2f\nGreen: %.2f\nBlue: %.2f\nIteration #: %d", redColorValue, greenColorValue, blueColorValue, i]
                              waitUntilDone:YES];
        
        // Put the thread to sleep for a while to let us see the color rotation easily.
        [NSThread sleepForTimeInterval:0.4];
    }
    
    // Show a message when the loop is over.
    [_label3 performSelectorOnMainThread:@selector(setText:) withObject:@"Thread #2 has finished." waitUntilDone:NO];
}

Các task sẽ được chạy theo 2 thread đồng thời và xen vào nhau mà ta sẽ thấy khi chạy app. Ngoài ra còn 1 thread cũng khác chạy đồng thời qua các phương thức bắt touch event của 1 trong các UIButton Color #1, Color #2, Color #3:

- (IBAction)applyBackgroundColor1:(id)sender {
    [self.view setBackgroundColor:[UIColor colorWithRed:255.0/255.0 green:204.0/255.0 blue:102.0/255.0 alpha:1.0]];
}

- (IBAction)applyBackgroundColor2:(id)sender {
    [self.view setBackgroundColor:[UIColor colorWithRed:204.0/255.0 green:255.0/255.0 blue:102.0/255.0 alpha:1.0]];
}

- (IBAction)applyBackgroundColor3:(id)sender {
    [self.view setBackgroundColor:[UIColor whiteColor]];
}

Khi chạy ứng dụng, ta sẽ nhận ra thread của counterTask và colorRotatorTask chạy đồng thời xen cài với nhau (trên trong Label 1 đôi lúc xuất hiện dòng chữ 'Thread #2 with number ... has just finished.' thay vì xuất hiện những số nguyên dương). Ví dụ này cho chúng ta thấy rõ cơ chế chạy Concurrency của các thread trong NSOperationQueue.