Thursday, April 16, 2015

Lập trình iOS: Bài 2 – Ngôn ngữ lập trình Objective C


Ở bài này chúng ta cùng tìm hiểu về Ngôn ngữ lập trình Objective C, ngôn ngữ được sử dụng để lập trình iOS.
Ở phần này mình giả sử các bạn được có “môi trường làm việc” MAC OS, đã cài đặt IDE Xcode.
Chúng ta sẽ chưa bắt tay vào lập trình nên các ứng dụng iOS, mà ở bài viết này chúng ta sẽ tìm hiểu về Objective C, các kiểu dữ liệu, cú pháp câu lệnh, hướng đối tượng,…

Video Bài 2:


1)   Khởi tạo dự án ví dụ

Mở Xcode và tạo một dự án để bắt đầu nắm các kiến thức cơ bản về Objective C.





Chúng ta tạm thời chấp nhận các phần hiện có, chưa cần thiết phải tìm hiểu ở bước này, ở phần này chúng ta tập trung tìm hiểu Objective C. Click chọn ViewController, chúng ta sẽ tìm hiểu Objective từ đây


2)   Cơ bản Objective C


Ngôn ngữ lập trình Objective-C là một ngôn ngữ lập trình hướng đối tượng được xây dựng chủ yếu dựa trên nền tảng ANSI C, và ngoài ra nó còn được mở rộng từ Smalltalk, một trong những ngôn ngữ lập trình hướng đối tượng đầu tiên. Objective-C được thiết kế với mục đích đưa vào C các tính năng hướng đối tượng một các đơn giản và dễ hiểu nhất.

Objective-C là ngôn ngữ chính được Apple chọn để viết các ứng dụng cho hệ điều hành MAC, iPod và iPhone.

a)    Kiểu dữ liệu cơ bản


Cũng tương tự như C, Objective C có các kiểu dữ liệu cơ bản như int, short, float, char, BOOL…

    int i = 0;
    short s = 0;
    float f = 1.0f;
    char *c = "c";
    BOOL b = YES;

b)    Kiểu đối tượng

Lập trình iOS chúng ta sẽ cần sử dụng đến Cocoa Touch framework (chúng ta chỉ cần biết vậy đã, nói chung nó nằm trong nền tảng mà Apple cung cấp cho chúng ta có thể lập trình được trên iOS), mọi đối tượng trong nền tảng này đều kế thừa từ một lớp cơ sở là NSObject.

NSObject *obj;

Từ lớp này chúng ta có nhiều lớp con khác nhau: NSString, NSArray,…

c)     Định kiểu động

Objective C cung cấp một định kiểu dữ liệu động

id anObject;

Với id, chúng ta sẽ không biết anObject là đối tượng gì (NSArray hay NSString…) mà chỉ đơn giản biết nó là một đối tượng. Điều này khá hữu ích cho chúng ta trong một số trường hợp mà chúng ta không thực sự quan tâm đến loại đối tượng mà ta đang làm việc là loại gì, chỉ cần biết đó là đối tượng để tham chiếu, so sánh…

d)    Cú pháp câu lệnh

Các câu lệnh điều kiện, lặp trong Objective C cũng tương tự như của C

    if (<#condition#>) {
        <#statements#>
    } else if (<#expression#>) {
        <#statements#>
    } else {
        <#statements#>
    }

         switch (<#expression#>) {
        case <#constant#>:
            <#statements#>
            break;
           
        default:
            break;
    }

    for (<#initialization#>; <#condition#>; <#increment#>) {
        <#statements#>
    }


    while (<#condition#>) {
        <#statements#>
    }

    do {
        <#statements#>
    } while (<#condition#>);


e)    Hàm và lời gọi hàm

Định nghĩa hàm và cú pháp gọi một hàm trong Objective-C có khác biệt khá nhiều với các ngôn ngữ lập trình mà ta thường biết như Java, C, C++…
Hàm thì vẫn có kiểu trả về, tên hàm và tham số, tuy nhiên cú pháp của Objective-C khá ngộ.

- (void)demoMethod:(int)param1 with:(NSString *)param2 and:(float)param3
{
}

Đây là cú pháp của một hàm trong Objective-C, các tham số thường không đặt trong cặp dấu () như các ngôn ngữ khác mà chúng được tách biệt ra bởi ký tự trắng và dấu :
Như hàm trên có 3 tham số, ta thấy từ with, and được đặt ở đây nhằm mục đích mô tả thêm cho tham số 2 và 3.

Với hàm trên, chúng ta có thể làm gọn lại như sau

- (void)demoMethod:(int)param1 :(NSString *)param2 :(float)param3
{
}

Tức là bỏ đi with, and và như vậy chỉ còn lại ký tự trắng và dấu :
Tuy cả 2 cách đều đúng, nhưng theo mình thì chúng ta nên sử dụng cách đầu, nó làm cho hàm của chúng ta tường minh hơn.

3)   Hướng đối tượng trong Objective C

Phần này chúng ta sẽ cùng lướt qua các khái niệm chính về hướng đối tượng trong Objective-C. Ở mục này mình mặc định là các bạn đã biết, quen thuộc với hướng đối tượng và ở đây chúng ta chỉ xem là hướng đối trong Objective-C sẽ như thế nào.

a)    Lớp

Từ Xcode chúng ta New một lớp mới như sau



Như đã nói ở trên, lớp đối tượng cơ bản nhất trong Objective-C là NSObject, chúng ta sẽ chọn cho lớp của mình MyClass kế thừa từ lớp này



Tương tự như ngôn ngữ C/C++, Objective-C sẽ tạo ra 2 file cho lớp MyClass:
-       file header (MyClass.h) dùng để khai báo các thông tin cho lớp
-       file m (MyClass.m) là nơi khai báo các đoạn mã thực thi của lớp
-        
Trong 2 file MyClass.h, MyClass.m chúng ta thấy

//MyClass.h
#import <Foundation/Foundation.h>

@interface MyClass : NSObject

@end

//MyClass.m
#import "MyClass.h"

@implementation MyClass

@end

Vậy cú pháp để khai báo một lớp đó là từ khóa @interface (các bạn không nên nhầm lẫn với khái niệm interface trong hướng đối tượng, vì đơn giản đây chỉ là từ khóa do Apple qui định, còn khái niệm interface “truyền thống” sẽ được tìm hiểu ở mục Protocol)

Tương tự như C/C++, kí hiệu : để qui định lớp MyClass của chúng ta kế thừa từ NSObject, và 1 điều chú ý là Objective-C không hỗ trợ đa kế thừa.

b)    Phương thức/Hàm tạo

Trong Objective-C không có khái niệm phương thức tạo cho một lớp. Tất cả các đối tượng của một lớp phải được cấp phát thông qua phương thức alloc và init.

MyClass *myObject = [[MyClass alloc] init];

Trong đó phương thức alloc là cấp phát vùng nhớ, còn phương thức init như là một phương thức tạo mặc định trong Objective-C, được định nghĩa trong lớp NSObject.

-(id)init;

Và như vậy, trong MyClass.m chúng ta có thể override lại hàm init này để khởi tạo các giá trị ban đầu cho lớp của mình, chúng ta sẽ tìm hiểu ở các mục tiếp theo.,

c)     Con trỏ this

Trong hướng đối tượng chúng ta luôn quen thuộc đến khái niệm con trỏ this, và Objective-C cũng không ngoại lệ. Trong Objective-C, con trỏ this được biết đến thông qua từ khóa self.

d)    Thuộc tính lớp

#import <Foundation/Foundation.h>

@interface MyClass : NSObject
{
@private
    int     att1;
    NSString *str1;
   
@protected
    int att2;
   
@public
    NSString *str2;
    int att3;
}

@end

Cách khai báo thuộc tính tương tự C/C++, các chỉ thị truy xuất cho phép giới hạn truy xuất đến thuộc tính, và mặc định khi không có chỉ thị này thì sẽ là @protected

Với thuộc tính public, chúng ta có thể truy xuất thông qua đối tượng của lớp như sau

MyClass *myObject = [[MyClass alloc] init];
   
NSLog(@"%@ %d", myObject->str2, myObject->att3);

Cú pháp tương tự C/C++

Thường thì khi lập trình iOS, mình ít khi sử dụng các thuộc tính public dạng này, vì nó có một số vấn đề liên quan đến quản lý bộ nhớ mà mình sẽ đề cập đến sau. Đa số mình dùng protected, tức là không xài các chỉ thị (private, protected, public) cho tiện J, còn việc truy xuất thuộc tính “public” bên ngoài thông qua đối tượng thì mình thường sử dụng Property hơn (khái niệm này sẽ được thảo luận ở mục dưới)

e)    Khái niệm Property

Khái niệm Property thực ra tương tự như khái niệm setter/getter trong các ngôn ngữ khác như java, C#...

//MyClass.h
@interface MyClass : NSObject
{
    int     myAtt;
    NSString *myStr;
}
@property (nonatomic, assign) int att;
@property (nonatomic, strong) NSString *str;
@end


//MyClass.m
#import "MyClass.h"

@implementation MyClass
@synthesize att = myAtt;
@synthesize str = myStr;

@end

Ở file .h, trong phần khai báo lớp, chúng ta có 2 thuộc tính myAtt và myStr, và mình đã khai báo 2 bộ setter/getter cho chúng thông qua 2 chỉ thị @property
@property (nonatomic, assign) int att;
@property (nonatomic, strong) NSString *str;

Chúng ta khoan hãy quan tâm đến các chỉ thị nonatomic, assign, strong, câu hỏi đặt ra là, làm thế nào để có thể mapping được property att với thuộc tính myAtt, str với myStr để chúng ta có thể quản lý được chúng.
Vấn đề đó ở trong file .m
@synthesize att = myAtt;
@synthesize str = myStr;

Với chỉ thị @synthesize, chúng ta thực hiện mapping property với thuộc tính trong lớp mà chúng ta cần sử dụng.

Khi đó chúng ta có thể truy xuất các thuộc tính này thông qua các property
MyClass *myObject = [[MyClass alloc] init];
myObject.att      = 1;
myObject.str      = @"Hello Kungfu";

Ở đây mình đã cố ý đặt tên thuộc tính là myAtt và property là att để các bạn khỏi bị nhầm lẫn, thực ra mình hoàn toàn có thể đặt tên thuộc tính là att và khi đó
@synthesize att = att;

Và trong bản thân của lớp, khi mình muốn sử dụng thuộc tính (biến) att, mình sẽ dùng trực tiếp
att = 1;

Trong trường hợp mình muốn sử dụng property thay vì gọi trực tiếp biến att, mình sẽ gọi thông qua con trỏ self
self.att = 2;



Trong các phiên bản mới của Xcode, Apple cũng đã linh động hơn cho chúng ta trong việc khai báo các property này. Ở file .h, mình hoàn toàn có thể định nghĩa property nhưng không cần phải định nghĩa thuộc tính (biến)

@interface MyClass : NSObject
{
}
@property (nonatomic, assign) int att;
@property (nonatomic, strong) NSString *str;

@end

Và trong file .m mình không còn sử dụng chỉ thị @synthesize
@implementation MyClass

- (id)init {
}
@end

Tuy nhiên trình biên dịch lúc này sẽ tự động ngầm hiểu cho chúng ta là đã có khai báo 2 thuộc tính
int         _att;
NSString    *_str;

Các bạn để ý 2 dấu ‘_’ ở trước tên biến, Apple mặc định ngầm rằng biến “cục bộ” sẽ có định dạng như vậy, và trình biên dịch cũng mặc định luôn
@synthesize att = _att;
@synthesize str = _str;
Mặc dù chúng ta không hề khai báo, và chúng ta hoàn toàn có thể truy xuất như trên trong lớp
_att = 1;
self.att = 2;
Lưu ý, trường hợp tự nhận biết này chỉ đúng khi chúng ta chỉ có khai báo chỉ thị @property, ngoài ra không khai báo thêm gì khác.

Bây giờ chúng ta qua lại với cú pháp chỉ thị @property.
-       phần 1 (nonatomic/atomic): tham số này các bạn nên chấp nhận nó, khi chúng ta quen thuộc với Objective-C sẽ tìm hiểu thêm
-       phần 2 (assign/retain/strong/copy/readonly): các tham số này sẽ quy định việc thực thi các bộ setter/getter của chúng ta
o   assign: tạm hiểu là gán ngay, thường dùng cho các thuộc tính kiểu vô hướng hoặc các liên kết yếu
o   retain/strong: sử dụng khi thuộc tính nắm giữ  chủ động vùng nhớ set
o   copy: đúng với ý nghĩa của nó, copy vùng nhớ khi set
o   readonly: chỉ được đọc (get), không ghi (set)
Phần này chúng ta sẽ tìm hiểu lại một lần nữa ở bài viết quản lý bộ nhớ trong Objective-C.

f)     Phương thức của lớp

Phương thức, cú pháp thì như chúng ta đã làm quen ở trên. Mục này chúng ta sẽ tìm hiểu cách khai báo phương thức cho lớp.

//MyClass.h
@interface MyClass : NSObject
{
}
@property (nonatomic, assign) int att;
@property (nonatomic, strong) NSString *str;

- (void)publicMethod:(int)param;
+ (int)staticMethod:(int)param;
@end

//MyClass.m
@implementation MyClass

- (void)publicMethod:(int)param;
{
    NSLog(@"%d", param);
}

+ (int)staticMethod:(int)param
{
    return param + 2;
}
@end

Trong file .h chúng ta sẽ tiền định nghĩa phương thức, với phần tên, tham số mà không có phần thực thi, nó tương tự cách của C/C++.
Dấu ‘-’ cho biết đây là phương thức public của lớp, có thể truy xuất thông qua đối tượng.

[myObject publicMethod:1];

Dấu ‘+’ cho biết đây là phương thức static của lớp, có thể truy xuất thông qua tên lớp

[MyClass staticMethod:2];

Trong Objective-C không có định nghĩa phương thức private rõ ràng, thường thì chú ta sẽ định nghĩa phương thức này với phần thực thi và không có phần tiền định nghĩa trong file .h

g)    Protocol

Protocol nó cũng tương tự ý nghĩa một interface trong Java, nó cung cấp cho các lớp thực thi nó một giao tiếp với các phương thức được định nghĩa sẵn, và giao tiếp này sẽ được sử dụng trong các trường hợp cụ thể mà chúng ta sẽ tìm hiểu sau. Trước tiên chúng ta sẽ tìm hiểu khai báo một protocol sẽ như thế nào

@protocol MyProtocol

@optional
- (void)optionalMethod;

@required
- (void)requiredMethod;

@end

Sử dụng từ khóa @protocol để định nghĩa, có 2 chỉ thị @optional và @required với mục đích rõ ràng như ý nghĩa của nó:
-       Optional: các phương thức đằng sau chỉ thị này thì các lớp thực thi có thể không cần phải triển khai mã nguồn thực sự
-       Required: các phương thức đằng sau chỉ thị này thì các lớp thực thi phải triển khai mã nguồn thực sự, nếu không sẽ bị báo lỗi

Và lớp MyClass của chúng ta sẽ thực thi protocol này như thế nào

@interface MyClass : NSObject <MyProtocol>
- (void)publicMethod:(int)param;
+ (int)staticMethod:(int)param;

@end

#import "MyClass.h"

@implementation MyClass

- (void)optionalMethod
{
}

- (void)requiredMethod
{
}
@end

Như vậy, qua bài này, chúng ta đã làm quen và biết rõ hơn một chút về Objective-C, ở bài tiếp theo chúng ta sẽ tìm hiểu thêm 1 phần khá quan trọng, đó là Quản lý bộ nhớ trong Objective-C.

No comments:

Post a Comment