Saturday, September 1, 2012

[Bài Học Số 3] Cơ Bản Lập Trinh Objective C



 http://congdongios.com/showthread.php?1448

Quản lý bộ nhớ
Quản lý bộ nhớ là tiến trình xử lý việc khởi tạo vùng nhớ cho ứng dụng, sử dụng nó và giải phóng nó khi ta đã hoàn thành công việc. Một chương trình quản lý bộ nhớ tốt sẽ sử dụng ít vùng nhớ nhất có thể. Quản lý bộ nhớ là một vấn đề rất quan trọng trong lập trình. Một vấn đề thường hay gặp phải đó là leak memory, khi chúng ta khởi tạo mà không giải phóng vùng nhớ. Điều này có thể dẫn tới việc lãng phí bộ nhớ hoặc nghiêm trọng hơn là out of memory và dẫn tới crash chương trình.

Objective-C cung cấp 3 cách quản lý bộ nhớ
• Manual retain-release (MRR) hoặc có thể gọi là Manual Reference Counting (MRC) có nghĩa là chúng ta sẽ tự quản lý bộ nhớ bằng cách đánh dấu quá trình sử dụng của các object mà ta có. Cụ thể là ta sẽ đếm việc khởi tạo, sử dụng và giải phóng object trong chương trình.
• Autiomatic Reference Counting (ARC), hệ thống sẽ dùng bộ đếm tương tự như MRR nhưng nó sẽ thêm vào method quản lý bộ nhớ tự động tại thời điểm compile. Ta nên sử dụng ARC cho các project mới
• Garbage Collection (GC), hệ thống sẽ tự động lưu vết và tự động giải phóng vùng nhớ của object không cần thiết nữa. Nó sử dụng kỹ thuật quản lý bộ nhớ khác MRR hoặc ARC tuy nhiên GC chỉ hỗ trợ Mac OS X mà không hỗ trợ iOS

Reference Counting
Khi một object được sinh ra, Objective-C sử dụng một kỹ thuật gọi là reference counting hoặc đôi khi gọi là retain counting để theo vết vòng đời của object. Mỗi object sẽ có một số interger đánh dấu nó, được biết như là reference count hoặc retain count. Khi một đoạn code nào đó tác động tới object, đoạn code đó sẽ tăng retain count, khi kết thúc đoạn code và không sử dụng object nữa, nó sẽ giảm retain count. Khi retain count về 0 cũng có nghĩa là nó không sử dụng nữa và lúc này nó sẽ bị hủy khỏi bộ nhớ.

Khi object được khởi tạo bằng các từ khóa như alloc, new hoặc xử lý message copy, retain count của object sẽ tự động là 1. Để tăng retain count lên, ta gửi retain message cho object. Để giảm retain count ta gửi release message.

Khi retain count của object về 0 và object cần được hủy, Objective-C sẽ tự động gửi dealloc message cho object. Ta có thể override dealloc method trong object để release resource nào mà ta muốn. Không nên gọi dealloc method trực tiếp. Để có thể biết được retain count hiện tại, ta gửi retainCount message. Tóm lại ta có 3 method quan trọng như sau
PHP Code:
PHP Code:
-    (idretain
-    (
voidrelease
-    (
unsignedretainCount



retain trả về id nên ta có thể kết hợp nó với chuỗi message ví dụ [[car retain] setTire:tire atIndex:2]; báo cho object car tăng retain count và gửi message setTire. Ta coi ví dụ sau

PHP Code:
PHP Code:
@interface RetainTracker NSObject 
   
@end 

   
@implementation RetainTracker 
   
-(id)init 
   

      if (
self = [super init]) 
      { 
         
NSLog(@”init”); 
      } 
      return 
self
   } 

   -(
void)dealloc 
   

      
NSLog(“dealloc”); 
      [
super dealloc]; 
   } 
   @
end 


Object RetainTracker trong ví dụ trên override lại dealloc methods để xử lý cần thiết khi object bị hủy, tiếp theo ta sẽ coi vòng đời của object RetainTracker thông qua các methods retain và alloc trong ví dụ dưới.

PHP Code:
PHP Code:
int main(int argc, const char *argv[]) 
   { 
      
RetainTracker *tracker = [RetainTracker new]; 
      
//count: 1 

      
[tracker retain];     // count: 2 
      
[tracker retain];     // count: 2 
      
[tracker release];   // count: 1 
      
[tracker retain];     // count: 2  
      
[tracker release];   // count: 1 
      
[tracker release];   // count: 0, dealloc 
      
return (0); 
   } 

Qua ví dụ trên, ta thấy rõ cơ cấu quản lý bộ nhớ của Objective-C như thế nào.


Object Ownership
Cho tới giờ việc quản lý bộ nhớ mà ta biết chưa có gì quá phức tạp, nó đi theo chu trình từ lúc khởi tạo thông qua bộ đếm retain count và dealloc khi về 0. Vấn đề sẽ thực sự trở nên rắc rối khi ta đề cập tới khái niệm object ownership.

Một object với instance variables trỏ tới một object khác được gọi là own object khác. Ta có object CarParts có các variables engine trỏ tới object engine và tires trỏ tới các objects tires. Ví dụ trong object CarParts có methods

PHP Code:
-(void)setEngine: (Engine*) newEngine;


Và trong hàm main() ta gọi

PHP Code:
PHP Code:
Engine *engine = [Engine new]; 
      [
car setEngineengine]; 



Trong trường hợp trên ta sẽ gặp rắt rối nảy sinh khi object engine vừa nằm trong hàm main() vừa được sử dụng trong object car (kiểu CarParts) và khó xác định khi nào thực sự object engine bị dealloc. Nếu ta release trong hàm main() thì object engine sẽ bị xóa trong khi CarParts vẫn đang sử dụng, tương tự nếu release trong CarParts object nhưng hàm main() vẫn còn cần nó.

Cách giải quyết vấn đề trên là ta sẽ tăng retain count của engine lên 2 ngay khi object CarParts sử dụng nó. Như vậy retain count của engine là 2 và khi ta release khi CarParts không sử dụng nó nữa sau đó release tại main() thì retain count của engine về 0 sẽ được dealloc một cách hợp lý.

Retain và Release trong Methods
Đầu tiên ta nói kỹ hơn một chút về object CarParts, nó có chứa một instance variable engine kiểu Engine* và hàm setEngine được gọi là một accessor method (coi phần Property) có chức năng gán object cho engine. Ngoài ra trong dealloc method của object CarParts có xử lý cho phần dealloc của engine variables. Quay lại ví dụ ở phần trước, ta sẽ coi cách xử lý trong method setEngine như thế nào:

PHP Code:
PHP Code:
-(void)setEngine: (Engine *)newEngine 
   

      
engine=[newEngine retain];    //chú ý retain trả về id của Engine 
   



Cách code như trên đã thõa đúng cách giải quyết mà ta đưa ra ban đầu, newEngine (tức là engine mà main method đưa vào) được tăng retain count trong accessor method. Khi dealloc method trong CarParts được gọi thì engine sẽ được release để retain count giảm xuống như phương pháp ta đã nêu ở phía trên. Tuy nhiên cách code này có 1 vấn đề là nếu engine variable đang trỏ tới một object Engine khác (tức là ta đã gửi message method setEngine trước đó) thì object đó sẽ không được release khi engine trỏ sang object Engine mới, điều này làm leak object Engine cũ, do đó cách code như trên là không tối ưu.

Để khắc phục tình trạng ở phía trên, ta xem thử đoạn code dưới đây

PHP Code:
PHP Code:
-(void)setEngine: (Engine *)newEngine 
   

      [
engine release]; 
      
engine=[newEngine retain];    //chú ý retain trả về id của Engine 
   



Cách code trên đưa ra giải pháp release ngay engine variable trước khi nhận object Engine mới. Nhìn có vẻ hợp lý, tuy nhiên đoạn code trên sẽ gặp vấn đề cực kỳ lớn nếu như ta gặp phải đoạn code dưới đây

PHP Code:
PHP Code:
Engine *engine = [Engine new];   //count: 1 
   
Car *car1 = [Car new]; 
   
Car *car2 = [Car new]; 

   [
car1 setEngineengine];   //count: 2 
   
[engine release];  //count: 1 

   
[car2 setEngine: [car1 engine]];   //count: 0 


AutoRelease

AutoRelease là cơ chế mà ta không cần phải quan tâm đến việc release đối tương, nó sẽ tự động giảm retain count xuống và release khi ta không sử dụng nó nữa. Để khai báo autorelease ta sử dụng như sau :

PHP Code:
[PHP]Car *car1 = [[[Car alloc] init] autorelease];


[/PHP]

Tuy nhiên chúng ta nên cẩn thận đối với loại này. Ví dụ hàm ta có 1 đối tượng car1 trong class và nó được khai báo như trên trong phương thức A(), ở phương thức B() ta sử dụng đối tượng này sẽ bị crash do khi chạy hết hàm A() thì đối tượng car1 đã bị release.

Protocols
Khai báo các protocols methods có thể được implement trong bất cứ class nào. Protocols có thể hữu ích trong ba trường hợp sau:
• Khai báo method mà class khác dự định sẽ thực thi
• Khai báo interface cho một object và che dấu nội dung bên trong nó
• Dễ dàng nắm bắt được cấu trúc nhiều class tương đồng có liên quan (khi sử dụng chung interface)

Properties
Khai báo properties là một chức năng Objective-C cung cấp để ta có thể khai báo và truy cập dễ dàng vào các giá trị của object thông qua các accessor methods.

Khái quát
Ta chủ yếu có thể truy cập vào properties của object thông qua cặp method truy cập (accessor methods) được gọi là getter và setter. Sử dụng properties mang lại cho ta rất nhiều thuận lợi:
• Cung cấp một cái nhìn rõ ràng cho người dùng, hiểu được cách thức tương tác với API.
• Compiler có thể tổng hợp các accessor methods (getter/setter) mà ta đã khai báo


Khai báo và thực thi Property
Trong phần này sẽ có 2 phần là khai báo và thực thi property

Khai báo Property
Một property được khai báo bắt đầu bằng từ khóa @property. @property có thể xuất hiện bất cứ ở đâu trong danh sách khai báo các methods. @property cũng có xuất hiện trong phần khai báo protocol và category.

PHP Code:
@property (attribute) type name;


Từ khóa @property khai báo một property. Giống như các dạng khác trong Objective-C, mỗi property có một kiểu dữ liệu và một cái tên. Ngoài ra như ví dụ trên ta thấy còn có tùy chọn các thuộc tính attribute thêm trong dấu ngoặc trong trường hợp đặc biệt, ta sẽ coi ở phần bên dưới.

Tiếp theo chúng ta coi ví dụ đơn giản khai báo một property

PHP Code:
PHP Code:
@interface MyClass NSObject
   
@property float value;
   @
end 


Trong ví dụ trên thì dòng thứ 2 ta khai báo một property tên value kiểu float. Nó tương đương với dòng lệnh
PHP Code:
PHP Code:
-(float)value;
   -(
void)setValue:(float)newValue



Khai báo Property Attributes
Chúng ta có thể thêm vào một property các attributes theo cú pháp @property(attribute1, attribute2,…). Nếu ta dùng @synthesize (trong phần implementation) để nói cho compiler tạo các accessor methods truy cập property, compiler sẽ tự động xử lý hoàn toàn. Tuy nhiên nếu ta tự xử lý accessor methods, ta phải chắc rằng nó phù hợp với những gì ta đã khai báo trong interface, kể cả các attributes. Ta sẽ coi rõ trong phần implementation.

Accessor Method Names
Tên mặc định của getter và setter methods với property là PropertyName và setPropertyName. Ví dụ như property có tên foo thì getter method là foo và setter method là setFoo. Tuy nhiên ta có thể chỉnh sữa việc này trong property attribute với attributes getter= và setter=. Hai attributes này có thể kết hợp với các attribute khác một cách dễ dàng ngoại trừ attribute readonly, vì khi đó ta không được xài setter=. Chú ý là khi ta xài hai attributes này thì trong phần implementation phải xử lý thích hợp.

PHP Code:
getter=getterName


Khai báo trước tên accessor methods getter mà ta phải thực thi trong implementation. Chú ý getter method ko chứa parametters

PHP Code:
setter=setterName


Khai báo trước tên accessor methods setter mà ta phải thực thi trong implementation. Chú ý setter method ko chứa parametters và không đi cùng với attribute readonly.

Wriability
Những attributes này quyết định khả năng read/write của property, ta có thể quyết định một property có thể read hay write hay không qua các attributes này

PHP Code:
PHP Code:
readwrite :Property sẽ có thể vừa read vừa writeđây là attribute mặc định
readonly 
Property chỉ có thể readsetter method trong implementation là không có



Thực thi Property
Trong phần implementation, ta có thể dùng @synthesize và @dynamic để báo cho compiler thực thi các property mà ta đã khai báo trên interface. Chú ý là nếu ta không sử dụng các từ kháo trên, ta sẽ phải tự thực thi các getter/setter methods một cách chính xác hoàn toàn (theo các properties đã khai báo kèm).

@synthesize
Chúng ta dùng @synthesize để nói compiler tạo ngầm các setter/getter methods cho property nếu chúng ta không có khai báo chúng trong @implementation. Ta coi ví dụ

PHP Code:
PHP Code:
@interface MyClass NSObject
   
@property(copyreadwriteNSString *value;
   @
end

   
@implementation MyClass
   
@synthesize value;
   @
end 


Ta có thể dùng lệnh gán để chỉ định instance variable đặc biệt nào được dùng cho property. Ví dụ

PHP Code:
@synthesize firstName, lastName, age=yearOld;


Dòng lệnh trên tạo các accessor methods cho firstName, lastName và age trong đó giá trị của property age là giá trị của instance variable yearOld. Mặc khác @synthesize sẽ thiết lập các accessor method phù hợp với các attributes mà ta khai báo cho properties.

@dynamic
Một vài trường hợp accessor methods được tạo động lúc runtime, khi đó ta dùng từ khóa @dynamic để thông báo với hệ thống rằng các accessor methods sẽ được tạo động trong lúc runtime. Ta sẽ sử dụng nó và coi kỹ hơn khi thao tác với NSManagedObject.

1 comment: