[iOS] - iOS Dev cần biết những gì để thực hiện công việc hàng ngày?
Bài đăng này đã không được cập nhật trong 3 năm
Để công việc của một iOS dev diễn ra suôn sẻ ngày qua ngày, bài viết dưới đây liệt kê các topic mà mỗi iOS dev bình thường nhất cần phải nắm rõ. Topic bao gồm:
[Source Control | Architecture Patterns | Objective-C vs Swift | React | Dependency Manager | Storing Information | CollectionViews & TableViews | UI | Protocols | Closures | Schemes | Tests | Location | Localizable Strings]
1. Soure Control
Đầu tiên là chúc mừng, chúng ta đã được tuyển dụng vào công ty outsource, phát triển sản phẩm/ dự án hoặc maintance một hoặc nhiều sản phẩm/ dự án đã phát triển trước đó. Công việc đầu tiên trong ngày làm việc đầu tiên là phải fetch code từ repo về và làm viêc với nó.z Mỗi dự án đều luôn cần phải có Source Control, thậm chí nó vẫn cần thiết nếu chỉ có một dev phát triển nó. Hầu hết các dự án đều sử dụng GIT hoặc SVN. SVN dựa vào hệ thống tập trung để quản lý phiên bản mã nguồn. Đây được coi là kho lưu trữ tập trung, nơi mà các bản sao làm việc được tạo ra và được phép truy cập từ xa bằng cách uỷ quyền quyền truy cập thông qua một URL. Đồng thời cho phép theo dõi sự thay đổi mã nguồn bằng cách đăng ký từng file trong kho lưu trữ (repository). Phiên bản làm việc được phải là phiên bản mới nhất. Tool SVN phổ biến nhất mà dev hay dùng là bản Mac Subversion Client
GIT , ngược lại, là dựa vào hệ thống phân tán để quản lý phiên bản mã nguồn. Giả sử là bạn có một bản lưu trữ mã nguồn dưới local, là mã nguồn mà bạn đang làm việc trực tiếp, với kết nối mạng khi cần yêu cầu đồng bộ. Việc uỷ quyền truy cập là cho cả kho lưu trữ, theo dõi thay đổi mã nguồn bằng cách đăng ký nội dung, bao gồm cả repository.
2. Architecture patterns
Công việc lập trình cũng giống như việc xây nhà, đầu tiên chúng ta cần phải biết kiến trúc chúng ta định xây là gì, hoặc là sửa nhà cũng thế, cũng cần phải biết kiến trúc của nó, nên sửa chỗ nào, đập chỗ nào mà không ảnh hưởng/phá vỡ kiến trúc tổng thế của nó. Khi bắt đầu được giao việc, sau khi lấy source code về, chúng ta cần tìm hiểu xem mã nguồn dự án đang được build theo pattern nào. Trong các ứng dụng mobile phổ biến hay dùng các pattern như là MCV, MVP, MVVM, VIPER, v.v... Tôi sẽ liệt kê dưới đây tổng quan chung chung về các pattern phổ biến này.
- MVC - Tức là Model, View, Controller. Controller sẽ tạo ra "cầu nối" giữa Model và View. Mối quan hệ giữa View và Controller khá là chặt chẽ, do đó, Controller phải kết thúc xử lý khá nhiều vấn đề. Có nghĩa là, khi bạn build một view phức tạp, Controller có thể sẽ dẫn đến tình trạng mất kiểm soát vì phải xử lý quá nhiều thứ. Có nhiều cách để phá vỡ điều này, nhưng lại không tuân theo luật của MVC. Một nhược điểm khác của MVC là vấn đề kiểm thử. Nếu bạn phải thực hiện test, bạn chỉ có thể thực hiện test với Model, vì nó là phần duy nhất tách biệt với các phần còn lại. Điểm cộng của việc sử dụng MVC là nó có tính trực quan, dễ hiểu, dễ sử dụng với hầu hết các iOS dev bình thường.
- MVVM - Tức là Model, View, ViewModel. Kỹ thuật binding (cơ chế ràng buộc data - ý tưởng cơ bản trong lập trình reactive) sẽ được thiết lập giữa View và ViewModel, nó cho phép ViewModel thực thi thay đổi tới Model, sau đó lại update lại ViewModel, và tự động update lên View theo cơ chế bindings. Thành phần ViewModel sẽ không hay biết gì về View. Điều này là điểm cộng khi thực hiện Unit-test và cơ chế binding nhằm giảm thiểu khá nhiều dòng code. Bài viết dưới đây mô tả khả kỹ về các pattern phổ biến, bạn có thể tham khảo thêm: iOS Architecture Patterns Việc cấu trúc và tổ chức code tốt giúp dev đỡ đau đầu, đỡ mất thời gian debug để tìm luồng xử lý. Một lỗi lớn của dev là commit một vài đoạn code để cố hoàn thành kết quả nhìn thấy được mà quên đi phải tổ chức code theo pattern ban đầu đã chỉ định. Có câu nói khá hay của Benjamin Franklin:
For every minute spent organising, an hour is earned. — Benjamin Franklin
(Từ giờ bạn nên cố gắng trau chuốt code theo pattern để tránh cho dev maintain đến sau đỡ cực)
3. Objective-C hay Swift?
Khi mà bạn phải quyết định lựa chọn ngôn ngữ nào để build ứng dụng, bạn cần phải biết mỗi ngôn ngữ thì có ưu nhược điểm gì? Theo ý kién cá nhân, tôi muốn dùng Swift hơn cả. Tại sao vậy? Thực lòng mà nói, Objective-C có nhiều ưu điểm hơn Swift. Hầu hết các bài viết hướng dẫn hay ví dụ đều viết bằng Objective-C , và cũng có nâng cấp, viết lại cho Swift, điều chỉnh lại cho phù hợp với các mô hình thiết kế mới. Tuy nhiên, trong thời gian tới, tôi nghĩ là sẽ có nhiều bài viết, sample code bằng Swift hơn Swift là ngôn ngữ có nhiều cải tiến hơn hẳn, là ngôn ngữ hiện đại, dễ đọc, ngắn gọn và trong sáng, nó gần với ngôn ngữ quốc tế hơn ( english), bởi vì nó ko được build từ C, nó đã lược bỏ bớt đi một số cú pháp kế thừa. Swift cũng khá dễ để maintain, chỉ cần 1 file .swift, không cần đến .h và .m như bên Objective-C. Bởi vì XCode và bộ biên dịch LLVM có thể tìm ra các dependency và thực hiện tăng tốc độ build một cách tự động. Tổng quát lại, bạn không cần tốn qua nhiều công sức để viết nhiều code mà vẫn đạt được kết quả tương đương với ít code hơn, tất nhiên là với Swift. Swift nhanh hơn, an toàn hơn, kiểm soát tốt vấn đề quản lý bộ nhớ hơn. Bạn biết điều gì sẽ xảy ra trong Objective-C khi bạn gọi một method mà chưa khởi tạo biến con trỏ? Ko sao cả, một đoạn mã sẽ được bỏ qua. Nghe có vẻ ok đấy, nó ko bị crash app, tuy nhiên nó sẽ dẫn đến khá nhiều bug và dẫn đến nhiều hành vi thất thường. Swift xử lý vấn đề này bằng cách đưa ra khái niệm Optional. Không chỉ là bạn có một ý tưởng tốt hơn là cái gì đó có thể là nil và đảm bảo rằng nó được đặt vào vùng nào đó được ngăn chặn khi nil được sử dụng, nhưng nếu nil optional được sử dụng, Swift sẽ trigger được crash, tạo điều kiện cho dev gỡ lỗi. ARC hoạt động hiệu quả hơn trong Swift. Với Objective-C, ARC không hoạt đọng được với các hàm C và APIs như kiểu Core Graphic, đôi lúc chúng ta phải release thủ công.
4 . To React or not to React?
Functional Reactive Program (FRP), như một kiểu thời trang lập trình mới nổi. Ý tưởng của nó là cho phép dễ dàng kết hợp operation bất đồng bộ với luồng event/data. Với Swift, nó là một tính trừu tượng kiểu generic của một cách thể hiện tính toán thông qua giao diện kiểu Observable<Element>.
Cách giải thích dễ hiểu nhất chúng ta sẽ xem một đoạn code. Chẳng hạn A, và B, muốn mua một game console mới. A lấy 5$ từ bố của A hàng tuần, B cũng được bố cho 5$ như thế. Tuy nhiên, B kiểm thêm được 5$ bằng việc đi bán báo dạo hàng tuần. Nếu như tuần nào cũng có tiền như vậy, tuần nào cũng check xem đã đủ tiền để mua được game hay chưa? Bằng cách tạo 1 var để "lắng nghe (hoăc là "đăng ký") sự thay đổi số tiền và A, B có được. Khi giá trị đó đủ để mua game thì sẽ hiển thị message Về code thì như thế này:
// Savings
let timmySavings = Variable(5)
let jennySavings = Variable(10)
var isConsoleAttainable =
Observable
.combineLatest(timmy.asObservable(), jenny.asObservable()) { $0 + $1 }
.filter { $0 >= 300 }
.map { "\($0) is enough for the gaming console!" }
// Week 2
timmySavings.value = 10
jennySavings.value = 20
isConsoleAttainable
.subscribe(onNext: { print($0) }) // Doesn't print anything
// Week 20
timmySavings.value = 100
jennySavings.value = 200
isConsoleAttainable
.subscribe(onNext: { print($0) }) // 300 is enough for the gaming console!
Các framework phổ biến về FRP RxSwift ReactiveCocoa
5. Dependency Manager
CocoaPods và Carthage là một trong những dependency manager phổ biến (dùng để quản lý phiên bản library) mà dev hay dùng cho các dự án Swift và Objective-C. CocoaPods lưu trữ lượng lớn các libraries, quản lý giúp chúng ta các phiên bản được cập nhật/release liên tục. Cách cài đặt đơn giản với Ruby sử dụng command line như sau:
$ sudo gem install cocoapods
Sau khi cài đặt xong, chúng ta cần tạo file Podfile cho dự án:
$ pod init
Sau đó thì tạo nội dung podfile với cấu trúc như sau, để lấy library về:
target 'Projecct' do
use_frameworks!
pod 'Alamofire', "4.4.0"
pod 'FacebookCore', "0.2.0"
pod 'FacebookShare', "0.2.0"
pod 'Firebase/Core', ">=3.6"
pod 'FirebaseCrash', "1.1.6"
pod 'Firebase/Messaging', "3.16.0"
pod 'GoogleAnalytics', "3.17.0"
end
Sau đó chạy:
$ pod install
Sau đó, mở file project’s .xcworkspace và import các dependeny vào.
Còn với Carthage, là một dependency manager kiểu phân tán, nó ngược với CocoaPod , là kiểu tập trung. Nhược điểm của Carthage là chúng ta phải thêm các framework bằng tay các thứ, còn ưu điểm thì tốc độ build project nhanh hơn. Tham khảo cách dùng ở bài viết này: https://viblo.asia/p/su-dung-carthage-de-rut-ngan-thoi-gian-build-xcode-projects-EoDkQOpNGbV
6. Storing information
Có nhiều cách để lưu giữ data trong ứng dụng của dev. Phổ biến nhất là NSUserDefaults, cho phép lưu trữ các user data mặc đinh, được sử dụng khi app load lần đầu. Các type được phép lưu trữ bao gồm:
- NSData
- NSDate
- NSNumber
- NSDictionary
- NSString
- NSArray Để tương thích với Swift, NSNumber được chấp nhận với các kiểu data sau:
- UInt
- Int
- Float
- Double
- Bool
Các Object có thể lưu vào NSUserDefault theo cách sau (tất nhiên trước hết cần tạo constant để keep các key cho các object được save)
let keyConstant = "objectKey"
let defaults = NSUserDefaults.standardsUserDefaults()
defaults.setObject("Object to save", objectKey: keyConstant)
Để đọc object từ NSUserDefault, chúng ta cần :
if let name = defaults.stringForKey(keyConstant) {
print(name)
}
Tiếp theo, chúng ta tìm hiểu về Keychain, là hệ thống quản lý password, chứa password, các chứng chỉ xác thực (certififcate), private key hoặc là private notes, tóm lại là các thông tin nhạy cảm. Keychain có 2 mức mã hoá device (device encryption). Mức đầu tiên sử dụng passcode của màn hình khoán như là mã hoá key (encryption key). Mức thứ 2 sử dụng một key được phát hành và lưu trữ trên device. Điều này không thực sự an toàn, đặc biệt khi bạn không sử dụng passcode ở màn hình lock. Có nhiều cách để truy cập vào key được sử dụng ở mức 2, vì nó được lưu trên device. Do vậy, cách tốt nhất là bạn không nên lưu trữ key trên device. CoreData, là framework được thiết kế/cung cấp với chính chủ Apple, dùng cho ứng dụng của bạn trong việc kết nối với database. Nó khá là đơn giản khi xử lý, giảm thiểu mã hoặc lược bỏ đi khi cần phải test. Nên dùng CoreData khi mà app của bạn có các thông tin của dữ liệu thường xuyên được truy cập và gần như không có thay đổi trong tương lai. Nó cung cấp các solution để đơn giản hoá quá trình xử lý, truy cập data, mà bạn không cần phải tự xây dựng lại các comunication với DB hoặc test lại.
7.CollectionView và TableView
Để hiển thị dữ liệu, hầu hết các app đều sử dụng hoặc collection view hoặc table view. Biết chúng hoạt động ra sao, khi nào sử dụng cái này, không sử dụng cái kia sẽ tránh được những thay đổi phức tạp tới app của bạn trong tương lai. TableView hiển thị các item dưới dạng list, trong một cột đơn, và giới hạn scroll theo trục dọc. Mỗi item đươc thể hiện dưới dạng UITableViewCell, hoàn toàn có thể custom theo ý muốn. Chúng được lưu trữ trong các section và các row. CollectionView cũng hiển thị các item dưới dạng list, tuy nhiên, chúng có thể có nhiều cột và hàng, ví dụ dưới dạng lưới chẳng hạn. Chúng có thể scroll theo trục dọc hoặc trục ngang, mỗi item được biểu diễn bởi UICollectionViewCell. Cũng tương tự như UITableViewCell, các cell này có thể tuỳ biến được, trong sắp xếp trong các section và row tương ứng. Cả 2 loại view phổ biến trên đều có chức năng tương tự nhau, đểu tái sử dụng cell để nâng cao độ mượt của ứng dụng. Lựa chọn cái nào bạn cần phải dựa vào mức độ phức tạp của list item bạn có. Lựa chọn phổ biến là hay sử dụng UICollectionView vì tính đa dạng của nó. Ví dụ, bạn muốn hiện thị một danh sách danh bạ chẳng hạn, với đơn giản là 1 column thôi, đơn giản chúng ta sẽ lựa chọn UITableView. Mọi thứ vẫn bình thường, nhưng sau một thời gian thì Designer muốn thay đổi danh bạ nên được hiện thị dưới dạng grid thì tốt hơn, thay vì dạng list. Để làm được điều này, bạn phải chuyển đổi code từ UITableView sang UICollectionView. Cũng khá bất tiện, việc thay đổi code đâu có dễ như thay đổi design? Như vậy, tốt nhất ngay từ đầu nên chọn UICollectionView, dễ cho việc tuỳ biến sau này.
8. Storyboad vs Xibs vs Programatic UI
Các phương pháp trên có thể được sử dụng độc lập với nhau để tạo UI, tuy nhiên, không có điều gì ngăn cản bạn kết hợp chúng lại với nhau. Storyboads cho phép một chúng ta quan sát một cái nhìn toàn diện về project, dưới dạng các screen và mối quan hệ giữa chúng, đây là điều mà designer rất thích, bởi vì họ có thể quan sát toàn bộ flow tất cả các màn hình. Nhưng điểm trừ là khi càng thêm vài màn hình mới vào, các kết nối giữa các màn hình càng trở nên phức tạp và khá là rối, đôi khi nó còn ảnh hưởng đến việc thời gian build ứng dụng, vì phải loading khá là lâu file storyboard. Một trong những issue làm đau đầu dev là công việc xử lý conflict khi phải merge các file storyboard như thế này. Quá nhiều UI phải phụ thuộc vào storyboard, mất khá nhiều thời gian khi phải sửa chữa lỗi merge, làm lại có khi còn nhanh hơn. Xib , đơn giản hơn, chung cấp một view cận cảnh, rõ ràng hơn của từng màn hình hoặc từng khu vực của màn hình. Lợi thế của nó là rất dễ để tái sử dụng, khi merge thì không gặp nhiều khó khăn, vì nó được tách bạch rõ cho từng màn hình. Programming, tức là tự code lấy UI, chúng ta có nắm được cụ thể từng vị trí, từng component của view, tốn ít sức merge khi xảy ra conflict nhất. Nhược điểm của nó là đôi khi quá chi tiết, mất thời gian kiểm soát từng dòng code một.
Có một vài phương pháp tiếp cận khác nhau để tạo ra UI. Lời khuyên đưa ra là nên kết hợp cả 3 phương pháp. Sử dụng Storyboard để control flow màn hình, bằng cách nhóm từng màn hình trong từng storyboard, còn với Xib, sử dụng cho các màn hình cụ thể hơn, mà nó không phải là màn hình chính, và cuối cùng sử dụng programing để xử lý các control của từng vị trí cụ thể, (chẳng hạn như la thay đổi state label, button các thứ).
9. Protocols
Protocols tồn tại trong cuộc sống hàng ngày của chúng ta để chắc chắn rằng, trong tình huống nhất định nào đó, chúng ta biết phải phản ứng như nào. Ví dụ, bạn là một người lính, trong một tình huống khẩn cấp, tất cả các lính cứu hoả phải đảm bảo đáp ứng được nguyên tắc là tất cả các thiết bị luôn trong trạng thái sẵn sàng phản hồi/xử lý khi có đám cháy xảy ra. Quay lại protocol trong Objective-C/Swift, các protocol định nghĩa sẵn một list các method, thuộc tính hoặc yêu cầu gì đo, mang tính chất giả định thôi, cho các chưc năng nhất định nào đó. Nó có thể được thực thi ở class, structure hoặc một enumeration nào đó, và nó sẽ được thực hiện cụ thể trong requirement nào đó. Ví dụ dưới đây mô tả một protocol được tạo ra và sử dụng như thế nào. Tôi cần phải tạo ra một enum các nguyên - vật liệu khác nhau có thể phải được sử dụng trong việc giải quyết một đám cháy.
enum ExtinguisherType: String {
case water, foam, sand
}
Tiếp theo, tôi sẽ tạo một protocol để phản hồi khi có đám cháy thực sự xảy ra.
protocol RespondEmergencyProtocol {
func putOutFire(with material: ExtinguisherType)
}
Tiếp, tôi sẽ tạo một fireman, để conform cái protocol kia:
class Fireman: RespondEmergencyProtocol {
func putOutFire(with material: ExtinguisherType) {
print("Fire was put out using \(material.rawValue).")
}
}
Xong, hãy xem khi có đám cháy xảy ra, fireman sẽ phải làm gì?
var fireman: Fireman = Fireman()
fireman.putOutFire(with: .foam)
Kết quả là, “Fire was put out using foam.”! Protocol đươc sử dụng phổ biến trong pattern Delegation (Uỷ nhiệm). Nó cho phép một class hay struct "uỷ nhiệm" một chức năng nhất định cho một "thể hiện" của type khác. Một protocol được tạo ra, chịu trách nhiệm thực thị quyền uỷ nhiệm đó, đảm bảo các chức năng function được đáp ứng đầy đủ. Ví dụ nhanh như sau:
protocol FireStationDelegate {
func handleEmergency()
}
Một firestation uỷ nhiệm một hành động xử lý tình huống khẩn cấp cho fireman.
class FireStation {
var delegate: FireStationDelegate?
fun emergencyCallReceived() {
delegate?.handleEmergency()
}
}
Điều này có nghĩa là fireman sẽ phải conform cái protocol FireStationDelegate ngay khi xảy ra sự kiện.
class Fireman: RespondEmergencyProtocol, FireStationDelegate {
func putOutFire(with material: ExtinguisherType) {
print("Fire was put out using \(material.rawValue).")
}
func handleEmergency() {
putOutFire(with: .water)
}
}
Fireman sẽ thực hiện các action mà fire station "uỷ nhiệm" cho, anh ta sẽ xử lý khi nhận được cuộc gọi khẩn cấp khi sự kiện xảy ra
let firestation: FireStation = FireStation()
firestation.delegate = fireman
firestation.emergencyCallReceived()
Kết quả là, “Fire was put out using water.”
10. Closures
Phần này chỉ tập trung vào closure trong Swift. Hầu hết trong các trường hợp sử dụng closure trả về một completion blocks hoặc là một high order functions. Completion blocks là một block of code, được trả về khi task đã kết thúc. Closure cũng tương tự như block trong C và Objective-C Các cấu trúc về block hay dùng liệt kê ở đây Swift block syntax Đôi khi chúng ta quên mất syntax khai báo với cả cách sử dụng, chúng ta có thể reference link trên để apply vào cái chúng ta cần. Đơn giản thôi.
11. Schemes
Đơn giản là, scheme là cách dễ nhất để chuyển đổi các configuration. Một workspace bao gồm các phần liên quan đến nhau của project. Một project có thể có nhiều target khác nhau, target có các configuration riêng biệt để build cho product. Scheme trong Xcode định nghĩa các configuration dùng để build hay để test tuỳ vào mục đích.
12. Tests
Nếu bạn dành thời gian test app của bạn, bạn phải chọn phương án test đúng ngay từ đầu. Nếu ko thế, bạn sẽ không thể bảo đảm tránh được các bug đơn giản, tránh được các issue tiềm ẩn Trước hết, cùng phân tích ưu nhược điểm của Unitest Điểm bất lợi là gì?
- Thời gian phát triển sẽ kéo dài hơn
- Tăng lượng code
Còn điểm lợi thì sao?
- Tập trung vào việc phải code theo module ( như thế dễ test hơn)
- Rõ ràng là, các nhiều bug phát hiện trước khi release thì tốt hơn
- Dễ maintain hơn
Kết hợp với Instruments, bạn sẽ có đầy đủ tool để chắc chắn rằng app sẽ ngon lành, không có bug và cũng không crash Có một vài instrument bạn có thể sử dụng để kiểm tra xem điều gì đang diễn ra trong khi app của bạn đang hoạt động. Một số chức năng được sử dụng trong Instrument bao gồm như là kiểm tra rò rì memory, profile timer, quản lý cấp phát bộ nhớ (memory allocation)
13. Location
Khá nhiều app có tính năng yêu cầu chia sẻ vị trí người dùng. Vậy location hoạt động như nào trong ứng dụng iOS nhỉ? Core Location là framework cho phép bạn truy cập vào tất cả các thứ bạn cần
Core Location là framework cho phép bạn xác định được vị trí hiện tại hoặc liên kết gần đó qua device. Framework này sử dụng phần cứng để xác định vị trí của user và các liên kết gần đó. Bạn sử dụng các class và protocol được cung cấp sẵn trong framework để cấu hình và đặt lịch cho phép phát tín hiệu vị trí và các sự kiện gần đó. Bạn cũng có thể sử dụng nó để định nghĩa vị trí địa lý vùng và giám sát người dùng khi họ vượt qua ranh giới vùng mà bạn định nghĩa. Trong iOS, bạn cũng có thể định nghĩa khu vực xung quanh bằng thiết bị bluetooth, gọi là iBeacon.
Khá là hay đúng không? Bạn có thể tham khảo tài liệu Apple hướng dẫn chi tiết mẫu về location service này
14. Localizable Strings
Điều này có ý nghĩa là cho phép app của bạn sự dụng được đa ngôn ngữ, theo các khu vực khác nhau. Nếu như bạn đã chuẩn bị sẵn các text như là dữ liệu sử dụng chuỗi đa ngôn ngữ, tất cả các thứ bạn cần là phải thêm bản dịch cho các text đó và đưa vào file Localizable.strings. Để lấy ra text đúng cho từng ngôn ngữ, bạn cần sử dụng NSLocalizedString:
NSLocalizedString(key:, comment:)
Cấu trúc sample một Localizable string như sau:
{
"APP_NAME" = "MyApp"
"LOGIN_LBL" = "Login"
...
}
Trên đây là liệt kê nho nhỏ các thứ mà dev trình độ lèm bèm cần phải biết, ngoài ra còn rất nhiều thứ basic khác nữa, chúng ta sẽ tìm hiểu vào lúc khác. Chúc các bạn vui vẻ.
Nguồn: https://swiftsailing.net/14-must-knows-for-an-ios-developer-5ae502d7d87f
All rights reserved