Tag Archives: Swift Programming

Swift String กับภาษาไทย

วันก่อนๆ มีคนโพสในกลุ่มของ iOS Dev Thai เกี่ยวกับปัญหาภาษาไทยของ swift string ว่ามันไม่สามารถค้นหาคำได้อย่างที่ต้องการ ยกตัวอย่างเช่น

จากตัวอย่างง่ายๆนี้จะเห็นว่า เราสามารถหาคำว่า “ชี” ได้  แต่ในขณะที่ “ช” ตัวเดียวนั้นกลับหาไม่เจอ

ว่าแต่ทำไม มันหาไม่เจอละ ?

ปัญหาของมันก็คือว่า Swift นั้นใช้ Grahpheme ซึ่งมันจะ เอา สระ วรรณยุกต์ และ ตัวอักษร มารวมเป็นตัวเดียวกัน คือพูดง่ายๆว่ามันนับตามช่องไฟของตัวอักษร

ถ้าหากเรานับจำนวนตัวอักษร มันก็จะนับได้แบบนี้

ก็จะเห็นว่า ไม่ว่าจะเป็น ก , กู , กู้ มันก็นับได้ 1 เหมือนกัน

ในภาษาจำพวกภาษาอังกฤษ ตัวอักษณ e กับ é เขามองเป็นตัวอักษรคนละตัวอักษร

จากโค้ดข้างบน มันก็ทำงานถูกละ เพราะ e กับ é มันคนละตัวอักษร

แต่ในกรณีของภาษาไทย  เราไม่ได้นับ ช กับ ชี เป็นตัวอักษรคนละตัวกัน แต่เราบอกว่า มันคือ

  • ช ที่มี สระ อี

ดังนั้นแล้ว มันจึงเกิดปัญหาว่า เมื่อใช้เมธอด อย่าง contains เนี่ย มันเจอ “ชี” แต่กลับไม่เจอ “ช” เพราะมันมองว่าเป็นคนละตัวกัน

เราจะแก้ปัญหานี้ยังไงดี

วิธีการแรก คือ ใช้เมธอด localizedStandardCompare เมธอดนี้ มีใช้ใน iOS9

เมธอดนี้ อาจจะแก้ปัญหาเรื่องที่ว่า มันหา “ช” ไม่เจอ

แต่ถ้าเราใช้เมธอดนี้ในการหา string มันก็อาจจะทำงานไม่ค่อยถูกมากนัก เพราะเมธอด มันทำการเปรียบเทียบ โดยที่ไม่สนใจ วรรณยุกต์ และ สระ

อย่างเช่นมี “ก” ตัวเดียว แต่ถ้าหาด้วย “กู้” มันก็จะบอกว่า เจอเหมือนกัน เช่นตัวอย่างนี้

 

วิธีการที่สองคือ เปรียบเทียบ ตาม character ไปทีละตัว โดยใช้เมธอด range(of:) แล้วก็กำหนด option ให้เป็น literal ซึ่งเราอาจจะเขียนออกมาเป็น extension ง่ายๆ แบบนี้

จะเห็นว่าผลลัพธ์นั้นให้ความถูกต้องมากกว่าวิธีการแรก
วิธีการแรก อาจจะเหมาะกับการค้นหาแบบ คร่าวๆ อย่างเช่นการ search แต่ถ้าหากต้องการผลลัพธ์ที่ถูกต้องวิธีที่สองน่าจะเหมาะสมกว่า

หวังว่าจะช่วยให้หลายคนหายสงสัยเกี่ยวกับ String ใน Swift นะครับ

Swift 3 @escapeing & @nonescape closure

วันก่อนๆ ได้โหลด XCode 8 มาใช้งานซึ่งมันก็มาพร้อมกับ Swift3 แน่นอนว่า ภาษามันเปลี่ยนแปลงไป ไม่ว่าจะเป็น ชนิดของข้อมูลแบบใหม่ หรือว่า syntax ภาษาใหม่ๆ และหนึ่งในนั้นคือ closure

ถ้าหากเคยเขียน โคลเชอร์ ใน Swift2 มา ก็อาจจะเห็น คีย์เวิร์ด @nonescape ผ่านตามาบ้าง

ใน Swift2 พารามิเตอร์ที่เป็นโคลเชอร์จะมีค่าเริ่มต้นเป็น escape closure ถ้าหากเขียน @nonescape กำกับไว้ ก็จะเป็นการบอกว่า closure นี้เป็น nonescape  อย่างไรก็ตาม keyword นี้ได้นำออกไปจาก Swift 3 เป็นที่เรียบร้อย เนื่องจากว่า ใน Swift3 นี้ได้กำหนดไว้ว่า โคลเชอร์มีค่าเริ่มต้นเป็น nonescape

ถึงตรงนี้หลายคนอาจจะเกาหัว แล้วร้องว่า What ? เชี่ยยยย อะไรเนี่ย .. nonescape closure , escape closure มันคืออะไรว่ะ ?

ใจเย็นๆ แป๊ะอย่าร้อง ผมจะอธิบายให้ฟังแบบง่ายๆก็แล้วกัน

closure ที่ถูกส่งเข้าเป็นพารามิเตอร์ในฟังก์ชัน ถ้า closure ถูกเรียกหลังจากทีฟังก์ชันทำงานเสร็จได้ เรียกว่า escape closure

เพื่อให้เห็นภาพง่ายๆ ว่า escape  คืออะไรก็ดูตัวอย่างต่อไปนี้ละกัน

สมมติว่า ผมเขียนฟังก์ชัน download ไฟล์รูป ซึ่งเรียกใช้ฟังก์ชัน loadData  ของ http ที่เป็น asynchronous call ประมาณนี้

เมื่อฟังก์ชัน loadData ทำงานเสร็จ ก็จะเรียกโคลเชอร์ completion  พร้อมกับส่ง image กลับไป

การทำงานของ completion ลักษณะแบบนี้ จะเรียกว่า escape เพราะว่า มันทำงานได้ แม้ว่า ฟังก์ชัน loadProfileImage จะทำการ return (แต่ http.loadData จะยังทำงานต่อไป และแน่นอนว่า completion ก็จะถูกเรียกหลังจากที่ http.loadData ทำงานเสร็จ) สรุปคือว่า แม้ว่า loadProfile จะทำงานเสร็จ มันก็ยังเรียก completion

โค้ดตัวอย่างที่เขียนไป สามารถทำงานได้ปกติใน Swift 2 เพราะ โคลเชอร์ได้กำหนดให้เป็น escape มาตั้งแต่แรกเริ่ม ไม่ต้องเขียนอะไรเพิ่มเติมทั้งสิ้น

แต่ถ้าหาก เอาโค้ดนี้ไปใช้งานกับ swift 3 ตัว  completion จะไม่ถูกเรียก เนื่องจาก ฟังก์ชัน loadProfileImage จะทำการ return ก่อน ที่ฟังชัน loadData จะทำงานเสร็จ (เพราะเป็น asynchronous ไม่ต้องรอให้ทำงานเสร็จก็ return ได้)

เมื่อ @nonescape ได้นำออกไป มันก็ถูกแทนที่ด้วย  @escaping

@escaping ใช้กำหนดให้โคลเชอร์เป็นแบบ escape คือทำงานได้แม้ว่าตัวฟังก์ชันที่เรียกมันจะทำงานเสร็จไปแล้ว

ดังนั้นหากเราต้องการให้มันเรียก completion หลังจากที่ loadData ทำงานเสร็จ ก็ต้องกำหนดให้เป็น @escaping ดังชั่นตัวอย่าง

และจะเห็นว่า @escaping นั้นเป็น Type ประเภทหนึ่ง เช่นเดียวกับ String , Int ไม่ได้เป็น parameter attribute  เหมือนอย่างแต่ก่อน

ทำไมต้อง non escape ?

Swift3 เปลี่ยนมาใช้ non escape closure ก็เพราะเรื่องประสิทธิภาพ รวมไปถึงการจัดการหน่วยความจำ เนื่องจากว่า non escape นั้น จะไม่ทำการเก็บ closure ไว้  ปัญหา retain cycle ก็น้อยลงไป  ข้อดีอีกอย่างคือเราไม่ต้องเขียน self ถ้าหาก closure เรียกฟังก์ชันของตัวเอง

นอกจากนี้แล้ว การเขียน closure ใน Swift3 นั้นจะไม่สามารถกำหนด argument label ได้

ยกตัวอย่างเช่นใน Swift2 เราอาจจะเขียนฟังก์ชั่น แบบนี้

แต่ใน Swift 3 จะแจ้งว่า error

untitled-2

เพราะใน Swift3 นั้น closure ได้กำหนดว่า ไม่ให้มี argument label ดังนั้นแล้ว เราจึงต้องเพิ่ม _ เข้ามาในโค้ด ดังเช่นตัวอย่าง

ลองทำความเข้าใจ และศึกษา Swift3 , Closure กันครับ หวังว่าจะช่วยให้หลายคนเข้าใจ closure มากขึ้น

Swift Programming ตอนที่ 11 : Introduction to iOS Programming

วันนี้เราจะเรียนรู้ และทำความเข้าใจ basic concept ต่างๆที่ใช้ในการเขียนโปรแกรม iOS  เช่น MCV ,  Delegate , Action-Target และการทำงานของ  Window

Closure Parameter

ช่วงนี้งานเยอะมาก ไม่ค่อยได้ update และอัด video tutorial สักเท่าไหร่ ช่วงนี้หยุดหลายวันมีเวลาได้เขียน blog บ้าง เอาละเข้าเรื่องเลยดีกว่า

Closure ในภาษา Swift ก็เหมือนกับ Block ในภาษา Objective-C อธิบายสั้นๆมันก็คือ รูปแบบหนึ่งของฟังก์ชันที่ต้องไม่มีชื่อ เมื่อเราเขียนโปรแกรมด้วยภาษา swift สักระยะหนึ่ง ก็จะได้ใช้ closure อย่างแน่นอน สำหรับโพสต์นี้ผมคงไม่ได้มาอธิบายว่า อะไรคือ closure แต่จะพูดถึง สไตล์ การเขียนเพื่อทำให้โค้ดมันอ่านง่ายมากขึ้น สำหรับคนที่เพิ่งหัดเขียน swift ก็ควรจะศึกษา closure ไว้นะครับ เพราะยังไงก็ต้องได้ใช้แน่นอน

ปกติการใช้ closure มักจะเห็นได้บ่อยกับฟังก์ชันคือ ตัวฟังก์ชันมักจะรับ closure เข้ามาเป็นพารามิเตอร์ เช่น ฟังก์ชัน sort ของ Array

เมื่อพิจารณาจากฟังก์ชัน sort ก็จะเห็นว่าตัวแปร isOrderedBefore นั้นเป็น closure ที่รับพารามิเตอร์สองตัว เป็นสตริง และส่งค่ากลับเป็น Bool เมื่อเรียกใช้งานฟังก์ชัน sort ก็จะมีลักษณะดังนี้

โดยทั่วๆไป ฟังก์ชันที่มี closure เป็นพารามิเตอร์ตัวสุดท้าย มักจะเขียนในลักษณะ trailing closure ดังนี้

การใช้ trailing closure นี้ก็ทำให้โค้ดสั้น กระชับ มากขึ้น

ในภาษา swift การใช้ closure นั้นมีประโยชน์มาก และเขียนลดรูปให้สั้นลงได้หลายแบบ แต่อย่างไรก็ตาม ถ้าหากฟังก์ชันรับพารามิเตอร์ ที่เป็น closure สัก 2 ตัว ก็จะเริ่มเกิดปัญหา คือ โค้ด มันอ่านยากกกกกก เช่นฟังก์ชัน animateWithDuration ของ UIView ซึ่งมีหน้าตาดังนี้

จากตัวอย่างจะเห็นว่า ฟังก์ชันนี้รับพารามิเตอร์มาด้วยกัน 3 ตัวคือ

  1. duration: NSTimInterVal
  2. animation: () -> Void
  3. completion: ((Bool) -> Void)?)

เมื่อเรียกใช้งาน โค้ดจะมีลักษณะดังนี้

ในครั้งแรกที่เห็น ผมเชื่อว่า หลายคน งง บอกตรงๆว่าแรกๆ ผมก็งง ถ้าคุณไม่งง แสดงว่าคุณได้เข้าใจ closure ในระดับดีมาก แต่สำหรับคนที่งงว่า โค้ดมันทำอะไร ถ้าลองถ้าพิจารณาอย่างคร่าวๆ ก็พอจะเห็นว่า โค้ดนี้  view จะเปลี่ยนขนาดของ frame จากนั้น view ก็จะเรียกเมธอด removeFromeSuperView

ทำไงดี ให้โค้ดมันอ่านง่ายขึ้น ?

คือบางคนอาจจะใช้ tab เว้นวรรคเพื่อช่วยให้โค้ดมันดูเป็นระเบียบอ่านง่ายขึ้น แต่ในลักษณะแบบนี้ ผมเห็นว่า tab มันไม่ค่อยได้ช่วยอะไรสักเท่าไหร่ และถึงเราจะเขียนแบบ trailing closure มันก็ได้แค่พารามิเตอร์สุดท้าย ผมก็ยังมองว่ามันอ่านยากอยู่ดี

สำหรับตัวผมเอง มีเทคนิค 2 อย่าง ที่คิดว่า ทำให้โค้ดมันอ่านง่ายขึ้น

  • อย่างแรกคือตัวแปร closure ขึ้นมา
  • สองก็คือประกาศฟังก์ชัน

เนื่องจาก swift นั้นได้ให้ function/closure เป็น first class variable ดังนั้น เราจึงสามารถประกาศตัวแปรให้เป็น function/closure ได้ ซึ่งการประกาศตัวแปรให้เก็บ closure นี้ก็จะมีการประกาศตัวแปรลักษณะแบบนี้

เมื่อนำโค้ด ฟังก์ชัน dissmiss มาเขียนใหม่ก็จะได้ว่า

ส่วนวิธีที่สอง ก็คล้ายกับวิธีการแรก คือใช้การประกาศฟังก์ชันแทน

แม้ว่าโค้ดที่เขียนใหม่ทั้งสองจะเขียนยาว กว่าเดิม แต่จะเห็นได้ว่า โค้ด ที่เขียนไปนั้น อ่านง่ายกว่าเดิมเยอะมาก

สุดท้ายแล้วสไตล์ในการเขียนโปรแกรมนั้นมันก็ขึ้นอยู่กับแต่ละคนครับ บางคนอาจจะไม่ชอบสไตล์แบบนี้เพราะมันเขียนเยอะกว่าเดิม หรือถ้าใครมีเทคนิคอื่นๆก็แนะนำกันมาได้ครับ