nghialv blog

practice makes perfect

#include, #import, @import (Clang Modules)

| Comments

Mở đầu

Happy New Year! Chúc mọi người năm mới vui vẻ, hạnh phúc.

Như các bạn cũng biết gần đây XCode5 cùng iOS7 đã được giới thiệu. Đi cùng XCode5 là feature mới “modules” của Clang, một giải pháp nhằm giải quyết một số vấn đề như tăng tốc độ compile source code của ứng dụng. Hôm nay mình sẽ giới thiệu qua về tính năng modules này. Hiện tại thì modules đã có thể sử dụng trong C và Objective-C trên môi trường iOS7 hoặc MacOSX 10.9. Các đoạn code dưới đây tuy mình viết bằng Objective-C nhưng cũng gần như tương tự với C. Để hiểu về modules thì trước tiên mình sẽ giải thích lần lượt về #include, #import, và pre-compiled headers (PCH), sau đó là về modules.

#include

Khi chúng ta include 1 file header thì tại giai đoạn preprocessing của quá trình compile, compiler sẽ copy nội dung của file header này và paste vào dòng #include. Và tất nhiên quá trình copy/paste này là đệ quy cho đến khi copy xong tất cả file header mà nó include và các file header khác được include tại các file nó include. (hơi xoắn)

Ví dụ với chương trình helloworld quen thuộc như dưới đây:

helloworld.m
1
2
3
4
5
6
7
8
#include <Foundation/Foundation.h>

int main(int argc, const char *argv[])
{
     NSLog(@“Hello world);

     return 0;
}

Chúng ta có thể chạy preprocessor để xem file sinh ra sau giai đoạn này bằng lệnh clang -E helloworld.m | less.

Nhìn vào kết quả output chúng ta có thể thấy tới hơn 92000 dòng là của Foundation.h (và của các file header mà Foundation.h include), chỉ 8 dòng cuối là code của chúng ta.

Với việc sử dụng #include tồn tại vấn đề gọi là recursive include. Ví dụ :

FirstFile.h
1
2
3
#include "SecondFile.h"

/* Some code */
SecondFile.h
1
2
3
#include "FirstFile.h"

/* Some other code */

Khi đấy preprocessor sẽ duyệt file FirstFile.h và copy nội dung của SecondFile.h vào FirstFile.h. Khi duyệt file SecondFile.h lại copy/paste nội dung của file FirstFile.h. Vấn đề này được gọi là recursive include.

#import

Trong Objective-C để tránh vấn đề recursive include như trên thì chúng ta thường dùng #import. Khi dùng #import thì trước khi include 1 file header, preprocessor sẽ kiểm tra xem file đấy đã được include chưa, nếu đã include rồi thì sẽ không include nữa. Tương tự trong C chúng ta cũng tránh recursive include bằng việc kiểm tra file header đã được include chưa như sau:

1
2
3
4
5
6
#ifndef MYFILE_H
#define MYFILE_H
 
// Some code
  
#endif

@import

Tuy nhiên việc sử dụng #import cũng như #include khiến cho preprocessor đối mặt với 1 số vấn đề khác như Fragility và Performance. Để hiểu về vấn đề Header Fragility chúng ta xem qua một ví dụ đơn giản sau:

MyFile.h
1
2
3
4
5
6
7
#define NSURL @“my url"

#import <Foundation/Foundation.h>

@interface MyClass :NSObject

@end

Khi đó sau quá trình preprocessing thì file header của chúng ta sẽ như sau:

1
2
3
4
5
6
7
8
#define NSURL @“my url"

// đoạn code được copy từ Foundation.h
// và tất cả những đoạn có chứa NSURL của Foundation.h đều bị thay bằng “my url”

@interface MyClass :NSObject

@end

Tất cả những đoạn NSURL của Foundation.h đều bị preprocessor thay thế bằng “my url” do có #define NSURL @“my url” bên trên. Từ đó ta thấy với việc dùng #include hay #import thông thường thì các header của các file khác, hay của thư viện mà chúng ta dùng đều có thể bị ảnh hưởng như việc dùng #define ở trên.

Về vấn đề performance thì như ở trên ta đã thấy #include#import sẽ copy/paste toàn bộ file header mà nó include (đệ quy). Như ở ví dụ đầu tiên chúng ta chỉ include mình Foundation.h nhưng sau khi preprocessing thì có tới hơn 92000 dòng là của Foundation.h (và các file header mà Foundation.h include), chỉ 8 dòng cuối là code của chúng ta. Thế nên thời gian compile sẽ trở nên nhiều hơn rất nhiều.

Pre-compiled headers

Để giải quyết 1 phần vấn đề performance chúng ta có thể dùng precompiled headers (.pch). Nếu các bạn chú ý thì tất cả iOS project khi được XCode tạo ra đều có file PROJECTNAME-Prefix.pch như sau:

PROJECTNAME-Prefix.pch
1
2
3
4
5
6
7
8
9
10
#import <Availability.h>

#ifndef __IPHONE_3_0
#warning "This project uses features only available in iOS SDK 3.0 and later."
#endif

#ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>Foundation;
#endif

Trong file .pch này chúng ta sẽ include những header mà có khả năng được include tại nhiều nơi trong source code của ứng dụng như Foundation.h, UIKit.h… Khi source code của ứng dụng được compile thì file .pch này sẽ được compile đầu tiên, đồng nghĩa với việc tất cả file header được include trong file .pch này sẽ được compile trước và được include vào tất cả source code.

Bằng viêc caching những file header đã được biên dịch này thì những file này chỉ cần compile 1 lần, những lần sau chỉ cần sử dụng lại nên thời gian compile sẽ được rút gọn.

Thế nhưng các developer thường không hay quản lý file .pch này, và không phải file header nào cũng được dùng tại nhiều nơi trong source code nên hiệu quả của .pch chưa được cao.

Modules

Vào tháng 11 năm 2012, Doug Gregor (một kỹ sư của Apple) đã giới thiệu tính năng modules nhằm giải quyết vấn đề trên của proprocessor thay cho .pch. Vậy module là gì? Module chính là một package mô tả một library, framework.

Ví dụ chạy 2 lệnh dưới đây ta sẽ có thể xem được các module trong SDK của iOS7.

1
2
3
4
5
6
7
8
9
10
11
12
13
% cd `xcrun --sdk iphoneos --show-sdk-path`
% find . -name module.map   

 ./Developer/Library/Frameworks/XCTest.framework/module.map   
 ./System/Library/Frameworks/AudioToolbox.framework/module.map   
 ./System/Library/Frameworks/AudioUnit.framework/module.map   
 ./System/Library/Frameworks/CoreAudio.framework/module.map     
    :     
    :   
 ./usr/include/dispatch/module.map   
 ./usr/include/mach-o/module.map   
 ./usr/include/module.map   
 ./usr/include/objc/module.map

Với mỗi framework ta thấy có 1 file module.map để mô tả framework đấy.

Và để sử dụng framework chúng ta có thể thay #import <Frameworkname.h> bằng @import Frameworkname; Ví dụ khi sử dụng framework Foundation ta sẽ dùng @import Foundation; Vậy khi trong một file header gặp đoạn import module thì compiler đã xử lý gì và tại sao lại giải quyết được vấn đề Fragility và Performance của preprocessor?

Ví dụ khi trong một file header, preprocessor gặp @import Foundation thì sẽ xử lý các bước như sau:

  • Tìm file module.map của framework có tên là Foundation
  • Dựa vào mô tả về framework trong file module.map này compiler sẽ parse các file headers và sinh ra file module (lưu dưới dạng AST - biểu diễn dưới dạng tree trước khi chuyển sang mã máy)
  • Load file module này tại đoạn khai báo import
  • Cache file module này để sử dụng lại cho những lần sau

Thứ nhất thay vì copy nội dung các file header được include rồi mới compile, mà import trưc tiếp file module đã được lưu dưới dạng AST nên các header của framework ko bị ảnh hưởng bởi các đoạn code trước khi import (như #define) -> tránh được vấn đề Fragility.

Thứ hai là nhờ việc cache những file module này mà compiler không phải biên dịch lần 2 nên sẽ rút gọn thời gian biên dịch.

Ngoài ra một điều thú vị nữa mà tính năng module mang lại cho lập trình viên đó là chúng ta không phải tự tay link các framework mà chúng ta import. Ví dụ như trước đây nếu trong file tmp.m có #include <Foundation/Foundation.h> thì khi biên dịch chúng ta phải tự link tới Foundation bằng lệnh : clang tmp.m -o tmp -framework Foundation

Thế nhưng khi sử dụng @import thì chúng ta không cần phải tự link tới framework nữa mà chỉ cần:

clang tmp.m -o tmp -fmodules

Với XCode chúng ta sẽ không phải add thêm các framework mà mình muốn dùng trong Link Binary With Libraries như hình dưới đây.

Đối với những project được tạo từ XCode5 thì tính năng module tự động được enable. Nhưng những project được tạo trước đây các bạn phải tự enable trong phần Build Settings (tức là set flag -fmodules).

Kết luận

Bài viết này mình đã giới thiệu qua tính năng module của Clang trong được giới thiệu từ XCode5. Và đồng thời cũng giải thích qua về #include, #import, pch. Mặc dù tính năng module vẫn đang trong quá trình hoàn thiện nhưng hiện tại chúng ta đã có thể sử dụng với XCode5.

Các bạn có thể tìm hiểu thêm tại:

| Comments

Description

“– Welcome to a new world of presentations –”

SSlide is a presentation tool that will make your presentations more interactive.

FEATURES

  • Integrating with SlideShare

    • Browse your slides on SlideShare
    • Download slides for offline viewing
    • Browse your favourite slides at top page (you can add or remove the tags)
    • Search slides by keyword with three options: relevance, most viewed, latest
  • Presentation

    • Streaming
      • establish streaming for the subscribing from other devices
      • subscribe to a streaming channel (established by other device or web)
      • synchronize your slide page
      • synchronize your drawing on each page
    • Send the questions to the presenter directly on your device
    • Take the notes in your slide
  • Super cool interface

keywords: slideshare, slide, presentation, streaming

[iOS Property:attributes]

| Comments

Mở đầu

Nếu bạn đã từng sử dụng Objective C thì thấy rằng khi khai báo các property cho 1 class nào đấy chúng ta có 2 cách như sau:

1
2
3
@interface MyClass : NSObject {
    NSString *myString;
}

hoặc có thể dùng @property (attributes) type name để khai báo như sau:

1
2
3
@interface MyClass : NSObject {
}
@property (strong, nonatomic) NSString *myString;

Với cách thứ 2 thì compiler sẽ tự động sinh ra các setter/getter cho property ấy. Thế nhưng việc sinh ra setter/getter như thế nào là phụ thuộc vào tập attributes mà bạn đã set ở trên. Khi mới bắt đầu code iOS mình thấy việc set thuộc tính này hơi bị loạn với khá nhiều thuộc tính (retain, strong, weak, unsafe_unretained, nonatomic…). Rồi khi phiên bản thay đổi, kiểu project có dùng ARC hay không cũng dẫn đến việc sử dụng các thuộc tính này cũng khác nhau. Ngoài ra trong một số trường hợp nếu bạn không sử dụng đúng thuộc tính có thể làm app của bạn chạy bị lỗi. Trong bài viết này mình sẽ tóm tắt lại các thuộc tính của property, cũng như nói về khi nào sẽ dùng thuộc tính nào, tại sao, và thuộc tính nào là mặc định.

Các thuộc tính của property

Nếu chia nhóm thì có lẽ bao gồm 3 nhóm thuộc tính như sau:

Writability

Nhóm này có 2 thuộc tính là readwritereadonly. Nhóm thuộc tính này thì khá là dễ hiểu. Với thuộc tính readwrite thì compiler sẽ generate ra cả setter và getter, còn readonly thì compiler chỉ generate ra getter. Mặc định là readwrite (không liên quan đến project dùng ARC hay không).

Setter Semantics

Nhóm này gồm các thuộc tính để chỉ ra cách thức quản lý bộ nhớ, bao gồm các thuộc tính như sau: assign, strong, weak, unsafe_unretained, retain, copy. Khi chúng ta set một trong các thuộc tính này cho property thì setter (getter không liên quan) được tạo ra thay đổi tương ứng với thuộc tính đó. Trước hết chúng ta sẽ nói qua về cách quản lý bộ nhớ trước iOS5 khi mà ARC chưa xuất hiện.

1
2
3
Car *car1 = [[Car alloc] init];
//...
[car1 release]

Trước khi ARC xuất hiện thì các lập trình viên iOS đều phải tự quản lý bộ nhớ. Khi chúng ta tạo object với vùng nhớ của nó, đồng nghĩa với việc chúng ta nắm giữ ownership của object đó. Khi không cần dùng nữa thì phải huỷ bỏ ownership đấy đi bằng cách gửi message release. Một object có thể có nhiều ownership và mỗi object sẽ có 1 property tên là retainCount để lưu số lượng owner của nó. Mỗi khi chúng ta tạo object, hay retain thì retainCount lại được tăng lên 1. Khi chúng ta gửi message release tới object đấy thì retainCount lại bị giảm đi 1. Một khi retainCount bằng 0 thì vùng nhớ của nó sẽ bị giải phóng. Chúng ta có thể gửi message retain để tạo thêm ownership như ví dụ dưới đây. Khi đó car1car2 cùng trỏ đến 1 vùng nhớ và retainCount bây giờ bằng 2.

1
2
// retain
Car *car2 = [car1 retain];  // retainCount = 2

Ngoài ra để copy sang vùng nhớ mới chúng ta có thể gửi message copy như ví dụ dưới đây. Khi đó retainCount ở vùng nhớ mới có giá trị khởi tạo là 1.

1
2
// copy
Car *car3 = [car1 copy];    // retainCount = 1

Quay trở lại với thuộc tính của property. Thuộc tính đầu tiên là retain. Như ví dụ dưới đây khi ta set thuộc tính retain cho property name thì compiler sẽ sinh ra setter setName như bên dưới.

1
2
3
4
5
@interface Car: NSObject

@property (nonatomic, retain) NSString *name;

@end;
1
2
3
4
5
- (void)setName:(NSString *)newName {
    [newName retain];
    [_name release];
    _name = newName;
}

Nhìn vào setter ta thấy đầu tiên là tạo ownership (hay tăng retainCount thêm 1) của newName bằng cách gọi [newNmane retain]. Tiếp theo là việc gửi message release tới _name ban đầu để xoá ownership ban đầu đi. Sau đó mới gán contrỏ trỏ đến object mới. Vậy nên thuộc tính retain giúp tạo ra setter trong đó tạo ownership mới và trỏ đến vùng nhớ mới. Chú ý rằng thuộc tính retain chỉ dùng cho những project không dùng ARC.

Và từ iOS5 trở đi Apple giới thiệu ARC giúp cho việc quản lý bộ nhớ đơn giản hơn. ARC không hoạt động như các Garbage Collection khác mà thực ra chỉ là phần front-end của compiler nhằm mục đich tự động chèn thêm các đoạn code gọi message như retain hay release. Từ đấy lập trình viên không phải gọi các message này nữa. Ví dụ như 1 object được tạo trong 1 method thì sẽ chèn thêm đoạn gửi message release tới object đó ở gần cuối method. Hay trong trường hợp là property của 1 class Car ở trên thì tự động chèn [_name release] trong method dealloc của class Car chẳng hạn. Khi project của bạn dùng ARC thì chúng ta sẽ dùng thuộc tính strong thay cho thuộc tính retain. strong cũng tương tự như retain sẽ giúp tạo ra setter, mà trong setter đó tạo ra ownership mới (tăng retainCount thêm 1). Và ngoài ra ARC sẽ thêm các đoạn gửi message release tới các property này trong method dealloc của class.

Thế nhưng xuất hiện vấn đề có tên là Strong Reference Cycles. Mình sẽ lấy 1 ví dụ để thấy rõ hơn về vấn đề này. Một object A nào đấy có ownership của 1 object B. Object B lại có ownership của 1 object C. Object C lại có ownership của object B. Một khi object A ko cần thiết nữa thì trong method dealloc của A sẽ gửi message release tới object B. retainCount của object B giảm đi 1 nhưng vẫn còn 1 ( do object C retain ) thế nên method dealloc của object B không bao giờ được gọi, kéo theo message release cũng không bao giờ được gửi tới object C. Từ đó dẫn đến vùng nhớ của object B và object C không được giải phóng => xuất hiện hiện tượng Leak Memory. Vì vậy để tránh hiện tượng này ta sẽ dùng thuộc tính weak thay vì dùng thuộc tính strong trong class của object C. Với thuộc tính weak thì trong setter được sinh ra sẽ không retain (không tăng retainCount thêm 1) mà chỉ đơn thuần gán con trỏ trỏ đến vùng nhớ mới. Thuộc tính weak cũng chỉ dùng trong trường hợp bạn đang dùng ARC. Và một cái hay của weak nữa là khi vùng nhớ bị giải phóng thì con trỏ được set bằng nil. Mà trong Objective C thì gửi message đến nil sẽ không vấn đề gì, app của bạn không bị crash. Điển hình nhất của việc dùng thuộc tính weak đó là cho các delegate, datasource.

Tuy nhiên vẫn còn một vài class như NSTextView, NSFont, NSColorSpace chưa hỗ trợ khai báo thuộc tính weak nên với những class này bạn có thể dùng thuộc tính unsafe_unretained thay cho weak. Thế nhưng chú ý 1 điều rằng sau khi vùng nhớ nó trỏ tới bị xoá thì con trỏ không được set la nil.

Tiếp theo là thuộc tính copy. Với việc thiết lập thuộc tính này compiller sẽ tạo ra setter như sau:

1
2
3
4
5
@interface Car: NSObject

@property (nonatomic, copy) NSString *name;

@end;
1
2
3
4
- (void)setName:(NSString *)newName {
    [_name release];
    _name = [newName copy];     // retainCount = 1
}

Như ở trên ta thấy 1 vùng nhớ mới được copy ra và _name giờ chiếm giữ 1 ownership của vùng nhớ đó. Tại sao chúng ta không dùng strong ở đây mà lại dùng copy. Giả sử ở trên chúng ta dùng thuộc tính strong và xem qua 2 ví dụ dưới đây.

1
2
3
NSString *name1 = @"Toyota";
car1.name = name1;
name1 = @"Honda";

Trong trường hợp này car1.name vẫn có giá trị là “Toyota” và name1 giờ chuyển thành “Honda”. Hoàn toàn không có vấn đề gì. Thế nhưng trong ví dụ thứ 2 dưới đây thay vì dùng NSString mà dùng subclass của nó là NSMutableString.

1
2
3
NSMutableString *name1 = @"Toyota";
car1.name = name1;
[name1 appendString:"2"];

Trong trường hợp này giá trị của car1.name là “Toyota2” mặc dù ban đầu chúng ta set là “Toyota”. Vì vậy mặc dù property name trong class Car với kiểu NSString nhưng nếu dùng strong giá trị của name vẫn có thể bị append như trên. Để tránh những trường hợp như thế ta dùng copy để mỗi lần gán sẽ copy 1 vùng nhớ mới tránh được những trường hợp như trên. Đối với những class có subclass là Mutable... thì chúng ta nên chú ý dùng thuộc tính copy. Ngoài ra block cũng phải dùng copy.

Thuộc tính cuối cùng trong nhóm này là assign thì dùng cho các property kiểu không phải là object. Tức là các kiểu dữ liệu như int, NSInteger, float,…

Với nhóm thuộc tính này thì strong là thuộc tính mặc định trong trường hợp dùng ARC, còn retain là thuộc tính mặc định trong trường hợp không dùng ARC.

Atomicity

Nhóm thuộc tính này bao gồm 2 thuộc tính là atomicnonatomic. Thuộc tính mặc định là atomic. Nhóm thuộc tính này liên quan đến vấn đề multithread. Chưa bàn đến atomic hay nonatomic, mà chúng ta cùng xem ví dụ sau:

1
2
3
4
5
6
@interface MyView {
}

@property CGPoint center;

@end

khi đấy chúng ta có setter/getter như sau:

1
2
3
4
5
6
7
- (CGPoint) center {
  return _center;
}

- (void)setCenter:(CGPoint)newCenter {
  _center = newCenter;
}

và bởi vì struct CGPoint có 2 thành phần CGFloat x, CGFloat y nên thực ra setter sẽ thực hiện các bước như sau:

1
2
3
4
- (void)setCenter:(CGPoint)newCenter {
  _center.x = newCenter.x;
  _center.y = newCenter.y;
}

Trong trường hợp chúng ta chạy multithread thì có thể xảy ra khả năng như sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// giả sủ ban đầu center của myView là (-5.f, -8.f)

// thread 1 gọi setter
[myView setCenter:CGPointMake(1.f, 2.f)];

// tiep theo bên trong setCenter sẽ chạy
_center.x = newCenter.x; // _center.x giờ có giá trị là 1.f và _center.y vẫn giữ giá trị là -8.f

// chưa kịp chạy lệnh tiếp theo để set _center.y thì ở thread 2 gọi getter
CGPoint point = [myView center];
// và getter chạy trả về (1.f, -8.f)

// thread 1 tiếp tục giá trị cho y
_center.y = newCenter.y // _center.y giờ là  2.f

Như trường hợp ở trên ta thấy giá trị center là (1.f, 2.f) nhưng tại thread 2 giá trị lấy được lại là (1.f, -8.f) dẫn đến kết quả không được như mong muốn. Vì vậy trong trường hợp multithread để tránh những tình huống như trên ta set thuộc tính atomic cho property. Khi đấy compiler sẽ sinh ra các setter/getter như sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (CGPoint) center {
  CGPoint curCenter;
  @synchronized(self) {
    curCenter = _center;
  }
  return curCenter;
}

- (void)setCenter:(CGPoint)newCenter {
  @synchronized(self) {
    _center = newCenter;
  }
}

Bên trong setter/getter sử dụng lock để tránh việc nhiều thread truy cập đồng thời. Thế nhưng việc dùng lock sẽ mất chi phí cũng như cản trở tốc độ của chương trình. Vì vậy nên trong trường hợp bạn không dùng multithread hoặc không thể xảy ra những vấn đề như trên thì bạn nên dùng thuộc tính nonatomic để tăng tốc độ cho chương trình.

Tổng kết

Bài viết này mình đã trình bày về các thuộc tính cho property, giải thích qua về các thuộc tính cũng như khi nào nên dùng thuộc tính nào. Mặc dù mình vẫn thấy còn những lập trình viên không dùng ARC nhưng có lẽ đa số mọi người đã chuyển qua dùng ARC. Thế nên thuộc tính retain có thể không cần dùng nữa. Để tìm hiểu kĩ hơn các bạn có thể đọc tại Programming With Objective C