Làm việc với Model và Dictionary trong ObjC và Swift (phần 2)
Ở bài Part 1 lần trước mình đã giới thiệu về Model và Dictionary trong ObjC. Mình có nêu ra một số ý tưởng về Model như:
- Model tự init data dựa trên JSON mà không phải tự viết hàm initWithDictionary cho mỗi model mới khai báo dựa trên thư viện objc-runtime.
- Model cho phép add các target cũng như cài đặt callback để tự động trigger events mỗi khi một thuộc tính của Model thay đổi giá trị bằng cách viết thư viện key-value observing. Vấn đề ở đây là nó sẽ không crash và tối ưu hoá hơn khi sử dụng cái mặc định của Apple.
- Model lấy ý tưởng tương tự như một ActiveRecord trong Ruby on Rails.
Hôm nay mình sẽ nói rõ hơn làm thế nào để làm được điều này bằng thư viện objc-runtime.
Model tự init data dựa trên JSON (Dictionary)
Để init data được cho một model tao có 2 cách tiếp cận:
- Cách dễ nhất là lặp qua hết các keys của Dictionary và lấy value sau đó set cho model.
- Biết được model đó có bao nhiêu thuộc tính cũng như kiểu dữ liệu của từng thuộc tính rồi tiến hành lấy dữ liệu tương ứng từ Dictionary để set cho từng thuộc tính.
Cách tiếp cận ban đầu có lợi thế là dễ làm nhưng nó dễ gặp nhiều vấn đề như: lặp qua các key dư thừa không có trong model (giả sử dictionary có 5 key nhưng model chỉ có 3 thuộc tính). Không khớp kiểu dữ liệu giữa dictinonary và model (giả sử price trong dictionary là kiểu int trong khi của model là kiểu float). Cụ thể ta có ví dụ như sau:
Dictionary
1
2
3
4
5
6
7
8
9
|
{
name : "Huy" ,
age : 23 ,
city : "Saigon" ,
country : "Vietnam" ,
bio : "Milk Carrot"
}
|
Model
1
2
3
4
5
6
7
|
{
name : String ,
age : float ,
bio : String
}
|
Ta thấy giả sử nếu viết một hàm init data mà lặp qua kết các keys của Dictionary để set giá trị cho model thì sẽ dư thừa key city, country
cho mỗi lần init data. Hơn nữa kiểu dữ liệu về age sẽ không khớp (không thể xác định được age trong dictionary là kiểu int hay kiểu float). Việc lặp qua hết các keys của Dictionary để set value cho thuộc tính model còn gặp một vấn đề nữa là nếu không handle exception thì chương trình sẽ bị crash khi key đó không có trong model ví dụ key city
và country
Với cách tiếp cận thứ hai là căn cứ vào thuộc tính của model để set giá trị có vẻ như là tốt hơn. Nhưng vấn đề là làm thế nào để lấy được danh sách thuộc tính cũng như kiểu dữ liệu của từng thuộc tính. Sử dụng thư viện objc-runtime. Các bước như sau:
Lấy danh sách thuộc tính -> lấy danh sách kiểu dữ liệu -> Lấy giá trị ứng với thuộc tính trong Dictionary -> Căn cứ vào kiểu dữ liệu ứng với thuộc tính tiến hành init dữ liệu cho thuộc tính.
- Lấy danh sách thuộc tính:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# hàm lấy danh sách thuộc tính của một class trong objc-runtime
objc_property_t * properties = class_copyPropertyList ( [ self class ] , & outCount ) ;
# lặp qua lần lượt các thuộc tính để lấy ra kiểu dữ liệu
for ( int i = 0 ; i < outCount ; i ++ ) {
objc_property_t property = properties [ i ] ;
objc_property_t property = properties [ i ] ;
const char * propName = property_getName ( property ) ;
. . . .
}
|
- Sau khi lấy ra được thuộc tính và kiểu dữ liệu ta có thể tiến hành lấy giá trị và set giá trị cho model.
1
2
3
4
5
6
7
|
# lấy giá trị ứng với thuộc tính trong Dictionary
id value = [ dictionary valueForKey : propertyName ] ;
# gán giá trị cho thuộc tính đó trong model.
[ self setValue : value forKey : propertyName ] ;
|
Toàn bộ source code bạn có thể xem ở đây: model
Tạo property reaction khi property thay đổi giá trị
Đôi khi trong lập trình, để dễ dàng hơn, ta muốn khi giá trị một thuộc tính của model thay đổi thì nó sẽ tiến hành gọi một hàm nào đó (callback). Để làm được điều này, chúng ta sử dụng tính năng key-value observing
của ObjC cung cấp. Giả sử ta gọi hàm callback đó là một Action.
- Tạo Object class cho Action:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@ interface SModelAction : NSObject
// Đặc tả tên thuộc tính của model mà có sự thay đổi
@ property ( nonatomic , copy ) NSString * keyPath ;
// Đặc tả sự thay đổi (add/remove/init/changed...)
@ property ( nonatomic ) SModelEvent event ;
// Con trỏ của objective gọi hàm callback
@ property ( nonatomic , weak ) id target ;
// Hàm callback sẽ được target gọi
@ property ( nonatomic ) SEL selector ;
@ end
|
- Tiến hành đăng kí một event cho một thuộc tính của model
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
- ( void ) property : ( NSString * ) property
target : ( id ) target
selector : ( SEL ) selector
onEvent : ( SModelEvent ) event {
// Kiểm tra xem action đã được đăng kí trước hay chưa, nếu đã đăng kí rồi thì báo trùng lập và không làm gì cả
if ( [ [ self getActionsOfProperty : property
target : target
selector : selector
onEvent : event ] count ] > 0 ) {
#ifdef DEBUG
NSLog ( @ "Duplicated register keyPath: %@" , property ) ;
#endif
return ;
}
// Đăng kí observer cho thuộc tính nếu chưa đăng ký
[ self registerObserverForKeyPath : property ] ;
// Tạo một đối tượng mô tả event và lưu lại
SModelAction * modelAction = [ [ SModelAction alloc ] init ] ;
modelAction . keyPath = property ;
modelAction . target = target ;
modelAction . selector = selector ;
modelAction . event = event ;
[ [ self actions ] addObject : modelAction ] ;
}
|
- Hàm đăng ký key-observer
1
2
3
4
5
6
7
8
9
10
11
12
13
|
- ( void ) registerObserverForKeyPath : ( NSString * ) keyPath {
if ( ! [ self keyPathExisted : keyPath ] ) {
@ synchronized ( self ) {
[ self addKeyPath : keyPath ] ;
[ self addObserver : self
forKeyPath : keyPath
options : ( NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew )
context : SPreadContext ] ;
}
}
}
|
- Cài đặt hàm thực thi khi nhận được notification
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
- ( void ) observeValueForKeyPath : ( NSString * ) keyPath
ofObject : ( id ) object
change : ( NSDictionary * ) change
context : ( void * ) context {
id oldValue = change [ @ "old" ] ;
id newValue = change [ @ "new" ] ;
SModelEvent event = SModelEventOnChange ;
NSMutableArray * actionsToDelete = [ NSMutableArray
NSArray * actions = [ self getActionsOfProperty : keyPath
array ] ;
onEvent : event ] ;
[ [ NSOperationQueue mainQueue ] addOperationWithBlock : ^ {
for ( SModelAction * action in actions ) {
id target = action . target ;
if ( target ) {
( ( void ( * ) ( id , SEL ) ) [ target methodForSelector : action . selector ] ) ( target ,
action . selector ) ;
} else {
[ actionsToDelete addObject : action ] ;
}
}
for ( SModelAction * action in actionsToDelete ) {
[ self removeActionsForProperty : action . keyPath
target : action . target ] ;
}
} ] ;
}
|
- Khi giá trị của model bị thay đổi giả sử trong đoạn code sau:
1
2
3
4
5
6
7
|
User * user = [ [ User init ] alloc ] ;
[ user property : "name" target : self selector : @ selector ( renderNameLabel ) onEvent : SModelEventOnChange ] ;
// Gán giá trị mới cho field name
user . name = "New name" ;
|
Sau dòng lệnh gán giá trị mới cho thuộc tính name trong model user, thì model user sẽ tiến hành gọi hàm renderNameLabel. Model cung cấp một cơ chế tự động remove key-observer nên khi sử dụng chỉ cần add target, không cần quan tâm tới việc remove key-observer khi dealloc model. Điều này giảm thiểu việc crash khi lập trình.
Toàn bộ source code bạn có thể xem ở đây
Model lấy ý tưởng tương tự như một ActiveRecord trong Ruby on Rails
Trong model ở đây, mình chỉ lấy ý tưởng từ phần active record về việc auto mapping giữa tên, kiểu dữ liệu của thuộc tính với dữ liệu trong JSON (Dictionary). Trong khi làm việc với các app có lấy dữ liệu từ mạng về, các bạn rất hay gặp trường hợp cần tải dữ liệu của một đối tượng về mà chỉ biết id của đối tượng đó. Ví dụ khởi tạo đối tượng User có id bằng 1.
1
2
3
|
User * user = User . findById ( 1 )
|
Hoặc bất đồng bộ
1
2
3
4
5
|
User * user = User ( )
user . id = 1
user . fetchInBackgroud ( )
|
Các công việc cần làm là ta sử dụng một private networking cho class model để tiến hành lấy dữ liệu từ server và kết hợp với phần ở trên để init dữ liệu. Với ý tưởng này, ta có thể đóng gói được phần code làm việc với server gói gọn chỉ trong phần model base mà không cần phải viết lại cho mỗi lần tạo thêm model mới. Từ đó lượng code sinh ra ít hơn và dễ quản lý hơn.
Trong phần model (mình đã update thành opensource) mình đã hiện thực tất cả các phần ở trên, việc sử dụng cũng cực kì đơn giản, chỉ việc kết thừa từ class SModel là có tất cả các tiện ích kể trên. Đồng thời SModel cũng cung cấp thêm nhiều hàm tiện ích khác như mặc định gái trị default cho Model khi giá trị tron Dictionary bị null, convert ngược lại từ class model thành Dictionary cho việc gửi parameters hoặc lưu thành file JSON. Cung cấp hàm callback bằng block, thêm hàm callback khi fetchInBackground.
- Google Play đã cho phép bán ứng dụng Việt
- Từ con số 0 anh chàng này đã học code để trở thành kỹ sư phần mềm trong vòng 12 tháng như thế nào?
- tài liệu cấu hình webservice ksoap trên Magento
- Những cơ sở học liệu miễn phí và uy tín bạn đã biết chưa?
- Thiết kế trải nghiệm người dùng (UX) tốt thì kinh doanh tốt?
- Xuất bản ứng dụng trong Java
- Vì sao lập trình viên nữ ngày càng vắng bóng?
- Những trang tạo web miễn phí, thiết kế web miễn phí, xây dựng website miễn phí
- Gọi điện lừa đảo, yêu cầu chủ website đăng ký với Bộ Công Thương
- Xây dựng hệ thống tiêu chuẩn, quy chuẩn trong giao thông thông minh tại Việt Nam
- Bạn có chậm chân trước Bước nhảy công nghệ và làm giảm khả năng cạnh tranh?
- 5 lỗi bảo mật cơ bản trong Rails app hay gặp trong thực tế
DVMS chuyên:
- Tư vấn, xây dựng, chuyển giao công nghệ Blockchain, mạng xã hội,...
- Tư vấn ứng dụng cho smartphone và máy tính bảng, tư vấn ứng dụng vận tải thông minh, thực tế ảo, game mobile,...
- Tư vấn các hệ thống theo mô hình kinh tế chia sẻ như Uber, Grab, ứng dụng giúp việc,...
- Xây dựng các giải pháp quản lý vận tải, quản lý xe công vụ, quản lý xe doanh nghiệp, phần mềm và ứng dụng logistics, kho vận, vé xe điện tử,...
- Tư vấn và xây dựng mạng xã hội, tư vấn giải pháp CNTT cho doanh nghiệp, startup,...
Vì sao chọn DVMS?
- DVMS nắm vững nhiều công nghệ phần mềm, mạng và viễn thông. Như Payment gateway, SMS gateway, GIS, VOIP, iOS, Android, Blackberry, Windows Phone, cloud computing,…
- DVMS có kinh nghiệm triển khai các hệ thống trên các nền tảng điện toán đám mây nổi tiếng như Google, Amazon, Microsoft,…
- DVMS có kinh nghiệm thực tế tư vấn, xây dựng, triển khai, chuyển giao, gia công các giải pháp phần mềm cho khách hàng Việt Nam, USA, Singapore, Germany, France, các tập đoàn của nước ngoài tại Việt Nam,…
Quý khách xem Hồ sơ năng lực của DVMS tại đây >>
Quý khách gửi yêu cầu tư vấn và báo giá tại đây >>