When it comes to compensation and benefits, other than base salary, which is the most important to you?
This blog post is part of Udacity Data Scientists Nanodegree Program. Detailed analysis with all required code is posted in my github repository and Jupyter notebook.
Each stage of the CRISP-DM process. These stages are:
Business Understanding
Data Understanding
Prepare Data
Data Modeling
Evaluate the Resultssults
Business Understanding
In this Notebook, I will explore the 2023 Stack Overflow results to gather some information about the organization, main field of study, important benefits, and the increase in salary with the years of learning how to program.
There are 4 questions I want to find answers to:
Question 1: What type of company or organization do you work for?
Question 2: What is your main field of study in college or university/for your undergraduate studies in 2023?
Question 3: Which is the most important benefits to you?
Question 4: The more you learn, will the more salary you get?
Data Understanding
In order to understanding data. I will do these steps:
Handle categorical data
Analyze, Model, and Visualize
Question 1: What type of company or organization do you work for?
Evaluate the Results
Through the chart, we can easily see:
The number of people working in Privately-held limited companies, not in startup mode, accounts for the largest number, about more than 16,000 people.
The number of people working in Publicly-traded corporations accounts for the second largest number, about 6,000 people.
The number of people working in State-owned companies is small, about less than 1,000 people.
Thus, the difference between the number of people working between a Privately-held limited company and a State-owned company is quite large, about 15,000 people
Question 2: What is your main field of study in college or university/for your undergraduate studies in 2023?
Evaluate the Results
Through the chart, we can easily see:
The proportion of people graduating from field Computer science or software engineering is the largest, about 43%.
The rate of people graduating from field Computer engineering or electrical/electronics engineering ranks second, about 9%.
The rate of people graduating from field A health science is the least, about 1%.
Thus, the proportion of people graduating from field Computer science or software engineering is much larger than field A health science, about 42%. Thereby, we see that field Computer science or software engineering is a field that is very attractive to learnersers.
Question 3: Which is most important benefits to you?
Evaluate the Results
Based on the chart, we see:
The benefit users are most interested in is Vacation/days off, accounting for about 15%.
Remote options is the second most important benefit, accounting for about 13.7%.
Child/elder care and Other are benefits that receive little attention, accounting for about 2% and 1% respectively.
Lastly, when it comes to compensation and benefits, other than base salary, Vacation/days off is the following are most important to users
Question 4: The more you learn, will the more salary you get?
Evaluate the Results
Based on the map, we can see:
We can achieve the highest salary when we have 20 years or more of study experience.
From 19 to 20 years of study, we can reach the 2nd highest average salary.
The average salary is lowest when we have less than 1 year of study experience.
From there we conclude that the more years of experience accumulated working on a project, the higher the average salary and vice versa.
Conclusion
In this article, we took a look at main field of study, important benefits, and the increase in salary with the years of learning how to program according to Stack Overflow 2023 survey data.
We gathered the advice of the masses as to how to increase in salary with the years, which showed that the more years of experience accumulated working on a project, the higher the average salary and vice versa.
When it comes to compensation and benefits, other than base salary, we then looked at the satisfaction of important benefits. This showed that Vacation/days off is the following are most important to users.
We found that field Computer science or software engineering is a field that is very attractive to learnersers.
The findings here are observational, not the result of a formal study
To see more about this analysis, see the link to my Github available here
Xin chào, lại là mình đây – baka3k – một coder thích nói lan man về những điều nhảm nhí
Đã rất lâu rồi mình ko viết về những thứ ngoài công nghệ, lần này mình sẽ cố gắng giữ sự cân bằng để ko đưa quá nhiều toxic vào bài viết này, nên nếu bạn thấy mình trong đó, đừng giật mình, vì mình không cố ý đâu
4 mức độ hiểu biết(vài chỗ thì người ta sẽ gọi là 4 mức độ ngu dốt) gồm 4 level sau:
Không Biết rằng mình Không Biết
Không Biết rằng mình Biết
Biết rằng mình Không Biết
Biết rằng mình Biết
Không Biết rằng mình Không Biết
Hay còn gọi là Ngu mà không biết là mình ngu
Cái Ngu ở đây mình đang nói về cái ngu của Trí giả, ko phải cái ngu mang tính miệt thị đâu nhé
Thời kỳ mông muội sơ khai, khi linh trí của tu hành giả chưa được khai mở, ý lộn… khi lập trình viên còn chưa biết mình không biết cái gì, cần học cái gì, cần đi theo đường nào.
Đôi khi nó xuất hiện cả ở những level cao hơn một chút Middle hoặc Senior. Có những giai đoạn các bạn này làm việc & phát triển bản thân trong vô định
Vô định ở đây là các bạn làm rất tốt công việc hiện tại của mình, nhưng loay hoay không biết mình nên phát triển tiếp thế nào vì mọi thứ đều đang rất tốt, các bài toán công ty đưa cho đều ở mức độ vừa phải, không nhiều thách thức. Ừ thì đại khái là training vài bạn fresher, đôi khi review code hộ ai đó, đôi khi fix bug hộ, làm vài cái seminar, hiểu cách triển khai vài cái design pattern…blabla. Mọi thứ các bạn đều làm được, các bạn cảm thấy mình đang đứng ở đỉnh của domain đang làm việc, cuối chuỗi thức ăn. Lúc này rất dễ dàng để bạn lạc lối. rất dễ dàng để tự nhận rank của mình là Expert, Guru. Nếu bạn tự thấy mình đang ở level này, thì nên ngồi xuống, bình tĩnh lại và tự review bản thân mình thật cẩn thận.
Senior 5 năm Kinh nghiệm, Expert 7 năm kinh nghiệm, Guru 10 năm kinh nghiệm… chỉ làm loanh quanh mấy cái app client server, vài ba cái architecture MVVM, MVP, rồi cao hơn tý là Hexagonal hay Clean architecture – đó là một sự giễu cợt
Tuy người ta không thể lấy số năm kinh nghiệm để đánh giá về Competency của một người, nhưng nó sẽ phản ánh đúng được phần nào đó, giống như lái xe ô tô cần khoảng 1 vạn km, lái máy bay cần khoảng 10.000 giờ bay mới có thể tạm gọi là nhuần nhuyễn vậy.
Tất nhiên, còn liên quan đến lĩnh vực công việc bạn đang làm việc, ví dụ như 5 năm CRUD, 5 năm làm ứng dụng client server chả hạn… thì thôi, bỏ đi. Nếu bạn không muốn ngồi trong giếng, hãy nghiêm túc suy nghĩ lại con đường phát triển bản thân, tìm các cột mốc mới, tìm ra những điểm thiếu sót – nếu không thể, hãy tìm cho mình một Mentor có tâm và đủ Tầm
Không Biết rằng mình Biết
Ai cũng sẽ ở giai đoạn này, chúng ta đều sẽ gặp giai đoạn này trong quá trình phát triển bản thân
Khi chúng ta học và khám phá, khi chúng ta dung nhập một lượng kiến thức khổng lồ vào, trước khi biến nó thành của bản thân mình, bạn sẽ thấy keyword này quen quen, mình thấy nó rồi, có thể nó là lĩnh vực này
Đó là chúng ta, khi vào giai đoạn Không biết là mình Biết
Biết rằng mình không biết
Mọi thứ trở lên rõ ràng hơn, kiến thức được sắp xếp lại, được hoạch định đúng cách hơn
Bạn đủ overview, đủ nhận thức để phân biệt giữa biết lờ mờ, biết keyword, nắm vững, hiểu biết chuyên sâu…etc
Đừng khinh thường những cụm từ được bôi đen này vì nó giúp bạn không lạc lối
Việc không hiểu rõ các cụm từ này – sẽ khiến bạn quay về thời kỳ mông muội Không biết là mình Không Biết
Khi bạn biết là mình không biết cái gì, bạn mới có thể có kế hoạch để cải thiện, hoạch định lại bản thân một cách rõ ràng hơn
Đây là thời điểm bạn sẵng sàng và đủ năng lượng để trở lên mạnh mẽ hơn, thời điểm này mang lại sức bật rất lớn cho hầu hết tất cả mọi người. Nếu bạn ở trong thời điểm này, đừng bỏ lỡ, tận dụng tốt, bạn có thể đi rất xa
Xin chào mọi người, hôm nay mình sẽ chia sẻ với các bạn về hàm ARRAY_AGG (JSON_ARRAYAGG), ARRAY_DISTINCT trong SQL và ví dụ cụ thể để truy vấn dữ liệu.
Chúng ta có 1 bảng dữ liệu như sau:
Đề bài: Tổng hợp các giá trị bao gồm cả giá trị NULL trong cột Cus_rank thành một mảng (group by id). Loại bỏ các giá trị trùng lặp trong mảng đó.
Để giải quyết được bài toán trên chúng ta sẽ phải sử dụng đến hàm ARRAY_AGG, vậy hàm ARRAY_AGG có chức năng gì?
1. Chức năng hàm ARRAY_AGG
Cú pháp:
ARRAY_AGG(expression [ORDER BY [sort_expression {ASC | DESC}], [...])
Chức năng: tổng hợp tập hợp các giá trị bao gồm NULL trong một cột thành một mảng
Chi tiết:
ARRAY_AGG() nhận một biểu thức trả về giá trị thuộc bất kỳ loại nào hợp lệ cho một phần tử mảng.
Mệnh đề ORDER BY là mệnh đề tùy chọn. Nó chỉ định thứ tự các hàng được xử lý trong tập hợp, xác định thứ tự của các phần tử trong mảng kết quả.
Tương tự như các hàm tổng hợp khác như AVG(), COUNT(), MAX(), MIN() và SUM(), ARRAY_AGG() thường được sử dụng với mệnh đề GROUP BY.
Chú ý:
Thứ tự của các phần tử trong mảng là ngẫu nhiên.
Kiểu dữ liệu của các phần tử trong mảng trả về giống với kiểu dữ liệu của các giá trị trong cột.
2. Giải toán
Chúng ta cùng nhau đi giải quyết từng yêu cầu của bài toán trên nhé!
Đối với yêu cầu ‘Tổng hợp các giá trị bao gồm cả giá trị NULL trong cột Cus_rank thành một mảng (group by id)’ chúng ta sử dụng hàm ARRAY_AGG() để thực hiện truy vấn như sau:
Select id, ARRAY_AGG(cus_rank) as cus_rank_1 from Customer group by id;
Tuy nhiên, trong MySQL Workbench lại không hỗ trợ hàm ARRAY_AGG => chúng ta sẽ thay thế bằng hàm JSON_ARRAYAGG như sau:
Select id, JSON_ARRAYAGG(cus_rank) as cus_rank_1 from Customer group by id;
Kết quả trả về như sau:
Đối với case Loại bỏ các giá trị trùng lặp trong mảng, thường chúng ta sẽ sử dụng hàm array_distinct:
Cú pháp:array_distinct(array) - Removes duplicate values from the array.
Tuy nhiên, trong MySQL Workbench JSON_ARRAYAGG() không hỗ trợ DISTINCT. Bạn có thể SELECT DISTINCT trong truy vấn subquery và sau đó tổng hợp, như sau:
SELECT id, JSON_ARRAYAGG(cus_rank) AS cus_rank_1 FROM (SELECT DISTINCT id, cus_rank from Customer) a GROUP BY id;
Kết quả trả về như sau:
Trên đây là một vài chia sẻ của mình, nếu các bạn có cách làm nào hay, đừng ngần ngại comment nhé. Hẹn gặp lại mọi người trong bài viết tiếp theo.
Xin chào mọi người, hôm nay mình sẽ chia sẻ với các bạn về hàm Lag và Lead trong SQL và ví dụ cụ thể để truy vấn dữ liệu.
Chúng ta có 1 bảng dữ liệu như sau:
Đề bài: Lấy ra giá trị cus_rank liền trước (Pre_cus_rank) của cùng 1 cus_id trong bảng Customer. Đối với case null thì gán Pre_cus_rank = cus_rank.
Để giải quyết được bài toán trên chúng ta sẽ phải sử dụng đến hàm LAG, vậy chức năng của hàm LAG là gì?
1. Chức năng hàm LAG
Cú pháp:
LAG (value_expr [, offset ]) [ IGNORE NULLS | RESPECT NULLS ] OVER ( [ PARTITION BY window_partition ] ORDER BY window_ordering )
Chức năng: Hàm LAG trả về các giá trị cho một hàng tại một khoảng lệch nhất định phía trên (trước) hàng hiện tại trong phân vùng.
Chi tiết:
value_expr: Cột mục tiêu hoặc biểu thức mà hàm hoạt động trên đó.
offset: Một tham số tùy chọn chỉ định số lượng hàng trước hàng hiện tại cần trả về giá trị. Phần bù có thể là một số nguyên không đổi hoặc một biểu thức đánh giá thành một số nguyên. Nếu bạn không chỉ định chênh lệch, Amazon Redshift sẽ sử dụng 1 làm giá trị mặc định. Độ lệch bằng 0 cho biết hàng hiện tại.
IGNORE NULLS: Thông số kỹ thuật tùy chọn chỉ ra rằng Amazon Redshift nên bỏ qua các giá trị null khi xác định hàng nào sẽ sử dụng. Giá trị null được bao gồm nếu IGNORE NULLS không được liệt kê.
RESPECT NULLS: Cho biết Amazon Redshift phải bao gồm các giá trị null khi xác định hàng nào sẽ sử dụng. RESPECT NULLS được hỗ trợ theo mặc định nếu bạn không chỉ định IGNORE NULLS.
OVER: Chỉ định phân vùng và thứ tự.
PARTITION BY window_partition: Đối số tùy chọn đặt phạm vi bản ghi cho mỗi nhóm trong mệnh đề OVER.
8 ORDER BY window_ordering: Sắp xếp các hàng trong mỗi phân vùn
2. Giải toán
Chúng ta cùng nhau đi giải quyết từng yêu cầu của bài toán trên nhé!
Đối với yêu cầu ‘Lấy ra giá trị cus_rank liền trước của cùng 1 cus_id trong bảng Customer’ chúng ta sử dụng hàm lag để thực hiện truy vấn như sau:
Select *, lag(Cus_rank,1) over (partition by id order by update_dt) as Pre_cus_rank from Customer
Kết quả trả về như sau:
Đối với case null thì gán Pre_cus_rank = cus_rank của bài toán, ta thực hiện truy vấn như sau:
with a as (Select *, lag(Cus_rank,1) over (partition by id order by update_dt) as Pre_cus_rank from Customer) select *, case when Pre_cus_rank <> Cus_rank then Pre_cus_rank else Cus_rank end as Pre_cus_rank_1 from a;
Kết quả trả về như sau:
Trên đây là trường hợp lấy ra giá trị liền trước của 1 bản ghi (dùng hàm LAG), ngược lại muốn lấy giá trị tiếp sau của bản ghi đó chúng ta sẽ sử dụng hàm LEAD
Cú pháp:LEAD (value_expr [, offset ]) [ IGNORE NULLS | RESPECT NULLS ] OVER ( [ PARTITION BY window_partition ] ORDER BY window_ordering )
Áp dụng:
with a as (Select *, lead(Cus_rank) over (partition by id order by update_dt) as Update_cus_rank from Customer) select *, case when Update_cus_rank <> Cus_rank then Update_cus_rank else Cus_rank end as Update_cus_rank_1 from a;
Kết quả:
Để tìm hiểu thêm về hàm LAG, LEAD, các bạn có thể tham khảo các link sau:
Trên đây là một vài chia sẻ của mình, nếu các bạn có cách nào giải toán hay, đừng ngần ngại commnet nhé. Hẹn gặp lại mọi người trong bài viết tiếp theo.
Hi. Apple CarPlay không còn xa lạ với nhiều người, đặc biệt là các anh em sử dụng xe hàng ngày. Vậy làm sao để một ứng dụng hỗ trợ CP? (CarPlay). Trong bài này mình sẽ giới thiệu đến anh em về CP.
CP là gì?
CP là một cách an toàn để sử dụng iPhone trên xe ô tô, CP sẽ thực hiện các tác vụ mà người dùng muốn thực hiện với iPhone trong khi lái xe.
CP Types
Ở thời điểm hiện tại Apple cung cấp 9 CP types
Audio
Communication (Messaging & calling)
Driving task
EV charging
Fueling
Navigation
Packing
Quick food ordering
Automaker
Đến đây chắc hẳn các bạn cũng thắc mắc tại sao type Automaker mình lại highlight lên như vậy. Thì đây là một type rất đặc biệt. Và ở phần 1 này mình chỉ chia sẻ về 8 types bên trên, riêng type Automaker mình sẽ chia sẻ ở phần tiếp theo.
Mỗi CP type sẽ có một entitlement key, key này chính là để enable/disable tính năng CP trong các ứng dụng
Entitlement
Key
Minimum version
CarPlay Audio App (CarPlay framework) App supports the CarPlay framework. Include both CarPlay audio app entitlements if your app supports the CarPlay framework and the Media Player framework.
com.apple.developer.carplay-audio
iOS 14
CarPlay Audio App (Media Player framework) Deprecated. App supports the Media Player framework. Include both CarPlay audio app entitlements if your app supports the CarPlay framework and the Media Player framework.
com.apple.developer.playable-content
CarPlay Communication App App supports the CarPlay framework, and SiriKit intents for messaging or VoIP calling apps. May be combined with the optional CarPlay Messaging App and CarPlay VoIP Calling App entitlements to support iOS 13 and earlier.
com.apple.developer.carplay-communication
iOS 14
CarPlay Messaging App Deprecated. App relies solely on SiriKit and supports SiriKit intents to send, request, and modify messages. May be combined with the CarPlay Communication App entitlement, and the optional CarPlay VoIP Calling App entitlement to support iOS 13 and earlier.
com.apple.developer.carplay-messaging
CarPlay VoIP Calling App Deprecated. App relies solely on SiriKit and CallKit, and supports SiriKit intents for starting calls and requesting a list of calls. May be combined with the CarPlay Communication App entitlement, and the optional CarPlay Messaging App entitlement to support iOS 13 and earlier.
com.apple.developer.carplay-calling
CarPlay Driving Task App App supports the CarPlay framework.
com.apple.developer.carplay-driving-task
iOS 16
CarPlay EV Charging App App supports the CarPlay framework. May be combined with the CarPlay Fueling App entitlement.
com.apple.developer.carplay-charging
iOS 14
CarPlay Fueling App App supports the CarPlay framework. May be combined with the CarPlay EV Charging App entitlement.
com.apple.developer.carplay-fueling
iOS 16
CarPlay Navigation App App supports the CarPlay framework.
com.apple.developer.carplay-maps
iOS 12
CarPlay Parking App App supports the CarPlay framework.
com.apple.developer.carplay-parking
iOS 14
CarPlay Quick Food Ordering App App supports the CarPlay framework.
com.apple.developer.carplay-quick-ordering
iOS 14
đây là các key của 8 types được cung cấp bưởi Apple
Note: Khi xây dựng một ứng dụng có hỗ trợ CP, thì chúng ta chỉ được chọn một trong 8 type này. Và config với key tương ứng trong file entitlement, như vậy là compiler đã hiểu là ứng dụng này có hỗ trợ CP.
CP Templates
Apple cung cấp 12 templates để hiển thị trên CP. Chúng ta không thể thay đổi layout của của các template này mà chỉ có thể input data vào để hiển thị.
Ở trên mình đã giới thiệu đến các bạn types và templates trong CP. Vậy nó có mối quan hệ gì với nhau không?
Câu trả lời là có, mỗi CP type sẽ hỗ trợ một vài templates.
Ví dụ: với những ứng dụng CP Audio thì chỉ có thể hiển thị được với các templates như Alert, Grid, List, Tab bar, Now Playing
Trong trường hợp chọn một template không hỗ trợ để hiển thị thì app sẽ crash.
Ví dụ: ứng dụng CP Audio không hỗ trợ type Action Sheet, nếu chúng ta sử dụng type Action Sheet để hiển thị thì ứng dụng CP sẽ crash.
Di chuyển màn hình
Như một ứng dụng chạy trên iPhone, thì CP cũng có thể di chuyển màn hình bằng hai phương thức chính là push và present. Nhưng bản chất ở đây là di chuyển giữa các templates Ví dụ: Chúng ta xây dựng ứng dụng CP Audio, màn hình root view là List template, khi bấm vào một item trong list thì di chuyển đến template Now Playing thì đơn giản là chúng ta sử dụng phương thức push hoặc present để di chuyển.
Không phải template nào cũng có thể push và present được. Ví dụ: khi các bạn push đên Grid template thì không vấn đề gì, còn khi các bạn present đến Grid thì CP sẽ crash và báo present không support Grid.
limit khi di chuyển màn hình, hầu hết các app sẽ có độ sâu phần cấp là 5, ứng dụng Fueling sẽ có độ sâu là 3, ứng dụng Driving task và Quick food ordering có độ sâu là 2. Tính từ màn hình root. Ví dụ: khi xây dựng CP Audio các bạn không thể di chuyển quá 5 màn hình Template1 -> Template2 -> Template3 -> Template4 -> Template5 -> Template6 Di chuyển từ Template1 đến Template5 thì không sao, và app sẽ crash khi di chuyển từ Template5 đến Template6
Hướng dẫn khi xây dựng ứng dụng CP
Khi xây dựng một ứng dụng CP thì chúng ta phải tuân thủ theo 8 hướng chung sau đây: (Mình sẽ lấy nguyên các hướng dẫn của Apple cung cấp để cho các bạn tham khảo nhé)
Your CarPlay app must be designed primarily to provide the specified feature to a user (e.g. CarPlay audio apps must be designed primarily to provide audio playback services, CarPlay parking apps must be designed primarily to provide parking services, etc.).
Never instruct users to pick up their iPhone to perform a task. If there is an error condition, such as a required log in, you can let users know about the condition so they can take action when safe. However, user messages must not include wording that asks users to manipulate their iPhone.
All CarPlay user flows must be possible without interacting with iPhone.
All CarPlay user flows must be meaningful to use while driving. Don’t include features in CarPlay that aren’t related to the primary task (e.g. unrelated settings, maintenance features, etc.).
No gaming or social networking.
Never show the content of messages, texts, or emails on the CarPlay screen.
Use templates for their intended purpose, and only populate templates with the specified information types (e.g. a list template must be used to present a list for selection, album artwork in the now playing screen must be used to show an album cover, etc.).
All voice interaction must be handled using SiriKit (with the exception of CarPlay navigation apps, see below).
Ngoài 8 hướng dẫn chung này ra thì một số CP types có các hướng dẫn đặc biệt nữa. Chúng ta có thể tham khảo ở page 6, 7, 8, 9, 10
Môi trường phát triển
Cũng như các ứng dụng trên iPhone khi các bạn muốn chạy trên device thật thì cần certificate và provisioning. Thì ứng dụng CP cũng vậy. Tuy nhiên khi chạy ứng dụng ra simulator thì không cần thiết. Ví dụ: Khi xây dựng một ứng dụng hỗ trợ notification thì cần enable notification ở capabilities, thì với ứng dụng CP cũng cần enable entitlement ở trong Additional Capabilities.
Mục Additional Capabilities của tất cả các Apple Account đều là rỗng, developer phải request đến Apple và đợi họ approve thì mới có thể enable Capabilities để chạy trên device thật.
Account của mình đã được Apple approve với type Audio, mình nhớ là mất khoảng 14 ngày để Apple approve.
Apple cung cấp cho chúng ta hai simulators để thực hiện test trong quá trình phát triển ứng dụng CP
xCode Simulator: đây là simulator tích hợp sẵn trong xCode. Chúng ta có thể mở simulator này lên bằng cách chọn I/O -> External Display -> CarPlay…
CP Simulator: đây là một tool riêng biệt, nó mô phỏng lại môi trường của xe ô tô và phải cài ứng dụng hỗ trợ CP lên iPhone, sau đó kết nối với máy tính và mở CP Simulator lên (sau khi tải tool về thì mở folder Hardware -> CarPlay Simulator), tất cả các apps hỗ trợ CP trên iPhone sẽ hiển thị trên CP simulator. Link tải tool đây nhé, lưu ý các bạn chọn đúng tool mà xCode của mình đang sử dụng nhé
Trên đây là phần giới thiệu của mình về CP, phần tiếp theo mình sẽ chia sẻ việc implement một ứng dụng hỗ trợ CP.
Mình hi vọng bài viết có thể giúp ích được cho các bạn. Chúc các bạn thành công!
Quá dài chỉ để lấy ra 1 vài giá trị, chưa kể nó bị lặp lại ở nhiều nơi
Không set private cho configuration được vì như vậy consumer của API sẽ k lấy được giá trị của baseHeaders/baseURL.
Leak detail implementation cho consumer của API.
Để giải quyết vấn đề này, ta có thể sử dụng Dynamic Member Lookup
Table of contents
Giới thiệu
Dynamic Member Lookup với ExpressibleByStringLiteral
Dynamic Member Lookup với KeyPath
Kết luận
Giới thiệu
Apply this attribute to a class, structure, enumeration, or protocol to enable members to be looked up by name at runtime.
By Apple
Sử dụng Dynamic Member Lookup bằng cách:
Sử dụng @dynamicMemberLookup attribute cho class/struct/enum/protocol bạn muốn support
Khai báo subscript subscript(dynamicMember:). Kiểu dữ liệu của dynamicMember có thể là KeyPath, hoặc 1 kiểu dữ liệu conform ExpressibleByStringLiteral
Dynamic Member Lookup với ExpressibleByStringLiteral
Dòng @dynamicMemberLookup đánh dấu struct User để cho biết rằng nó hỗ trợ tính năng dynamicMemberLookup.
Phương thức subscript được sử dụng để xử lý truy cập vào thành viên của đối tượng. Tham số dynamicMember đại diện cho tên thành viên được truy cập, và phương thức sẽ trả về một chuỗi đơn giản là "Tuan" để minh họa cho việc truy cập thành viên.
user.name được truy cập, sử dụng cú pháp giống như truy cập vào một thuộc tính của đối tượng. Khi đó, phương thức subscript của struct User được gọi để xử lý việc truy cập thành viên và trả về chuỗi "Tuan".
Chúng ta cũng có thể implement nhiều subscript(dynamicMember) khác nhau cho cùng 1 đối tượng support dynamicMemberLookup
Tuy nhiên, đừng quá lạm dụng sử dụng subscript(dynamicMember:) với ExpressibleByStringLiteral. Nó có 1 vài nhược điểm:
Swift tất nhiên sẽ không gợi í cho consumer của class là phải gọi name/age để lấy thông tin. Chúng ta sẽ phải bảo với consumer là phải dùng dynamicMemberLookup để lấy thông tin => Bad API design.
Làm mất tính safety của Swift.
Having to be aware of internal implementation details is usually a bad sign when it comes to API design
Dynamic Member Lookup với KeyPath
Sử dụng Dynamic Member Lookup với KeyPath là 1 sự lựa chọn tuyệt vời khi nó vẫn giữ được tính safety của Swift, đồng thời vẫn có recommend cho user.
Bạn có thể thấy, Swift vẫn recommend sử dụng baseHeaders và baseURL cho consumer mà k leak detail implementation của API -> Good API design
Kết luận
DynamicMemberLookup được sử dụng rộng rãi trong các thư viện phần mềm như Alamofire, Combine, SwiftUI và nhiều thư viện khác để giảm thiểu việc viết mã boilerplate và tăng tính linh hoạt của ứng dụng.
Với DynamicMemberLookup, bạn có thể viết mã Swift hiệu quả hơn và tránh việc phải sao chép và dán nhiều mã lặp đi lặp lại.
DynamicMemberLookup với ExpressibleByStringLiteral giải quyết bài toán khi app cần tương tác với WebView khá tốt, vì ta phải handle javascript code. Đối với các case còn lại, đừng quá lạm dụng DynamicMemberLookup :3
Thông thường chúng ta có 3 cách dùng khi tạo 1 file custom UIView. Chúng ta hãy cùng làm theo cả 3 cách sau đây để xem ưu, nhược điểm của từng cái nhé.
A. Set File owner
B. Set Custom class
C. Set cả hai File owner và Custom class
Giờ hãy tạo 1 Tabbar Controller chứa 3 view controller để thực hành add custom view theo 3 cách trên nhé.
Cách A. Thêm custom view dùng File owner
– Trước hết, ta cần hiểu FIleOwner là gì? File owner là một controller object giúp ta kết nối code với các elements, UI trong file nib.
Bước 1: Tạo file xib và class “CustomViewA”
Views có thể được tạo bằng hai cách:
+ Tạo view bằng code, ta gọi hàm init(frame: CGRect)
+ Nếu dùng file nib, khi file nib được load sẽ gọi hàm init?(coder: NSCoder)
Nếu muốn support cả 2 cách, ta implement cả 2 hàm trên
Ở ví dụ này ta tạo custom view bằng cách dùng file xib chứ không dùng code
Tạo file xib CustomViewA.xib
Tạo file CustomViewA.swift. Ở file CustomViewA.swift, ta setup code như sau:
import UIKit
final class CustomViewA: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
initView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
initView()
}
private func initView() {
// Để load file nib, ta tạo một instance của UINib
// Ta set bundle là nil để dùng bundle default
let nib = UINib(nibName: "CustomViewA", bundle: nil)
// Khởi tạo contents trong file nib, objects là 1 array của tất cả top-level objects trong file nibs
let objects = nib.instantiate(withOwner: self, options: nil)
// Lưu ý: Vì list object trả ra từ file nib chưa thuộc view hierarchy nào nên ta phải addSubview vào CustomViewA
if let view = objects.first as? UIView {
view.backgroundColor = .clear
addSubview(view)
view.frame = bounds
view.backgroundColor = .orange
}
}
}
Bước 2: Set class cho file owner
Mở file xib, chọn File’s Owner, rồi set Custom class là “CustomViewA” vừa tạo
Bước 3: Mở “ViewControllerA” trong storyboard, kéo một view vào rồi set Custom Class cho nó là “CustomViewA”.
Chạy app và ta đã tạo được một custom view, kết quả như hình bên dưới.
Cách B. Thêm custom view dùng Custom Class
Bước 1:
Tạo file CustomViewB giống file CustomViewA như ở cách A
Bước 2:
Thay vì FileOwner như cách A, ta chọn View ở dưới, rồi set Custom Class là “CustomViewB”
Bước 3: Ta cũng kéo view “CustomViewB” vừa tạo vào ViewControllerB như bước 3 cách A, rồi chạy app
Oops!!! Cách này dùng thì lại bị crash. Tại sao lại thế???
Lý do vì khi load ViewControllerB, app sẽ call “init?(coder: NSCoder)” của custom view (CustomViewB), tiếp theo sẽ call “initView()“. Trong hàm này ta dùng “instantiate(withOwner)” để load file Nib.
Tuy nhiên, vì ở bước 2 ta đã set top-level view của file nib là class CustomViewB nên nó sẽ load lại file nib và call hàm “init?(coder: NSCoder)” một lần nữa. Cứ thế nó sẽ tạo 1 vòng loop vô hạn khiến app bị crash.
Để tránh trường hợp này, ta bỏ hàm “initView()” ra ngoài, không để trong “init?(coder: NSCoder)” or “init(frame: CGRect)” nữa.
Bước 4: Move hàm load xib ra bên ngoài View Controller (parent view controller)
import UIKit
class ViewControllerB: UIViewController {
var viewCustomB: CustomViewB!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
initView()
}
private func initView() {
let nib = UINib(nibName: "CustomViewB", bundle: nil)
let objects = nib.instantiate(withOwner: nil, options: nil)
if let firstView = objects.first as? CustomViewB {
firstView.backgroundColor = .red
viewCustomB = firstView
view.addSubview(viewCustomB)
viewCustomB.translatesAutoresizingMaskIntoConstraints = false
viewCustomB.widthAnchor.constraint(equalToConstant: 240).isActive = true
viewCustomB.heightAnchor.constraint(equalTo: viewCustomB.widthAnchor).isActive = true
viewCustomB.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
viewCustomB.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
}
Chạy app và ta đã tạo được một custom view, kết quả như hình bên dưới.
Như vậy ta vẫn có thể add custom view bằng cách B này, nhưng nó có nhược điểm:
Ta sẽ phải load file nib (CustomViewB.xib) mọi nơi mà ta muốn dùng nó và sẽ gây ra duplicate code.
Cách C. Thêm custom view dùng Custom Class và FileOwner
Bước 1: Tạo file CustomViewC.swift và CustomViewC.xib giống file CustomViewA như ở cách A
Bước 2: Tạo 1 button ở giữa view
Bước 3: Lần này, ta set CustomClass của view là “CustomViewC”, còn set FileOwner là parent class của nó (ViewControllerC)
Bước 4: Để logic load file xib ở parent class (để tránh crash như cách B) và kéo IBAction của button vào cả parent và custom view class
Khi tap button, ta thấy debug đều chỉ ra cùng là một object.
==> Ok vậy ta đã thử qua 3 cách để tạo một custom view, ta có thể thấy rằng cách A đầu tiên dùng FileOwner là tiện lợi nhất
Dễ reuse trong toàn app thông qua code, xib hoặc storyboards
Không dính crash, tránh duplicate code như cách B
Sau đây là một số tip mà các bạn chắc cũng đã biết =)))
Tip1: Ở trong file xib, bạn có thể chọn Size “Freeform” trong Attribute Inspector để resize view
Tip2: Trong cách A, ta có thể tạo một outlet contentView là top-level view của file xib, từ đó không cần phải get first object khi instantiate xib file nữa.
Bài viết có tham khảo nguồn từ 2 link dưới đây, ở đó họ sẽ giải thích chi tiết view là gì, được khởi tạo như thế nào, file nib là gì, cách dùng customview bằng code hoặc file nib…
Bản thân Flutter là một framework khá hoàn thiện đến mức việc tạo giao diện với Flutter trở nên dễ dàng và nhanh chóng đến mức bất ngờ! Điều này khiến lập trình viên Flutter cảm thấy việc lập trình mobile quá dễ (trừ khi họ là người đã từng nếm trái đắng từ khi code native). Mình cũng thấy dễ cho đến khi nhìn thấy giao diện ứa nước mắt mà designer gửi (đôi khi là kèm theo video animation siêu xịn…).
Thế bạn đã trang bị cho mình gì để ứng phó trước những design đẹp (hoặc k) nào? Bạn đã biết custom Slider trong Flutter chưa? Hay chỉ là những bé slider cũ mèn như này này này:
mặc địnhrange slidercupertino *hình ảnh chụp từ video youtube nên hơi mờ
Nhưng khi nhận được design thì trông như này
như này nèrồi như nàytrông cũng dễ?
Chắc ai nhận design cũng sẽ nghĩ: trông dễ phết, khó thì dùng thư viện (nhiều thư viện Slider quá mà). Có thể kể ra một số thư viện cho ai muốn dùng nè:
Đọc đến đây thì ai thích dùng thư viện có thể dừng lại và tham khảo 3 thư viện này. Đôi khi một số lại thấy thư viện thì quá là nặng thêm nhiều thứ lắt nhắt, rồi cho customize đủ thứ mà cái mình cần thì không có? Hoặc bạn muốn tự mình thử sức làm những slider này, thì tiếp tục đọc cách custom slider nào :3
Đầu tiên, chúng ta sẽ tìm hiểu về các thành phần của 1 slider và tên gọi của nó trong Flutter với thiết kế theo Material: (đoạn này cop trên document lười dịch nên nhờ bạn đọc tạm nhé)
The “thumb”, which is a shape that slides horizontally when the user drags it.
The “track”, which is the line that the slider thumb slides along.
The “value indicator”, which is a shape that pops up when the user is dragging the thumb to indicate the value being selected.
The “active” side of the slider is the side between the thumb and the minimum value.
The “inactive” side of the slider is the side between the thumb and the maximum value.
The “tick marks”, which are regularly spaced marks that are drawn when using discrete divisions.
The “value indicator”, which appears when the user is dragging the thumb to indicate the value being selected.
The “overlay”, which appears around the thumb, and is shown when the thumb is pressed, focused, or hovered. It is painted underneath the thumb, so it must extend beyond the bounds of the thumb itself to actually be visible.
Để thiết kế ra một Slider thì đầu tiên chúng ta sẽ xem đến các widget trực tiếp để tạo ra slider: Slider, RangeSlider, and CupertinoSlider(Flutter Widget of the Week) – YouTube. Tuy nhiên khi xem document (ờ lại là document, document của Flutter là tài liệu hữu ích nhất mà dev Flutter có thể tìm được bên cạnh source code open của nó) thì bạn phát hiện ra nó không có nhiều thứ liên quan đến giao diện cho lắm, ngoài việc cho phép đổi tí màu. Bạn bắt đầu tuyệt vọng.
một constructor của slidercó gì đó
và rồi cuối document, họ bảo, vẻ bề ngoài của nó thì dùng SliderThemeData từ widget SliderTheme hoặc một cái theme nào đó cha ông của nó mà có định nghĩa sliderTheme. Yeah XD
Bạn tiếp tục đi đến wiki của SliderThemeData xem tùy chỉnh được gì, ở đây nhé SliderThemeData class – material library – Dart API (flutter.dev). Có thể chỉnh được kha khá, hình tròn hình chữ nhật, bo góc (hoặc k), … bạn có thể dừng lại, đọc, và thử xem bạn có thể custom như nào (ảnh phía trên là 1 slider đã được 1 tác giả khác tạo ra nhờ tùy chỉnh các thuộc tính này).
Để rồi nhận ra rằng: nó không giống mấy cái design nhận được xíu nào 🙁
hình ảnh slider trong blog cuối bài
Thế làm sao để custom chúng nó như này? Hãy xem Flutter Team đã tạo ra Slider như nào đã nhé :3
ồ là một file hơn 3000 dòng code và rất nhiều tác giả
Nên cứ nghe theo thôi ha, họ bảo code mẫu rồi đấy, muốn custom thì xem mà học :3 họ bảo kế thừa mấy cái có sẵn này đi mà custom, nhưng custom sao thì họ không nói 🙁 nên t ở đây để giúp bạn đây hehe. Không dài dòng nữa, bắt đầu thôi :3
Khi bạn extend các lớp kể ở trên, bạn sẽ được trình gợi ý yêu cầu override hàm paint, đây là hàm để vẽ nên chúng nó. Trong hàm paint có một số thứ bạn cần chú ý: – Đầu tiên là PaintingContext context, ở đây bạn sẽ lấy được canvas ra và vẽ những gì bạn cần (context.canvas) – Tiếp theo là center với Thumb và parentBox với Track, bạn sẽ tìm được những điểm hữu dụng để vẽ – Tiếp theo là sliderTheme, từ đó bạn có thể lấy ra các thuộc tính khác mà bạn có thể truyền vào theme của slider (màu active, inactive…) – Cuối cùng, mn luôn luôn có thể tham khảo code open của Flutter xem họ làm như nào với các shape mặc định, hoặc tham khảo code của mình sau đây :3
// sử dụng
CustomSlider(
max: 100,
min: 10,
onValueChange: (_) { },
value: 30,
),
Mình đã custom cái slider này từ rất lâu rồi (nên code của nó mình không chắc là ổn lắm).
Và lí do mình viết bài này chủ yếu để nhớ kĩ hơn và muốn chia sẻ với mọi người khi mà chiều nay mình đã custom ra mấy cái extend SliderComponentShape, rồi ghép vào SliderTheme mà reload restart hoài UI không nhận :'( Mình rất bối rối muốn ném máy ra cửa sổ thì nhận ra nếu là RangeSlider thì cần extend RangeSliderTrackShape, RangeSliderThumbShape… (thêm Range cơ). Hóa ra do mình không đọc kĩ (tiện muốn kể chuyện InteractiveViewer ghê mà ai muốn nghe ib nhé :v)
Copy on Write (CoW) là 1 khái niệm ko hề mới trong Swift. Nó đã được Apple giới thiệu trong WWDC 2015 và được áp dụng từ iOS 7.0. Tuy nhiên thực chất CoW là gì và chúng có tác dụng gì? Hãy cùng tìm hiểu trong bài viết này nhé.
Table of contents
Copy on Write là gì
Kết luận
References
Copy on Write là gì?
Trong Swift, chúng ta có các kiểu Reference type và Value type. Nếu bạn gán một value type cho một biến hoặc pass nó như một parameter của function (không phải parameter kiểu inout) thì dữ liệu của value type này sẽ được copy. Lúc này, ta sẽ có hai value type có nội dung giống nhau nhưng trỏ đến hai địa chỉ bộ nhớ riêng biệt. Hôm nay ta sẽ bàn về Copy on Write – một cơ chế quan trọng trong việc tối ưu bộ nhớ của Swift.
Trong Swift, khi bạn có một khối lượng lớn các value type và muốn gán hoặc truyền chúng qua các function, nếu bạn copy tất cả dữ liệu sang một vị trí khác trong bộ nhớ thì sẽ gây ra hiện tượng lãng phí hiệu năng. Để giảm thiểu tình trạng này, Swift đã triển khai cơ chế Copy on Write cho một số kiểu dữ liệu là value type như array, dictionary,…
Hiểu 1 cách đơn giản, nếu bạn có 1 array có 1000 phần tử và bạn muốn copy mảng đó vào 1 biến khác, Swift sẽ không sao chép ngay lập tức cả 1000 phần tử này mà sẽ sử dụng đến cơ chế Copy on Write: Khi bạn trỏ 2 biến vào cùng 1 mảng, chúng đều trỏ vào cùng 1 địa chỉ ô nhớ, và chỉ đến khi bạn sửa đổi 1 trong 2 biến đó, swift mới tạo ra 1 bản copy mới để sửa và chỉ sửa trên bản copy đó và vẫn giữ nguyên biến còn lại. Bằng cách trì hoãn việc sao chép dữ liệu cho đến khi thực sự cần thiết, Swift đã đảm bảo được việc tối ưu được performance của hệ thống
Copy on Write ko phải là cơ chế mặc định cho tất cả các kiểu value type, mà chỉ được áp dụng cho 1 số kiểu như Aray, Collections,… Ngoài ra, với những kiểu value type mà bạn tự custom thì cũng ko có sẵn cơ chế này mà phải tự implement thêm.
Ví dụ về cách hoạt động của Copy on Write
import Foundation
func print(address o: UnsafeRawPointer ) {
print(String(format: "%p", Int(bitPattern: o)))
}
var array1: [Int] = [0, 1, 2, 3]
var array2 = array1
//Print with just assign
print(address: array1) //0x600000078de0
print(address: array2) //0x600000078de0
//Let's mutate array2 to see what's
array2.append(4)
print(address: array2) //0x6000000aa100
//Output
//0x600000078de0 array1 address
//0x600000078de0 array2 address before mutation
//0x6000000aa100 array2 address after mutation
Đây là 1 ví dụ đơn giản để chỉ cách hoạt động của Copy on Write. Trước hết, tạo biến array1 rồi sau đó gán aray2 bằng với array1. Khi chưa thực hiện thay đổi giá trị thì array2 vẫn trỏ vào cùng 1 địa chỉ ô nhớ với array1. Chỉ khi ta thay đổi giá trị của array2 thì nó mới được copy sang 1 địa chỉ ô nhớ khác, và giá trị mới sẽ trỏ vào địa chỉ ô nhớ này, còn array1 sẽ không có sự thay đổi gì.
Implement cơ chế Copy on Write cho các dạng value type tự tạo
Bạn có thể tự mình implement cơ chế Copy on Write cho các kiểu dữ liệu mà bạn tự custom. Đây là ví dụ trên OptimizationTips.rst trong repo chính của Swift
final class Ref<T> {
var val : T
init(_ v : T) {val = v}
}
struct Box<T> {
var ref : Ref<T>
init(_ x : T) { ref = Ref(x) }
var value: T {
get { return ref.val }
set {
if (!isUniquelyReferencedNonObjC(&ref)) {
ref = Ref(newValue)
return
}
ref.val = newValue
}
}
}
// This code was an example taken from the swift repo doc file OptimizationTips
// Link: https://github.com/apple/swift/blob/master/docs/OptimizationTips.rst#advice-use-copy-on-write-semantics-for-large-values
Đoạn code trên sử dụng loại reference type để triển khai cho kiểu giá trị dạng generics. Về cơ bản, đây là 1 warrper quản lý loại reference type và chỉ trả về 1 instance mới nếu giá trị được tham chiếu không là duy nhất. Nếu không, nó chỉ thay đổi giá trị của kiểu tham chiếu.
Kết luận:
Copy on Write là 1 cơ chế rất thông minh để tối ưu hoá việc copy giá trị của các kiểu value type. Đây là 1 cơ chế được sử dụng rất nhiều Swift, dù hầu như chúng ta ko nhìn thấy nó 1 cách rõ ràng vì chúng đã được thực hiện trên các thư viện chuẩn của Swift. Nhưng chúng ta nên biết để có thể tận dụng tối đa lợi ích mà Copy on Write mang lại.