Wednesday, April 20, 2016

(Kinh nghiệm) Cách xử lý Exception trong Objective-C có Demo

Có nhiều người hỏi mình có nên sử dụng try/catch để xử lý exception khi lập trình trên iOS không? Theo kinh nghiệm mình làm trên Objective-C được 5 năm, thì mình xin trả lời là có thể có hoặc không có cũng được, miễn sao bạn bắt được hết những trường hợp exception đó bằng câu lệnh if, để có thể không xảy ra tình trạng crash ứng dụng và chạy theo đúng yêu cầu của khách hàng là được.





Mình có biết kha khá kiến thức về Java nên có sự so sánh nhỏ như vậy. Nếu đúng hay không đúng các bạn có thể góp ý cho mình để mình nâng cao kiến thức và sửa lại tài liệu này nha.

So sánh ngôn ngữ Java và Objective-C:

-  Theo mình thấy tư tưởng khi lập trình bằng ngôn ngữ Java mọi người thấy việc bắt Exception của Java quá tốt nên xử dụng rất thường xuyên mà không cần lo âu việc gì, miễn sao mình try/catch tốt là OK. Vì thế có nhiều người đặt try/catch rất nhiều chỗ thành ra có thể bị rối khi xử lý code, dẫn đến sinh ra bug.
- Còn ngôn ngữ lập trình Objective-C thì ngược lại, nó cũng gần giống khi bạn lập trình bằng ngôn ngữ C, do đó việc bắt Exception tương đối là ít. Bạn có bắt try/catch cũng bị crash ứng dụng như thường. Do đó cách tốt nhất khi lập trình Objective-C thì bạn nên sử dụng câu lệnh if nhiều để kiểm tra tính hợp lệ của đối tượng, không thì ứng dụng của bạn có thể bị crash và chạy với dữ liệu sai. Cho nên tư tưởng khi lập trình Objective-C là nên sử dụng if để check điều kiện nhiều, nếu có dùng try/catch thì nên nhớ xử lý cho đúng (Kinh nghiệm mình thấy có mấy bạn mới làm trên iOS bắt try/catch mà không xử lý dúng thành ra phát trinh bug tiềm tàng khó chịu lắm, vì những trường hợp đó hiếm khi xảy ra để có thể test lại). Nếu bạn đọc code của những project cũ từ xưa thì rất hiếm người dùng try/catch.

Cách đây 5 năm (thời đó còn lập trình trên iPhone 3 và iOS 3.x hoặc 4.x hay sao đó) khi mình mới học lập trình trên iOS bằng ngôn ngữ Objective-C thì team của mình có những rule như sau:
- Mỗi khi xử lý 1 đối tượng trên tham số thì bạn nên kiểm tra chúng có khác nil hay không thì mới thực hiện. Vì thời điểm đó đa số vụ crash ứng dụng là do nguyên nhân sao không biết nhưng tham số đối tượng đó mà bằng nil cho dù có try/catch thì crash ứng dụng liền. 1 quy tắc bất thành văn mà lúc nào mình cũng nhớ trong đầu.
- Nên kiểm tra giá trị nil mỗi khi bạn truyền vào những kiểu dữ liệu là collection (Array, Dictionary, Set....).
- Mỗi khi lấy giá trị từ mảng (Array) thì nên kiểm tra tổng số phần tử trong mảng để có thể không bị vượt giá trị của mảng.

Hiện tại mình đang xử dụng XCode 7.2.1 và LLVM 7.0 để chứng minh lại những trường hợp trên chạy như thế nào. Mình có kiểm tra khi chạy trên 2 chế độ là Debug và Release luôn để có thể áp dụng vào thực tế khi Release ứng dụng. Lúc trước còn sử dụng XCode 6.x mình có test thử rồi nhưng chỉ demo sơ và không lưu lại trên git, lúc đo thì thấy vài trường hợp Debug thì bắt được khi dùng try/catch, nhưng chuyển sang mode Release là vẫn chết ứng dụng như thường. Mình nghĩ có thể do Apple đã sử lại compiler để dân lập trình viên khỏi do bị chết ứng dụng đột ngột như vậy. Không crash ứng dụng như có thể bị chạy sai luồng vì dữ liệu trả ra không đúng.

Mặc định khi chạy ứng dụng là chế độ Debug, các bạn có thể chuyển sang chế độ Release bằng cách chọn trên thanh toolbar: Product > Scheme > Edit Scheme, sẽ hiển thị màn hình như hình sau:
Sau đó bạn chỉnh thông số Build ConfigurationRelease và bỏ check Debug executable đi. Việc làm này cho phép bạn chạy mode Release giống như khi bạn export ra file .ipa .

Mình có 1 demo nho nhỏ để demo những trường hợp nên dùng try/catch hoặc dùng if trong Objective-C:

- Trường hợp thứ 1: Thêm giá trị nil vào Collections

Giả sử mình thêm giá trị nil vào Array, Dictionary, Set (kiểu đối tượng là Collection). Trường hợp này mình gặp bị crash ứng dụng nhiều. Những trường hợp này mình khuyên các bạn nên sử dụng try/catch để bắt. Code mình viết như sau để bắt try/catch:
Khi chạy những đoạn code này thì cả 3 trường hợp nếu bạn không bắt try/catch, chắc chắn sẽ bị crash ứng dụng. Vì thế bắt buộc phải try/catch khi thêm những đối tượng vào collection để tránh crash ứng dụng.

- Trường hợp thứ 2: Lấy giá trị nằm ngoài phạm vi của đối tượng

Giả sử mình lấy giá trị vượt khoảng hợp lệ của Array, hay lấy giá trị với key không có trong Dictionary, hay lấy thuộc tính không có của 1 object bằng hàm valueForKey. Code của mình như sau:

Trường hợp này bị crash khi bạn lấy giá trị không đúng của Array và Object. Còn kiểu Dictionary thì vẫn có thể chạy bình thường nhưng giá trị được trả về là nil, bạn nên dùng if kiểm tra để đảm bảo luồng ứng dụng chạy đúng.

- Trường hợp thứ 3: Lấy giá trị từ 1 đối tượng nil

Giả sử mình truyền giá trị là nil trên tham số của 1 phương thức nào đó rồi mình truy suất những thuộc tính bên trong đối tượng đó. Mình demo trên những đối tượng như NSArray, NSDictionary và 1 object thông thường. Code của mình như sau:
Như bạn thấy theo code này mà mình viết trên phiên bản iOS cũ chắc chắn sẽ crash ứng dụng hay rơi vào trường hợp catch của Exception. Hiện tại complier đã xử lý luôn dùm mình và luôn luôn giá trị trả về là nil.

- Trong câu giới thiệu của Apple về Swift 2 cũng đề cập đến cơ chế mới nhằm cải thiện Cocoa framework, giúp cho mọi người viết code trở nên an toàn hơn. Lời giới thiệu từ trang của Apple:

Lưu ý: Trong trường hợp này mình demo thử khi truy xuất biến public thì vẫn xảy ra tình trạng crash ứng dụng, cho dù mình có try/catch (Đoạn code mình comment lại). Vì thế các bạn nên dùng property làm biến public thay cho từ khoá @public.

- Trường hợp thứ 4: Chạy multithread

Giả sử mình chạy multithread truy suất cùng biến property là nonatomic hay atomic, có dùng từ khoá @synchronize hay không dùng. Thì vẫn không bị crash ứng dụng chỉ bị tính toán sai thôi. Code giả sử như sau:

- Trường hợp thứ 5: Chia cho số 0

Giả sử mình lấy 1 giá trị và đem chia cho 0.
- Trong Java bạn có thể dùng try/catch để bắt exception này, chi tiết bạn xem trong đây. Giải thích cơ chế của phép chia cho 0 trong Java.
- Còn bên Objective-C, bạn không thể nào handle chúng bằng exception, nó không gây ra crash ứng dụng, chỉ xử lý sai giá trị, nên có dùng if để kiểm tra tính hợp lệ của chúng. Giải thích cơ chế của phép chia cho 0 trong OS X.

Bạn có thể tải code demo của mình tại đây.


Các bạn cũng thấy những trường hợp mình demo có những trường hợp bạn bắt try/catch được nhưng có những trường hợp không thể nào bắt try/catch được thì phải dùng if để kiểm tra. Vì thế bạn nên code làm sao cho hợp lý là được.

Hiện tại mình chưa nhớ thêm trường hợp nào khác, ngoài ra khi nào mình nhớ được trường hợp nào đặc biệt nữa mình sẽ cập nhật lại tài liệu này. Các bạn có trường hợp nào khác hay hơn xin góp ý cho mình để có thể cập nhật thêm cho tài liệu phong phú. Cám ơn sự cộng tác của các bạn.

Tài liệu tham khảo:

- Phân biệt Exception và Error.
- iOS multithread.

1 comment:

  1. This comment has been removed by a blog administrator.

    ReplyDelete

Note: Only a member of this blog may post a comment.