Cũng giống như GCD, Operations giúp chúng ta có thể thực hiện các task đa luồng. Vì Operations là API bậc cao hơn GCD, do đó, Operations cung cấp nhiều tính năng hơn so với GCD như cung cấp các state để quản lí task, dependency giữa các task,… nhưng cũng vì thế mà để khơi tạo và sử dụng operation sẽ khó hơn so với GCD.
Nếu bạn chưa hiểu về GCD, hãy đọc bài về GCD của mình tại:
Ở phần 1 của bài viết Operation, mình sẽ giới thiệu về tính chất và các thuộc tính của Operation và Operation queue.
Nội dung bài viết
Operation State
Block Operation
Subclass Operation
Operation Queue
Operation States
Operation cũng có công dụng như là 1 DispatchWorkItem vậy. Chúng đều là 1 task, bạn khai báo thân hàm cho chúng và đưa chúng vào queue để thực hiện.
Tuy nhiên, điểm vượt trội của Operation so với DispatchWorkItem là mỗi Operation đều có State của riêng mình, còn workItem thì không.
isReady: sẵn sàng để được thực hiện
isExecuting: đang được thực hiện
isCancelled: Nếu bạn gọi phương thức cancel(), operation sẽ chuyển qua state isCancelled trước khi chuyển sang state isFinished.
isFinished: Nếu operation k bị cancel, nó sẽ chuyển từ state isExecuting sang isFinished khi hoàn thành.
Note:
Các state của Operation là các thuộc tính read-only.
Bạn có thể gọi cancel() để hủy 1 operation, nhưng tương tự như workItem, chúng chỉ có thể bị cancel trước khi được thực hiện. Tuy nhiên, do Operation có state, nên bạn có thể kiểm tra state trong thân hàm để cancel hàm. Mình sẽ làm rõ hơn ở phần sau.
Block Operation
Có thể tạo 1 operation 1 cách nhanh chóng bằng cách sử dụng 1 block operation:
Gọi hàm start() để thực hiện BlockOperation
Bạn cũng có thể add thêm nhiều closures vào block operation:
Block Operation hoạt động như một DispatchGroup. Nếu bạn cung cấp 1 completionBlock closure cho blockOperation, thì nó sẽ được thực hiên khi tất cả các closure được add vào block operation đã được thực hiện hết (tương tự hàm notify của DispatchGroup).
Kết quả thu được trên màn hình console:
Note: Từ kết quả thu được ở trên, ta có thể dễ dàng kết luận:
Task trong block operation chạy concurrent.
Operation Blocks chạy default trên global queue.
Subclass Operation
BlockOperation rất dễ sử dụng và thích hợp cho các task đơn giản. Tuy nhiên, đối với những task phức tạp cần kiểm soát state, hoặc những task sử dụng nhiều lần, thì bạn nên tự tạo 1 Operation để sử dụng.
Bạn phải override lại hàm main(), đây là hàm được thực hiện khi operation đó bắt đầu thực hiện.
Gọi start() để chạy. Tuy nhiên, lưu í rằng, khi gọi start() 1 cách trực tiếp trên 1 operation, Operation đó sẽ chạy theo kiểu sync trên current thread. -> Không nên gọi start trên main thread.
Note: Không chỉ việc gọi start sẽ block current thread, mà nó còn dễ dẫn tới 1 exception rằng task đó chưa sẵn sàng để thực hiện. Vì vậy bạn không nên gọi start ở 1 operation.
Vậy làm cách nào để có thể start 1 operation ?
Câu trả lời cho việc này là tương tự như DispatchWorkItem, chúng ta sẽ đưa operation vào Operation Queue để chạy.
Operation Queue:
Bạn đưa các task vào trong operation queue, operation queue sẽ tự động thực hiện task khi thích hợp mà bạn không cần phải gọi start.
Operation Queue chỉ thực hiện task khi task đó đang ở state isReady.
Khi bạn add 1 operation vào 1 queue, task đó sẽ chạy cho đến khi hoàn thành hoặc bị hủy (cancel).
Không thể add 1 operation vào nhiều operation queues khác nhau.
Block queue:
Operation queue có 1 thuộc tính là waitUntilAllOperationsAreFinished, có tác dụng block queue hiện tại, vì vậy bạn sẽ không nên gọi trên main thread.
Trong trường hợp bạn không muốn block cả 1 queue chỉ để đợi 1 task thực hiện xong mà bạn chỉ muốn block 1 vài task, bạn có thể gọi addOperations(_:waitUntilFinished:) method on OperationQueue.
Tạm dừng queue:
Bạn có thể tạm dừng operation queue bằng cách set isSuspend = true.
-> Những task đang được thực hiện vẫn sẽ dc tiếp tục thực hiện, nhưng những task chưa dc thực hiện thì sẽ đợi cho đến khi isSuspend được set lại thành false.
Set số lượng operations tối đa
Còn nếu như bạn muốn giới hạn số lượng tối đa operations chạy trong 1 Operation Queue tại 1 thời điểm, chẳng hạn như việc chỉ load những ảnh của những visible cell của collectionView chẳng hạn?
Khi đó, bạn chỉ cần set thuộc tính maxConcurrentOperationCount của Operation Queue thành 1 số bạn muốn.
class DownloadImage: Operation {
override func main() {
// Download image task
print("Start downloading... at time: \(Date().timeIntervalSince1970)")
sleep(5)
print("Finish downloading... at time: \(Date().timeIntervalSince1970)")
}
}
let operation = DownloadImage()
let operation2 = DownloadImage()
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 1
operationQueue.addOperations([operation, operation2], waitUntilFinished: true)
Nếu set maxConcurrentOperationCount = 1, thì task 1 chạy xong, sau đó task 2 mới chạy. Đây là kết quả trên màn hình console:
Nếu set maxConcurrentOperationCount = 2, thì 2 task sẽ chạy song song. Đây là kết quả trên màn hình console:
Ở part cuối của Animation Basic này, mình xin giới thiệu đến mọi người phần ảo diệu nhất của animation với UIView đó là UIView.transition. Transitions được dùng khi chúng ta muốn tạo hiệu ứng cho việc hidden view hay add hoặc remove một view lên hoặc khỏi một view cha của nó hay thay thế 2 views cho nhau khá tương đồng với các slide chúng ta hay gặp trong power point.
Transition rất đơn giản và cực kỳ hiệu quả, nó giúp app của chúng ta trở lên thân thiện, gần gũi hơn đối với người dùng. Tuy nhiên, nếu trong một màn hình có quá nhiều chuyển động sẽ dễ gấy rối mắt vì vậy hãy sử dụng các hiệu ứng animation một cách hiệu quả để đạt được kết quả tối ưu nhất.
Cảm ơn mọi người đã theo dõi loạt bài về Basic Animation của mình! Hi vọng sẽ được mọi người ủng hộ ở những bài tiếp theo.
Như phần trước, mình đã đề cập đến những khái niệm cơ bản về animation trong iOS. Với phần này, mình sẽ đi sâu hơn về các thuộc tính và hiệu ứng của UIView để làm ra một animation đẹp mắt.
withDuration: giá trị TimeInterval (typealias cho Double) thời gian thực hiện animation tính bằng giây.
delay: độ trễ tính bằng giây (TimeInterval cũng vậy) trước khi animation bắt đầu. Nếu bạn muốn bắt đầu hoạt hình ngay lập tức, bạn có thể bỏ qua tham số này (chỉ trong một số trường hợp nhất định) hoặc đặt thành 0.
usingSpringWithDamping: độ nẩy của view khi gần đến điểm kết thúc( thuộc tính này giống với sự dao động của một vật thể khi được treo trên một cái lò xo trong thực tế).
initSpringVelocity: Tốc độ di chuyển của view bắt đầu ở thời điểm xuất phát.
Animation with Spring
options: hiệu ứng hoạt hình của view trên quãng đường. Dưới đây là 1 số thuộc tính cơ bản của options:
.repeat: dùng để lặp lại animation vô hạn.
.autoreverse: là hiệu ứng view tới điểm kết thúc và trở lại điểm xuất phát.
.curveLinear: animation không có sự thay đổi về tốc độ từ khi bắt đầu tới điểm kết thúc.
.curveEaseIn: Tăng tốc khi bắt đầu animation
.curveEaseOut: Giảm tốc khi kết thúc animation
.curveEaseInOut: Kết hợp của .CurveEaseIn và .CurveEaseOut, tăng tốc khi bắt đầu animation và giảm tốc khi kết thúc animation.
Keyframes giúp tạo các chuỗi animation riêng biệt
Chúng ta có thể sử dụng các UIView.animation chồng lên nhau để tạo thành các chuỗi animation tuy nhiên sẽ gây lộn xộn các dòng code. Mọi việc sẽ trở lên dễ dàng hơn rất nhiều với keyframes hỗ trợ rất tốt xử lý các chuỗi animation kế tiếp nhau.
Tương tự UIView.animation, câu lệnh của keyframe cũng gồm 1 số thuộc tính:
relativeStartTime: là thời điểm bắt đầu tính theo % trên tổng thời gian diễn ra animation. RelativeStartTime có giá trị 0 -> 1. Ví dụ: relativeStartTime = 0.2 -> animation sẽ bắt đầu sau 20% * tổng thời gian animation.
relativeDuration: là lượng thời gian tương đối tính theo % diễn ra animation cho keyframes với tổng thời gian diễn ra toàn bộ animation. Ví dụ: relativeDuration = 0.25 tức là animation sẽ diễn ra trong 25% * tổng thời gian animation.
animations: closure chỉ ra animation đối tượng gì.
Hôm nay, mình sẽ chia sẻ đôi chút kiến thức của mình về tạo animation giúp app của chúng ta trở lên thú vị và thu hút hơn đối với người dùng.
Những điều cơ bản để tạo được animation
Về cơ bản, animation là tổ hợp các thay đổi về frame, bounds, center, transform, alpla, backgroundColor của đối tượng trên mặt phẳng và không gian chứa đối tượng ấy. Tất cả những animaton phức tạp đều được tạo thành từ những phần tử cơ bản nói trên vì vậy khi gặp 1 yêu cầu về animation phức tạp, bạn đừng vội lo lắng, thay vào đó, hãy phân tích animation ấy và đưa về các khái niệm cơ bản nhất và thực hiện từng bước các thay đổi điều kì diệu sẽ xuất hiện và bạn sẽ rất thích thú với những gì mình làm được. 🙂
UIView.animation thường được sử dụng với những animation đơn giản:
Nói về ViewController thì chắc hẳn tất cả iOS Developer đều biết đến và đã sử dụng rất nhiều. Nhưng đối với các bạn mới bắt đầu với iOS, mọi người thường không chú ý nhiều đến vòng đời của ViewController, dẫn đến mắc phải một số lỗi không đáng có. Bài viết này mình sẽ giới thiệu cho các bạn mới bắt đầu với iOS về vòng đời của View Controller và cách sử dụng để tránh những lỗi không đáng có.
View Controller Lifecycle là gì?
View Controller lifecycle là vòng đời của một view controller được tính từ lúc nó được nạp vào bộ nhớ (RAM) cho tới khi nó bị giải phóng khỏi bộ nhớ.
Phân tích vòng đời của View Controller
Dưới đây là sơ đồ về vòng đời của nó:
Như các bạn đã thấy, trên sơ đồ này có khá nhiều trạng thái mà các bạn chắc dã nhìn thấy rất nhiều nhưng một số thì không phải không 😀 Và nó cũng là các phương thức tương ứng được gọi tự động trong vòng đời của View Controller
OK, bây giờ chúng ta sẽ đi vào chi tiết.
loadView
Phương thức này được gọi khi View hiện tại đang bằng nil. Cơ bản nó sẽ đưa View mà bạn tạo trong phương thức này vào view của ViewController.
NOTE: Phương thức này được sử dụng khi View Controller được tạo bằng code. Nếu chúng ta tạo View Controller từ file .xib hoặc storyboard thì tốt nhất không sử dụng hương thức này.
viewDidload:
Phương thức này được gọi một lần duy nhất trong vòng đời của ViewController. Nó được gọi khi tất cả các view đã được load vào bộ nhớ(RAM).
Ứng dụng: 1. Khi bạn muốn cài đặt giao diện người dùng (User Interface) 2. Những công việc mà bạn muốn nó chỉ chạy duy nhất một lần trên View Controller này.
viewWillAppear:
Phương thức này được gọi mỗi lần trước khi nội dung của View được thêm vào view hierarchy của ứng dụng.
Ứng dụng: Vì phương thức này sẽ được gọi mỗi lần trước khi View được xuất hiện nên nó thường dùng khi bạn muốn 1 công việc nào đó luôn được gọi mỗi khi View Controller đó hiển thị trên màn hình. VD: Kiểm tra kết nối mạng, kiểm tra service state, add observer Notification v.v.
NOTE: • Tránh làm các công việc mà bạn chỉ muốn thực hiện nó một lần trong vòng đời của View Controller trong phương thức này. • Nếu bạn add observer notification ở hàm này thì cần remove notification ở phương thức viewDidDisappear:. Để tránh trường hợp khi quay trở lại màn hình này hàm add observer notification sẽ được đăng kí một lần nữa -> nó sẽ thực thi hàm trong #selector nhiều lần. • Với tương tác tầng Application (VD: Bấm Home, show notification center, show control center… ) rồi trở lại ứng dụng thì sẽ không kích hoạt phương thức này mà nó sẽ kích hoạt các phương thức của UIApplication.
viewDidAppear:
Phương thức này được gọi mỗi lần sau khi nội dung của View được thêm vào view hierarchy của ứng dụng.
Ứng dụng: Thường được sử dụng để lưu dữ liệu, bắt đầu animation, bắt đầu chơi Video hoặc âm thanh hoặc thu thập dữ liệu từ network.
NOTE: • Tương tự như viewWillAppear, với tương tác tầng Application (VD: Bấm Home, show notification center, show control center… ) rồi trở lại ứng dụng thì sẽ không kích hoạt phương thức này mà nó sẽ kích hoạt các phương thức của UIApplication. • Nếu bạn add observer notification ở hàm này thì cũng phải xóa notification ở viewDidDisappear:
viewWillDisappear:
Phương thức này được gọi trước khi view được xóa khỏi view hierarchy. View vẫn còn trên view hierarchy nhưng chưa được xóa.
Ứng dụng: Thường dùng để quản lí các timer, ẩn bàn phím, hủy các network request và lưu lại các trạng thái.
viewDidDisappear:
Phương thức này được gọi sau khi view của ViewController được xóa khỏi view hierarchy.
Ứng dụng: Thường sử dụng để hủy việc lắng nghe các thông báo (Notification) hoặc các cảm biến của thiết bị các trên màn hình này.
deinit:
Phương thức này được gọi trước khi một view controller bị xóa khỏi bộ nhớ.
Ứng dụng: Thường được sử dụng để xóa tài nguyên mà view controller đã được phân bổ nhưng không được giải phóng bới ARC(Automatic reference counting)
NOTE: Hãy nhớ rằng một view controller không còn hiển thị trên màn hình nữa không có nghĩa là nó đã được giải phóng. Ngay cả khi màn hình bị tắt nếu nó vẫn còn trong bộ nhớ thì nó vẫn hoạt động như thường.
didReceiveMemoryWarning:
Phương thức này được gọi khi bộ nhớ (RAM) của máy gần đầy. Và iOS không tự động di chuyển dữ liệu từ bộ nhớ sang không gian ổ cứng hạn chế của nó.
Ứng dụng: Xóa một số đối tượng ra khỏi bộ nhớ.
NOTE: Hãy nhớ rằng nếu bộ nhớ của ứng dụng vượt quá một ngưỡng nhất định, iOS sẽ tắt ứng dụng của bạn. Và nó trông giống như là ứng dụng bị crash
Cảm ơn mọi người đã theo dõi bài viết! Mọi ý kiến đóng góp mọi người hãy comment xuống phía dưới để mình có thể thay đổi cho bài biết được tốt hơn. :v
Xin chào các bạn, lại là tôi đây, bài viết lần này tôi sẽ chỉ cho các bạn cách cải thiện mã nguồn của bạn với Detekt.
Detekt là gì?
Detekt là một công cụ để phân tích code cho lập trình kotlin. Nó hoạt động dựa trên các cú phúp trừu tượng được cung cấp bởi trình biên dịch kotlin.
Detekt có những đặc trưng nào?
Phân tích code smell cho các dự án kotlin của bạn.
Báo cáo độ phức tạp dựa trên các dòng code. Độ phức tạp của McCabe và số lượng code smells.
Cấu hình cao (rule set or rule level)
Loại bỏ các phát hiện với chú thích của kotlin là @Suppress và java là @SuppressWarnings
Xác định ngưỡng code smell sẽ phá vỡ bản build của bạn và in ra cảnh báo.
Code smell baseline và bỏ qua danh sách legacy của dự án.
Gradle Plugin để phân tích code qua Gradle Build.
Grade task sử dụng IntelliJ để định dạng và kiểm tra mã kotlin.
Tuỳ chọn cấu hình của detekt cho mỗi module bằng cách sử dụng profiles (gradle-plugin).
Tích hợp SonarQube.
Có thể mở rộng bằng quy tắc riêng và FileProcessListener’s.
Tích hợp IntelliJ.
Sử dụng detekt như nào?
Trước tiên, bạn hãy cấu hình trong gradle build file.
Chúng ta cùng đi tìm hiểu trong task detekt bên trên có những gì? def input = "$projectDir/src/" là phần nào trong dự án mà bạn muốn được phân tích. def config = "$rootDir/detekt/detekt-config.yml" là phần bạn cài đặt sẽ sử dụng những quy tắc nào để phân tích. def filters = ".*/techover.detekt/.*" là phần loại bỏ không cần phân tích, với nhiều đường dẫn khác nhau thì bạn sử dụng dấu phẩy để phân tách. def output = "$rootDir/detekt/reports" là phần mà khi chạy phân tích sẽ đưa ra tài liệu báo cáo.
Vậy là bạn đã có thể chạy detekt cho dự án của bạn rồi, bằng cách chạy câu lệnh:
./gradlew detekt
Sau khi chạy câu lệnh gradlew bên trên thì sẽ xuất hiện output như dưới đây:
Starting a Gradle Daemon, 2 incompatible Daemons could not be reused, use --status for details
> Task :app:detekt
Successfully generated XmlOutputReport.
Successfully generated PlainOutputReport.
detekt run within 1019 ms
BUILD SUCCESSFUL in 14s
1 actionable task: 1 executed
Như vậy là source code của dự án bạn đang không có lỗi nào.
Nếu như bạn chạy mà source code có lỗi thì sẽ hiển thị như sau:
> Task :app:detekt
Ruleset: code-smell
Ruleset: comments
Ruleset: complexity
Ruleset: empty-blocks
Ruleset: exceptions
Ruleset: performance
Ruleset: potential-bugs
Ruleset: style
WildcardImport - [ExampleInstrumentedTest.kt] at androidTest/java/techover/detekt/ExampleInstrumentedTest.kt:9:1
WildcardImport - [ExampleUnitTest.kt] at test/java/techover/detekt/ExampleUnitTest.kt:5:1
FunctionNaming - [addition_isCorrect] at test/java/techover/detekt/ExampleUnitTest.kt:13:5
FunctionNaming - [addition_isCorrect] at test/java/techover/detekt/ExampleUnitTest.kt:13:5
MagicNumber - [addition_isCorrect] at test/java/techover/detekt/ExampleUnitTest.kt:15:22
Successfully generated XmlOutputReport.
Successfully generated PlainOutputReport.
detekt run within 1203 ms
BUILD SUCCESSFUL in 3s
1 actionable task: 1 executed
Để bạn có thể hiểu hơn về tập tin cấu hình mà bạn tham khảo ở link bên trên detekt-config.yml thì dưới đây tôi sẽ nói qua về phần này cho bạn hiểu hơn.
Tập tin cấu hình detekt có những gì?
Detekt sử dụng tệp cấu hình kiểu yaml cho nhiều thứ khác nhau:
Bộ quy tắc và thuộc tính quy tắc.
Build thất bại.
Bộ xử lý tệp kotlin.
Console và định dạng đầu ra.
Bộ quy tắc và quy tắc:
Detekt cho phép dễ dàng chỉ cần chọn các quy tắc bạn muốn và cấu hình chúng theo cách bạn muốn. Ví dụ, nếu bạn muốn cho phép tối đa 20 chức năng trong tệp Kotlin thay vì ngưỡng mặc định là 10.
complexity:
TooManyFunctions:
threshold: 20
Bộ lọc đường dẫn / Không bao gồm / Bao gồm:
Bắt đầu với phiên bản RC15 bộ lọc đường dẫn có thể được xác định cho từng quy tắc hoặc bộ quy tắc:
Xây dựng UICollection View Custom Layout và làm thế nào để lưu cache và dynamically size cell.
UICollection view được giới thiệu từ bản iOS 6 và nó đã là UI Control mà các developer hay dùng nhất.
UICollection view có thể hỗ trợ các loại hiển thị khác nhau và đặc biệt là tính năng dynamically size cell hỗ trợ người dùng hiển thị những phần mô tả ngắn mà đầy đủ nội dung cần truyền tải nhất mà không làm thay đổi thiết kế của mình. Như vậy thì UICollection view được sử dụng nhiều ở các app dạng social network, news,…,.
Ở bài viết này tôi sẽ hướng dẫn các bạn
Custom layout
Xử lý dynamically cell
Những điểm cần lưu ý
Tạo custom UICollection View Layout
Bạn có thể tạo một chế độ hiển thị cho UICollection view theo cách riêng bằng cách tuỳ chỉnh layout của bạn. Collection view layout là lớp con của UICollectionViewLayout, nó xác đinh mọi thuộc tính trong chế độ hiển thị trên UICollectionView của bạn. Các thuộc tính của UICollectionViewLayout là intances của UICollectionViewLayoutAttributes. Chúng chứa các thuộc tính của từng mục trong chế độ hiển thị của bạn.
Bắt đầu
Trước tiên ta cần chuẩn bị một project sample đã được implement UICollection View. UI cần custom là dạng cột và dynamic height.
Tạo custom collection view layout
Cách dùng Custom Collection View Layout
Tạo Custom Collection View Layout
Tạo file CustomCollectionViewLayout sub class là UICollectionViewLayout
Tiếp theo config cho collection view sử dụng custom layout. Mở file view chứa collection view
Mở thanh công cụ Attributes inspector. Chọn Custom trên thuộc tính Layout
Như vậy chúng ta đã config thành công cho collection view sử dụng custom layout.
Như vậy để hiển thị chúng cần thực hiện những gì
Như vậy class CustomCollectionViewLayout vừa tạo cần thực hiện những function sau:
collectionViewContentSize: function này trả về chiều rộng chiều cao của toàn bộ collection view contents.
prepare: UIKit sẽ call function này trước khi có các thay đổi về UI. Đây là nơi mà bạn có thể chuẩn bị các tính toán liên quan đến chế độ xem, kích thước, vị trí ,…,.
layoutAttributesForElements(in:): Trong function này bạn trả về các thuộc tính bố cục cho tất cả item dưới dạng array UICollectionViewLayoutAttributes.
layoutAttributesForItem(at:): Function này cung cấp thông tin bố trí riêng cho từng item. Bạn cần override lại nó để có thể hiển thị cho từng item mà bạn mong muốn.
Tính toán bố cục
Với layout dạng cột và bạn cần height nó được dãn tự động thì bạn cần tính toán lại bố cục của layout
Mở file CustomCollectionViewLayout và tạo protocol cho nó
Trong delegate bạn mới tạo 01 function yêu cầu trả về height, bạn sẽ implement function này trong Controller của chúng.
Tiếp đến bạn cần thực hiện implement class CustomCollectionViewLayout
// 1
weak var delegate: CustomCollectionViewLayoutDelegate?
// 2
private let numberOfColumns = 2
private let cellPadding: CGFloat = 6
// 3
private var cache: [UICollectionViewLayoutAttributes] = []
// 4
private var contentHeight: CGFloat = 0
private var contentWidth: CGFloat {
guard let collectionView = collectionView else {
return 0
}
let insets = collectionView.contentInset
return collectionView.bounds.width - (insets.left + insets.right)
}
// 5
override var collectionViewContentSize: CGSize {
return CGSize(width: contentWidth, height: contentHeight)
}
Bạn có thể thấy ở đây:
delegate
Số columns bạn cần hiển thị
cache để lưu trữ các thuộc tính hiển thị.
02 Thuộc tính width và height của contents
Trả về kích thước của collection view’s content. Bạn sử dụng cả 2 thuộc tính contentWidth và contentHeight để tính kích thước
Để tính toán cách hiển thị, mời bạn xem sơ đồ sau:
Bạn sẽ tính toán dựa trên số cột bạn muốn thiển thị và vị trí của mục trước đó trong cùng một cộ. Để tính toán thì bạn dùng xOffset cho cột và yOffset cho vị trí.
Tiếp tục bạn cần override lại function prepare
override func prepare() {
// 1
guard
cache.isEmpty,
let collectionView = collectionView
else {
return
}
// 2
let columnWidth = contentWidth / CGFloat(numberOfColumns)
var xOffset: [CGFloat] = []
for column in 0..<numberOfColumns {
xOffset.append(CGFloat(column) * columnWidth)
}
var column = 0
var yOffset: [CGFloat] = .init(repeating: 0, count: numberOfColumns)
// 3
for item in 0..<collectionView.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)
// 4
let photoHeight = delegate?.collectionView(
collectionView,
heightForPhotoAtIndexPath: indexPath) ?? 180
let height = cellPadding * 2 + photoHeight
let frame = CGRect(x: xOffset[column],
y: yOffset[column],
width: columnWidth,
height: height)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
// 5
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = insetFrame
cache.append(attributes)
// 6
contentHeight = max(contentHeight, frame.maxY)
yOffset[column] = yOffset[column] + height
column = column < (numberOfColumns - 1) ? (column + 1) : 0
}
}
Ở function này bạn có thể thấy tính width có thể cho số columns mà bạn muốn hiển thị, và tính height cho từng item và được lưu lại các thuộc tính ở cache.
Tiếp theo bạn override lại function layoutAttributesForElements(in rect: CGRect) function này sẽ được gọi sau khi function prepare() kết thúc
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var visibleLayoutAttributes: [UICollectionViewLayoutAttributes] = []
// Loop through the cache and look for items in the rect
for attributes in cache {
if attributes.frame.intersects(rect) {
visibleLayoutAttributes.append(attributes)
}
}
return visibleLayoutAttributes
}
Ở function này, bạn duyệt lại bộ cache xem frame của từng item có giao nhau với collection view không.
Cuối cùng bạn cần phải override lại function layoutAttributesForItem
Ở đây tôi đang dùng dynamic height. Height sẽ tự động theo height của image.
Tiếp theo trong function viewDidLoad() bạn thêm
if let layout = collectionView.collectionViewLayout as? CustomCollectionViewLayout {
layout.delegate = self
}
Như vậy bạn đã custom thành công layout trên UICollectionView.
Những điều cần lưu ý
Tới đây bạn sẽ gặp bug là bạn reload data cho collection view khi tăng số lượng item hiển thị. Bạn debug bạn sẽ thấy mọi quá trình bạn làm đã hoàn tất, từ việc get data và add vào list của data source cũng đã đầy đủ, nhưng chỉ thiếu một điều là collection view chỉ chạy lại với số lượng item lần đầu tiên.
Vậy vấn đề ở đây là gì?? tại sao reloadData lại không được và không có bất cứ một dòng log error nào ra ??
Vấn đề ở đây là contents size của collection view không được update, nên collection view không thể hiển thị thêm bất cứ item nào.
Vậy bug ở function nào?
Function nào tính toán lại contents size?
Là func prepare() nó là function được gọi để tính toán size. Ngay ở bước đầu tiên của func prepare()
Tôi thấy bài viết “LITTLE ENDIAN VS. BIG ENDIAN” rất hay nên xin phép tác giả đưa lên đây để cùng nhau tìm hiểu .
Little endian và big endian, đây là hai phương thức khác nhau để lưu trữ dữ liệu dạng nhị phân (binary). Bình thường thì chúng ta cũng chẳng cần quan tâm đến chúng làm gì. Bởi mọi việc sẽ được tự động hoá hết.
Thế nhưng có những tình huống, ví dụ khi phải xử lý các tập tin có cấu trúc, tập tin binary, nhất là những tập tin được ghi bằng ngôn ngữ khác, thì việc hiểu về little endian và big endian là rất quan trọng. Bởi nếu không, rất có thể chúng ta sẽ đọc sai thứ tự và xử lý với dữ liệu được hiểu sai.
Dữ liệu
Dữ liệu là thể hiện của thông tin dưới dạng lưu trữ được. Thông tin là thứ trừu tượng, không có hình dạng, đó là những hiểu biết về các sự vật, sự việc xung quanh chúng ta. Để lưu trữ, cũng như truyền đạt thông tin đến mọi người, chúng ta cần đến dữ liệu. Dữ liệu có thể là chữ viết, hình ảnh được ghi trên giấy, tất cả chúng ta dữ liệu mà con người có thể hiểu được.
Nhưng những dữ liệu đó cần phải được mã hoá một lần nữa, nếu chúng ta muốn lưu trữ chúng trên máy tính. Như chúng ta đều biết, máy tính chỉ làm việc với dữ liệu được mã hoá dưới dạng nhị phân, vậy nên mọi dữ liệu cần được mã hoá thành nhị phân mới có thể xử lý trên máy tính được.
Thực ra, máy tính không hiểu được các ký tự 0, 1 trong hệ nhị phân đâu, nó hoạt động theo các tín hiệu điện tử. Mô tả chính xác thì rất khó, nhưng chúng ta có thể hiểu “sơ sơ” rằng, gặp bit 1 thì sẽ có dòng diện, gặp bit 0 thì không có. Như vậy, các bit 0, 1 được xử lý thành các tín hiệu điện tử tương ứng, và chúng ta coi đó như máy tính đã hiểu được dữ liệu nhị phân.
Thế nhưng, mặc dù cùng sử dụng tín hiệu dạng nhị phân, các máy tính khác nhau cũng không thực sự nói chung một ngôn ngữ. Cũng giống như coi người vậy, khi nhìn các ký tự a, b, c có người hiểu, có người không. Máy tính khi nhìn vào các tín hiệu tương ứng với các ký hiệu 0 hay 1, mỗi máy tính có thể hiểu theo một cách khác nhau.
Thế nhưng, rất may là các máy tính vẫn hoạt động theo những tiêu chuẩn chung, thế nên nó vẫn có thể giao tiếp với nhau được. Tuy nhiên, lưu ý rằng, không phải bất cứ lúc nào, các máy tính cũng có thể hiểu được lẫn nhau.
Trong máy tính, các dữ liệu nhị phân không được xử lý theo từng bit riêng lẻ, mà được xử lý thành từng khối 8 bit một, và đơn vị xử lý nhỏ nhất này gọi là byte.
Ví dụ, số nguyên 123456789 được biểu diễn dưới dạng nhị phân sẽ là (ở đây tôi cho rằng kiểu dữ liệu int sẽ có kích thước là 4 byte, tuy nhiên, nhiều hệ thống 64 bit đã nâng kích thước này lên 8 byte)
00000111 01011011 11001101 00010101
Để ngắn gọn, chúng ta có thể viết nó dưới dạng hexa như sau:
07 5b cd 15
Đã có bao giờ, bạn tự hỏi, khi ghi dữ liệu này trên đĩa cứng chẳng hạn, nó được ghi thế nào chưa. Bạn cho rằng, nó sẽ được ghi lần lượt theo thứ tự mà chúng ta đang đọc và viết ở trên, thì bạn đã nhầm.
Đây là cách viết theo kiểu số Ả rập cho chúng ta dễ hiểu thôi, máy tính không “đọc” các ký tự giống như chúng ta nên nó cũng không lưu trữ giống cách chúng ta viết các ký tự này ra đâu. Việc ghi dữ liệu như thế nào chính là lúc little endian và big endian được dùng đến.
Đây là cách viết theo kiểu số Ả rập cho chúng ta dễ hiểu thôi, máy tính không “đọc” các ký tự giống như chúng ta nên nó cũng không lưu trữ giống cách chúng ta viết các ký tự này ra đâu. Việc ghi dữ liệu như thế nào chính là lúc little endian và big endian được dùng đến.
Little endian và big endian là gì?
Little endian và big endian là hai phương thức khác nhau để lưu trữ dữ liệu. Sự khác biệt của little endian và big endian khi lưu trữ chính là ở việc sắp xếp thứ tự các byte dữ liệu.
Trong cơ chế lưu trữ little endian (xuất phát từ “little-end” nghĩa kết thúc nhỏ hơn), byte cuối cùng trong biểu diễn nhị phân trên sẽ được ghi trước. Ví dụ 123456789 ghi theo kiểu little endian sẽ thành
15 cd 5b 07
Hơi ngược một chút đúng không? Big endian (xuất phát từ “big-end”) thì ngược lại, là cơ chế ghi dữ liệu theo thứ tự bình thường mà chúng ta vẫn dùng. 123456789 được lưu trữ vẫn theo đúng thứ tự là
07 5b cd 15
Các thuật ngữ big-end hay little-end xuất phát từ cuốn tiểu thuyết Gulliver du ký (Gulliver’s Travels), trong đó nhân vật Lilliputans tranh luận về việc nên đập trứng bằng đầu to hay nhỏ.
Và ngành IT đã ứng dụng thuật ngữ ngày, tương đối giống với nghĩa gốc. Lưu ý rằng, little endian hay big endian chỉ khác nhau ở cách sắp xếp các byte dữ liệu, còn thứ tự từng bit trong byte thì giống nhau. Rất may, các máy tính vẫn có điểm trung này.
Thêm một lưu ý nữa rằng, little endian hay big endian chỉ khác biệt khi cần lưu trữ những dữ liệu có nhiều byte. Những dữ liệu chỉ có 1 byte (ví dụ ký tự ASCII) thì không ảnh hưởng gì (chính xác là dù dùng phương thức nào kết quả cũng như nhau).
Little endian và big endian được dùng trên những máy tính nào?
Việc sắp xếp các byte dữ liệu theo kiểu little endian hay big endian không chỉ xảy ra khi chúng ta lưu trữ dữ liệu ra bộ nhớ ngoài. Mọi hoạt động của máy tính đều sử dụng dữ liệu nhị phân, nên little endian/big endian hiện hữu trong mọi hoạt động của máy tính.
Ngoài việc sử dụng little endian/big endian một phần phụ thuộc vào phần mềm (do lập trình viên cố ý sử dụng một trong hai loại, hoặc ngôn ngữ lập trình quy định trước), nó còn phụ thuộc vào bộ vi xử lý của chính máy tính đó.
Các bộ vi xử lý Intel đều sử dụng little endian, các bộ vi xử lý cả ARM trước đây cũng là little endian, nhưng hiện này ARM đã nâng cấp vi xử lý của mình thành bi-endian (tức là xử lý cả little endian và big endian).
Các bộ vi xử lý PowerPC và SPARK trước đây đều là big endian, nhưng hiện nay chúng cũng được nâng cấp thành bi-endian.
Các làm nào thì tốt hơn: little endian hay big endian?
Little endian hay big endian cũng như tranh luận gốc về việc đập trứng, không có một phương thức nào thực sự tốt hơn phương thức nào.
Little endian hay big endian chỉ khác nhau ở việc lưu trữ thứ tự các byte dữ liệu. Cả hai phương thức đều không làm ảnh hưởng đến tốc độ xử lý của CPU. Thế nên cả hai phương thức đều vẫn tồn tại song song và sẽ không bao giờ có thể có một câu trả lời thoả đáng: Phương thức nào thì tốt hơn?
Mỗi phương thức đều có những lợi thế nhất định. Với little endian, vì byte nhỏ nhất luôn nằm bên trái, nó sẽ cho phép chúng ta đọc dữ liệu với độ dài tuỳ ý. Nó sẽ rất thích hợp nếu chúng ta cần ép kiểu, ví dụ từ int thành long int.
Với giả định int là 4 byte, long int là 8 byte, nếu dùng little endian, khi ép kiểu, địa chỉ bộ nhớ không cần phải thay đổi, chúng ta chỉ cần ghi tiếp các byte lớn hơn mà thôi.
Nhưng nếu cũng trường hợp đó, mà sử dụng big endian, thì chúng ta sẽ phải dịch địa chỉ bộ nhớ hiện tại thêm 4 byte nữa mới có không gian để lưu trữ.
Nhưng big endian cũng có nhưng lợi thế nhất định, với việc đọc dữ liệu byte lớn nhất trước, nó sẽ rất dễ dàng kiểm tra một số là âm hay dương, do byte chứa dấu được đọc đầu tiên.
Xem các byte dữ liệu trong bộ nhớ
Chương trình C đơn giản nhau cho chúng ta cách nhìn về việc sắp xếp các byte trong bộ nhớ.
#include <stdio.h>
/* function to show bytes in memory, from location start to start+n */
void
show_mem_rep (char *start, int n)
{
int i;
for (i = 0; i < n; i++)
printf (" %.2x", start[i]);
printf ("\n");
}
/* Main function to call above function for 0x01234567 */
int
main ()
{
int i = 0x01234567;
show_mem_rep ((char *) &i, sizeof (i));
return 0;
}
Khi thực thi chương trình trên, nếu máy của bạn là little endian thì kết quả sẽ là
67 45 23 01
còn nếu máy bạn là big endian thì nó sẽ hiển thị theo thứ tự thông thường
01 23 45 67
Có cách nào để xác định máy tính của chúng ta là little endian hay big endian hay không? Có vô số các cách khác nhau, dưới đây là một trong số những cách đó:
#include <stdio.h>
int
main ()
{
unsigned int i = 1;
char *c = (char *) &i;
if (*c)
printf ("Little endian");
else
printf ("Big endian");
return 0;
}
Với đoạn code đơn giản trên, c là con trỏ, nó trỏ đến vùng nhớ của biến i là một số nguyên. Bởi vì số nguyên là kiểu dữ liệu nhiều byte, trong khí dữ liệu của char chỉ là một byte mà thôi, nên *c sẽ trả về giá trị là byte đầu tiên của số nguyên i.
Nếu máy tính của chúng ta là little endian thì byte đầu tiên này sẽ là 1, ngược lại thì nó sẽ là 0.
Điều này ảnh hưởng thế nào đến việc lập trình
Về cơ bản thì little endian hay big endian không có ảnh hưởng lắm đến việc lập trình. Phần lớn các lập trình viên không cần quan tâm nhiều lắm, bởi mọi việc đã được các trình biên dịch/thông dich đảm nhiệm hết.
Tuy nhiên, một số trường hợp, chúng ta cần quan tâm, đặc biệt khi chuyển đổi dữ\ liệu giữa các máy tính khác nhau. Ví dụ: khi chúng ta cần xử lý một file có cấu trúc thế này, 4 byte đầu tiên là một số nguyên n, sau đó là n số nguyên, mỗi số chiếm 4 byte bộ nhớ, v.v…
Trong trường hợp này, khi nhận file được tạo ra từ một máy tính khác, việc nó được ghi theo kiểu little endian hay big endian rõ ràng là ảnh hưởng rất nghiêm trọng, nếu sử dụng sai phương thức, chúng ta sẽ thu về dữ liệu sai.
Một trường hợp khác nữa có thể xảy ra vấn đề là khi chúng ta ép kiểu cho các biến
#include <stdio.h>
int
main ()
{
unsigned char arr[2] = { 0x01, 0x00 };
unsigned short int x = *(unsigned short int *) arr;
printf ("%d", x);
return 0;
}
Trong đoạn code trên, chúng ta đã ép kiểu một array hai phần tử char thành một số nguyên 2 byte (short int). Trong ví dụ này, little endian hay big endian cũng có ảnh hưởng rất lớn.
Một máy tính dùng little endian sẽ có kết quả là 1 trong khi big endian sẽ cho kết quả là 256. Để tránh những lỗi đáng tiếc có thể xảy ra, những code như trên cần phải tránh.
NUXI là một vấn đề rất nổi tiếng liên quan đến little endian và big endian: UNIX được lưu trong một hệ thống big-endian sẽ được hiểu là NUXI trong một hệ thống little endian.
Giả sử chúng ta cần lưu trữ 4 byte (U, N, I, X) bằng hai số nguyên dạng short int: UN và IX.
#include <stdio.h>
int
main ()
{
short int *s; // pointer to set shorts
s = (short int *)malloc(sizeof(short int)); // point to location 0
*s = "UN"; // store first short: U * 256 + N (fictional code)
s += 2; // point to next location
*s = "IX"; // store second short: I * 256 + X
return 0;
}
Đoạn code trên hoàn toàn độc lập với hệ thống, bất kể nó là little hay big endian. Nếu chúng ta lưu trữ các giá trị “UN” và “IX” khi đọc ra, nó vẫn sẽ là “UNIX” hay không? Nếu mọi việc chỉ xảy ra trên một máy tính, dù là big endian hay little endian thì nó sẽ luôn là như vậy, bởi mọi thứ sẽ được tự động hoá giúp chúng ta.
Với bất cứ dữ liệu nào cũng vậy, chúng ta luôn thu được dữ liệu đúng nếu đọc và ghi trong cùng một hệ thống. Thế nhưng, hãy xem xét kỹ hơn về việc sắp xếp các byte trong bộ nhớ.
Một hệ thống big endian sẽ lưu trữ như sau:
U N I X
Còn một hệ thống little endian thì sẽ như sau:
N U X I
Mặc dù trông hơi ngược nhưng hệ thống little endian sẽ xử lý việc đọc giúp chúng ta, nên lưu trữ như vậy nhưng khi lấy ra chúng ta vẫn có dữ liệu ban đầu. Thế nhưng khi chúng ta ghi dữ liệu này ra file, chuyển sang một máy tính khác. Và mỗi máy tính lại xử lý theo cách riêng của nó thì UNIX trên máy big endian sẽ được hiểu là NUXI trên máy little endian (và ngược lại).
Đây chính là vấn đều nguy hiểm nhất khi chúng ta trao đỏi dữ liệu qua lại giữa các máy tính với nhau, đặc biệt trong thời đại Internet ngày nay.
Trao đổi dữ liệu giữa các máy có endian khác nhau
Ngày nay, mọi máy tính đều được kết nối để trao đổi dữ liệu với nhau. Little endian hay big endian cũng đều phải trao đổi với nhau, nhưng làm thế nào để có hiểu được nhau khi chúng không nói chung một thứ tiếng?
Có 2 giải pháp chính cho việc này
Sử dụng chung định dạng
Một phương án đơn giản nhất tất cả sử dụng chung một định dang khi truyền dữ liệu.
Ví dụ những tập tin dạng PNG đều bắt buộc phải sử dụng big endian. Tương tự với các tập tin có cấu trúc khác. Đó là lý do vì sao chúng ta nhiều khi cần phải dùng những phần mềm chuyên dụng để đọc và ghi các file này.
Thế nhưng trong kết nối với Internet, việc truyền dữ liệu còn phức tạp hơn thế. Chúng ta không thể cứ dùng một định dạng file nào đó, rồi truyền từng byte một sang máy khác được. Muốn tăng tốc độ, bắt buộc chúng ta phải truyền nhiều byte một lúc.
Và khi đó chúng ta cần có một chuẩn chung. Hiện nay, chuẩn chung cho việc truyền dữ liệu trên mạng, gọi là network byte order chính là big endian. Thế nhưng, dù đã chuẩn chung rồi, thỉnh thoảng vẫn có những giao thức chơi chội hơn, sử dụng little endian.
Để có thể chuyển đổi dữ liệu thành dữ liệu chuẩn theo network byte order, chương trình cần gọi hàm hton* (host-to-network) (trong ngôn ngữ C). Trong hệ thống big endian, hàm này không cần làm gì cả, còn little endian sẽ thực hiện chuyển đối các byte một chút.
Dù hệ thống big endian không cần chuyển đổi dữ liệu, việc gọi hàm này vẫn là rất cần thiết. Chương trình của chúng ta có thể được viết bằng một ngôn ngữ (C) nhưng có thể được dịch và thực thi ở nhiều hệ thống khác nhau, việc gọi hàm này sẽ giúp chúng ta làm điều đó.
Tương tự, ở chiều ngược lại, chúng ta cần gọi hàm ntoh* để chuyển đổi dữ liệu nhận được từ mạng về dữ liệu máy tính có thể hiểu được. Ngoài ra, chúng ta còn phải hiểu rõ kiểu dữ liệu mà chúng ta cần chuyển đổi nữa, danh sách các hàm chuyển đổi như sau:
htons – “Host to Network Short”
htonl– “Host to Network Long”
ntohs – “Network to Host Short”
ntohl – “Network to Host Long”
Những hàm này vô cùng quan trọng khi thực hiện chia sẽ dữ liệu ở tầng thấp, ví dụ khi kiểm tra checksum của các gói tin chẳng hạn. Nếu không hiểu rõ về little endian và big endian thì khi cần làm việc về mạng, bạn sẽ gặp nhiều khó khăn.
Sử dụng BOM (Byte Order Mark)
Một phương án khác để giải quyết sự khác biệt về endian là sử dụng BOM (Byte Order Mark). Đây là một ký tự đặc biệt, có giá trị là 0xFEFF, được ghi ở vị trí đầu tiên của file.
Nếu bạn đọc ký tự này là 0xFFFE (bị ngược) thì có nghĩa file này được ghi với endian khác với hệ thống của bạn, khi đó, bạn sẽ cần phải thay đổi phương thức đọc dữ liệu một chút.
Có một vài vấn đề nhỏ với việc sử dụng BOM. Thứ nhất, BOM sẽ gây tăng dữ liệu được ghi vào file. Ngay cả khi chúng ta chỉ gửi đi 2 byte dữ liệu, chúng ta vẫn cần thêm 2 byte BOM nữa.
Thứ hai, BOM không hoàn toàn thần thánh, bởi nó phụ thuộc vào lập trình viên. Có người có tâm thì đọc và xử lý khi gặp BOM, có người thì hoàn toàn bỏ quên nó và coi nói như dữ liệu thông thường. Unicode sử dụng BOM khi lưu trữ dữ liệu nhiều byte (nhiều ký tự Unicode được mã hoá thành 2, 3 thậm chí là 4 byte).
Xin chào mọi người. Trong swift có một số tính năng rất hay đó là Higher Order Function. Nó có một số hàm như là map, CompactMap, Filter and Reduce được sử dụng cho các kiểu dữ liệu dạng collection.
Khởi tạo giá trị mẫu
struct Person {
let name: String
let age: Int
let pets: [String]
}
struct Pet {
let name: String
let age: Int
}
var peopleArray = [Person(name: "Jack", age: 11, pets: ["Dog", "Cat"]),
Person(name: "Queen", age: 12, pets: ["Pig"]),
Person(name: "King", age: 13, pets: [])]
MAP
Trước khi sử dụng chúng ta cùng tìm hiểu về syntax của hàm này trước nhé:
let resultCollection = inputCollection.map { (elementOfCollection) -> ResultType in
return ResultType()
}
Nhìn vào đoạn code trên ta có thể hiểu hàm này sẽ trả về cho ta một collection có kiểu dữ liệu là ResultType( Một kiểu dữ liệu bất kì mà bạn mong muốn, nó có thể là Int, Double, String …)
OK, Giờ chúng ta đi vào code mẫu để dễ hiểu hơn
Dạng đầy đủ:
var ages = peopleArray.map { (person) -> Int in
return person.age
}
print(ages)
// OUTPUT: [11, 12, 13]
Ngoài dạng thông thường thì hàm này còn có thể viết dưới dạng rút gọn như sau:
Có thể thấy flat map sẽ loaị bỏ hết các tầng collection bên trong và chuyển về 1 collection chỉ còn 1 tầng duy nhất thay vì collection chứa collection như khi sử dụng hàm Map. Ngoài ra hàm flatMap cũng bỏ đi các giá trị collection empty hoặc nil.
NOTE: Hàm flatMap chỉ trả về giá trị đúng như mong đợi khi kiểu của nó là non-optional.
Vậy trong trường hợp khai báo kiểu optional thì sao: let pets: [String]? Trong trường hợp này hàm Flat Map sẽ trả về giá trị như hàm Map vì vậy chúng ta sẽ call flatMap thêm 1 lần nữa như sau:
Ở dạng rút gọn, để sử dụng được hàm Reduce trong trường hợp này. Chúng ta cần sử dụng hàm Map để tạo ra một collection mới, chứa các phần tử là tuổi của tất cả mọi người trước, rồi sử dụng hàm Reduce dạng rút gọn để thực hiện.
Ứng dụng: Bạn nên sử dụng hàm Reduce khi bạn muốn kết hợp các phần tử trong collection
Filter
Cấu trúc hàm Filter
let result = inputCollection.filter { (elementOfCollection) -> Bool in
return (Conditions)
}
Hàm Filter sẽ trả về kết quả là 1 collection chứa tất cả các phần tử thỏa mãn điều kiện (Conditions)
Bây giờ chúng ta sẽ đi vào ví dụ cho dễ hiểu hơn: Bài toán của chúng ta là cần lọc ra được tất cả các Person có số tuổi > 11.
Chúng ta sẽ sử dụng hàm Filter dạng đầy đủ như sau:
let result = peopleArray.filter { (person) -> Bool in
return person.age > 11
}
print("result: \(result)")
Dạng rút gọn:
let results = peopleArray.filter({ $0.age > 11 })
print(results)
Ứng dụng: Sử dụng khi bạn cần giải quyết bài toán lọc các phần tử trong collection có điều kiện xác định
NOTE:
print("Cảm ơn mọi người đã theo dõi bài viết của mình.\n
Mọi đóng góp cũng như góp ý, mọi người hãy comment ở phía dưới để mình có thể hoàn thiện bài viết tốt hơn.")
Chắc hẳn anh em làm việc với Java sẽ quen thuộc với Vector và ArrayList. Cả hai đều implement interface List và có cấu trúc dữ liệu dạng dynamically resizeable arrays, giống như một mảng thông thường.
Cốt thì đơn giản như sau
ArrayList<T> al = new ArrayList<T>();
Vector<T> v = new Vector<T>();
Tuy nhiên thì vẫn có những điểm khác nhau giữa Vector và ArrayList:
Synchronization: Về cơ bản VectorSynchronized, tức là tại một thời điểm, một và chỉ 1 thread có thể sử dụng Vector, trong khi đó Array List thì không, và nhiều thread có thể làm việc trên cùng một Array List. Ví dụ: một thread có thể thực hiện việc thêm (add), và một thread khác có thể thực hiện việc remove ở trong một môi trường multithreading.
Nếu nhiều thread truy cập mảng Array List đồng thời, thì chúng ta phải synchronize code block nếu block đó sửa cấu trúc(thêm hoặc xóa (các) phần tử). Re-assign value không làm thay đổi cấu trúc.
Nói cách khác thì Vectorthread-safe còn Array List sẽ đạt được thread-safe bằng synchronized.
Data Growth: Cả hai đều có thể quản lý dữ liệu động tức là ** grow and shrink dynamically** và tối ưu hoá việc sử dụng bộ nhớ. Khác biệt lớn nhất là size, cả 2 đều có capacity tức là số lượng phần tử tối da tại một thời điểm, và mặc định là 10 (kể cả khi cả 2 đều rỗng)
ArrayList()
Constructs an empty list with an initial capacity of ten.
https://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html
Vector()
Constructs an empty vector so that its internal data array has size 10 and its standard capacity increment is zero.
https://docs.oracle.com/javase/7/docs/api/java/util/Vector.html
Vấn đề là capacity của ArrayList sẽ tăng 50% khi số lượng phần tử vượt quá capacity còn vector là 100%
Traversal: Vector có thể sử dụng cả Enumeration và Iterator để traversing trong khi ArrayList chỉ có thể sử dụng Iterator,
Performance: Về cơ bản thì Array List sẽ nhanh hơn, vì nó non-synchronized, còn Vector thì synchronized(thread-safe). Nếu một thread hoạt động trên một Vector, thì nó sẽ có lock, các thread khác sẽ cần chờ lock để làm việc.
Đo Vector vs ArrayList
Nói chung là cần test, mình đã chuẩn bị một bài test cho ArrayList và Vector, cốt ở đây nhé. Bài này mình sử dụng OpenJDK JMH để thực hiện benchmark và thực hiện ở 2 bài toán: add và traversal. Đây là kết quả.
Về cơ bản thì ArrayList mạnh hơn ở cả 2 bài test vì non-thread-safe. Tuy nhiên để cân nhắc chúng ta sẽ thấy:
Với bài toán single-thread, ArrayList là một lựa chọn hiển nhiên, nhưng với multithreading rõ ràng Vector có sự ưu tiên hơn.
Nếu chúng ta không biết hoặc không áng chừng được lượng data trong Collection, nhưng biết được tốc độ tăng của lượng dữ liệu, Vector lợi thế hơn, vì với Vector chúng ta có thể quản lý được increment value của capacity.
Vector là một legacy class, ArrayList thì mới hơn nên nhanh hơn 🙂