"มีเพื่อนดี คนเดียว ถึงจะน้อย ดีกว่าร้อย เพื่อนคิด ริษยา..."
บันทึกการพอร์ตแอพฯจาก Swift 1.2 บน Xcode 6.3 ไปเป็น Swift 2.0 บน Xcode 7
17 Jun 2015 14:47   [7687 views]

บล็อกนี้ ... Geek นะจ๊ะ

Dev แอพฯ nuuneoi.com for iOS มาก็สักพัก (ไม่เสร็จสักที) ความตั้งใจหลักก็เพื่อทดสอบ Swift ในการทำแอพฯจริงออก Production เรียกว่าถ้า Swift ยังทำแอพฯจริงออกมาไม่รอดหรือติดปัญหาอะไรก็ไม่ต้องปล่อยอ่ะแอพฯนี้ รวมถึงถ้าอนาคตมีอะไรเปลี่ยนแปลงแล้วโค้ดที่ทำเกิดพังก็เขียนใหม่อ่ะ จะไม่กลับไปใช้ Objective C เป็นอันขาด นั่นคือความตั้งใจ เพราะอยากเจอปัญหาจริงในการทำงานกับ Swift นั่นเอง (งานสายนี้ ยิ่งเจอปัญหายิ่งดี) อยากโตไปกับ Swift หวังจะเข้าใจความคิดของ Apple

ก็ทำโน่นทำนี่มาเรื่อยเท่าที่มีเวลา รื้อ Structure โค้ดแบบรื้อแล้วรื้ออีกเพื่อหา Best Practice ในแบบของตัวเองทั้งเรื่องของ CocoaTouch และ Swift เพื่อให้ใช้ได้แบบยาวๆยาวๆยาวยาวยาวววว อีกสองปีก็ยังใช้โครงนี้ได้อะไรงี้ ผลคือรื้อแอพฯไปรวม 5 รอบแล้ว (รวมรอบนี้)

การรื้อครั้งล่าสุดเป็นการรื้อครั้งใหญ่ที่สุดเพราะเริ่มตกตะกอนในแนวทางของ Apple ที่มีกับ Swift แล้ว โดยคิดว่าน่าจะเป็นการรื้อครั้งสุดท้ายไปสู่ Swift 2.0 บน Xcode 7 beta ที่เพิ่งปล่อยมาตอน WWDC ที่ผ่านมา

ซึ่งโชคดี๊โชคดี Developer Account หมดอายุพอดีเมื่อเดือนที่แล้ว แล้วก็เผอิ๊ญเผอิญไม่มีตังค์จะต่ออายุพอดี แล้ว Apple ก็ใจดี๊ใจดีให้ Sideload แอพฯผ่าน Xcode 7 ฟรีโดยไม่ต้องมี Developer Account แล้ว !

เป็นแรงผลักดันให้พอร์ตแอพฯไป Swift 2.0 ได้เป็นอย่างดี 555 (แค่ 3,590 บาทก็เอา เอาสิ)

บล็อกนี้เลยเอาบันทึกเรื่องราวที่เจอตอนพอร์ตไป Swift 2.0 บน Xcode 7 beta พร้อมรื้อโครงสร้างเพื่อความยั่งยืนของโค้ดไปพร้อมๆกัน (เป็นการตัดสินจากหลังจากเจอความเปลี่ยนแปลงของ Swift 2.0 และพอมองออกว่าจะเป็นยังไงต่อไป) เรียกว่าต่อจากนี้ต่อให้ Swift 3.0, Swift 4.0, Swift 5.0 ออกมาก็ไม่ใช่ปัญหาอีกต่อไป

เผื่อใครลองย้ายมา Swift 2.0 แล้วติดปัญหาอะไร ก็หวังว่าข้อมูลเหล่านี้อาจช่วยได้ครับและเผื่อเป็นแนวทางสำหรับคนที่จะทำแอพฯเป็นตัวเป็นตนจาก Swift หลังจากนี้ =)

อย่ากดอัพเดต Project Settings ตัวใหม่

คำเตือนอย่างแรก อย่ากดอัพเดต Project Settings ตามคำแนะนำของ Xcode 7 Beta เพราะลองมาแล้วสองรอบ อัพเดตแล้วพังยับ โน่นนี่หายเกลี้ยง โปรดระวังไว้ ...

รื้อไลบรารี่ที่เขียนด้วย Swift ออกทั้งหมด ใช้ตัวที่เขียนด้วย Objective C เท่านั้น

(ขั้นตอนนี้แนะนำให้ทำให้เสร็จตั้งแต่บน Xcode 6.3 จนมันรันได้ก่อน แล้วค่อยไปทำต่อบน Xcode 7 beta ไม่งั้นมันจะมีปัจจัยเยอะเกิน)

เราดูดไลบรารี่มาฝังแอพฯผ่าน Cocoapods ที่ผ่านมาก็ดูดไลบรารี่ที่เขียนด้วย Swift เพื่อหวังว่าจะทำทุกอย่างให้เป็น Swift (อยากรักก็ต้องเสี่ยง)

แต่ก็พบปัญหาอย่างนึงคือ ทุกครั้งที่ Swift ออกเวอร์ชั่นใหม่ ไลบรารี่พวกนี้ล้วนมีปัญหาโดยพร้อมเพรียงกันหมด จะไล่แก้ก็ไม่ไหว ไลบรารี่ร้อยพ่อพันแม่มาก

ซึ่งตรงนี้เป็น Pain มาโดยตลอด เลยได้ข้อสรุปแล้วว่าการใช้ไลบรารี่ที่เขียนด้วย Swift นั้นเปรียบเสมือนระเบิดเวลาที่ทันทีที่ Apple กดระเบิด แอพฯเราก็จะมีปัญหาวอดวายทันที

อีกเรื่องที่อาจจะไม่ค่อยสำคัญคือคนที่เขียนไลบรารี่ด้วย Swift ชอบ Base iOS ไว้ที่ iOS 8 หากคนทำแอพฯให้สนับสนุน iOS 7 ก็จะไม่ค่อยสะดวกเท่าไหร่ [ส่วนตัวคิดว่าไม่ต้องแคร์ iOS 7 แล้วแล้ว ตามข้อมูล Adoption Rate ล่าสุดตามภาพด้านล่างนี้ เหลือแค่ 15% เอง]

ด้วยเหตุผลที่กล่าวมานี้ จึงได้ข้อสรุปว่า "ไม่ควรใช้ไลบรารี่ที่เขียนด้วย Swift ไม่ว่ากรณีใดๆ" และตัดสินใจ "รื้อไลบรารี่ที่เขียนด้วย Swift ออกทั้งหมด" แล้วใช้เฉพาะตัวที่เขียนด้วย Objective C ดั้งเดิมแทน (ซึ่งจะเอาอะไรก็มีอ่ะ)

คราวนี้ก็ติดต่อกับไลบรารี่เหล่านั้นผ่าน Bridging Header เอาตามท่าด้านล่างนี้

1) คลิกเมาส์ขวาที่ชื่อโปรเจค กด New File แล้วสร้าง Header File ขึ้นมาหนึ่งไฟล์โดยตั้งชื่อตาม Name Convention ที่แอปเปิ้ลแนะนำว่า ProjectName-Bridging-Header.h

2) ไปที่ Build Settings แล้ว Search หา Bridging

3) ใส่ชื่อไฟล์ Header ที่สร้างขึ้นมาไว้ในช่อง Objective-C Bridging Header

จากนั้นถ้าดึง Library อะไรเข้า Pod ก็เอามาใส่ใน Header ตัวนี้ไว้ด้วย เช่นถ้าดึง AFNetworking มา

pod 'AFNetworking', '~> 2.0'

ก็ไปใส่ #import ไว้ใน Header File ไว้แบบด้านล่างนี้ระหว่าง #define และ #endif

#ifndef nuuneoi_com_nuuneoi_Bridging_Header_h#define nuuneoi_com_nuuneoi_Bridging_Header_h#import "AFNetworking/AFNetworking.h"#endif

ถ้ามีไลบรารี่อะไรเพิ่มอีกก็ใส่ๆเข้าไป แค่นี้ไลบรารี่เราก็จะเอาไปใช้ใน Swift ได้แล้นนนน ส่วนต้องใส่อะไร ... ต้องใช้ใจมองกันหละครับงานนี้ ฮ่าๆๆ บางไลบรารี่ไฟล์เดียวจบ บางไลบรารี่ต้องหลายไฟล์หน่อย แล้วแต่สถานการณ์

Syntax เปลี่ยน

มีเยอะเหมือนกันนะ ดู Release Note ได้เพื่ออ้างอิง เป็น Resource ที่ช่วยได้มากที่สุดละ โปรเจคนี้ก็เจอแก้เยอะอยู่ ไล่แก้ไปร่วมครึ่งชั่วโมง (หลักๆเพราะหาไม่เจอว่าต้องแก้ยังไง)

- พวก Options Mask เปลี่ยนจาก | เป็นรูปแบบ [, ] เช่น

view!.autoresizingMask = .FlexibleWidth | .FlexibleHeight

เปลี่ยนเป็น

view!.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]

ไม่ค่อยชิน แต่ก็สวยขึ้นนะ เอาน่า เดี๋ยวก็คงชิน ...

- การ for NSDictionary แบบบังคับ Type จะต้องประกาศตามรูปแบบของ Swift 2.0 จากเดิม

for (key, val: AnyObject) in dict {    ...}

เป็น

for (key, val): (AnyObject, AnyObject) in dict {    ...}

- พวก Collection ต่างๆ คำสั่ง size และ count จากเดิม

let a = size(something)let b = count(something)

ก็เปลี่ยนไปเป็น Property แทน

let a = something.sizelet b = something.count

- คำสั่ง constraints() ของ UIView จากเดิม

class CustomView: UIView {   ...   var constraints = self.constraints()   ...}

ก็กลายเป็น Property ด้วยเช่นกัน

class CustomView: UIView {   ...   var constraints = self.constraints   ...}

- อันไหนเป็น var แล้วไม่มีการเปลี่ยนแปลงค่าหลังจากนั้น Xcode ก็ Encourage ให้เปลี่ยนเป็น let ด้วย (แค่ Warning แต่ก็รำคาญตา เปลี่ยนให้หมดเลยละกัน)

- ระบบ Error Handling ตอนนี้เปลี่ยนจากการรับค่า Error ทาง Parameter เช่น

var error: NSError?let response: AnyObject? = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(), &error)if error != nil {  // Tah Dahhh}

มาเป็น do-try-catch แทน (อันนี้ดีแต่ก็ต้องเปลี่ยน Flow เยอะทีเดียวเพื่อรองรับ Pattern ใหม่)

do {    let response: AnyObject? = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions())    // Do something with response} catch let error as NSError {    // Tah Dahhh    return}

- ใน Swift 2.0 มีการเพิ่ม Preprocessor ชื่อ #available มา (ซึ่งถือว่าเจ๋งมาก รอมานานแล้ว) เพื่อแบ่งโค้ดตามเวอร์ชั่น

if #available(iOS 9, *) {    // do iOS 9 stuff} else {    // do iOS 8 and lower stuff}

โค้ดไหนที่เผลอเรียกใช้แล้วดัน Require iOS Version ที่สูงกว่า Deployment Target iOS Version มันก็จะขึ้นมาบังคับให้เราใส่ #available ไม่งั้นรันไม่ได้ (และทำให้รู้ว่าที่ผ่านมาบน Xcode 6.3 มีบางคำสั่งที่ไม่ควรจะใช้งานได้แต่ดันเรียกได้ อันตรายมาก -0-)

ปิด App Transport Security (ATS)

งาน WWDC ที่ผ่านมา Apple เปิดตัว App Transport Security พร้อม iOS 9 ตามด้านล่างนี้

App Transport Security (ATS) lets an app add a declaration to its Info.plist file that specifies the domains with which it needs secure communication. ATS prevents accidental disclosure, provides secure default behavior, and is easy to adopt. You should adopt ATS as soon as possible, regardless of whether you’re creating a new app or updating an existing one.

If you’re developing a new app, you should use HTTPS exclusively. If you have an existing app, you should use HTTPS as much as you can right now, and create a plan for migrating the rest of your app as soon as possible.

ภาษาคนก็คือระบบจะบังคับให้แอพฯติดต่อ Server ผ่าน HTTPS เท่านั้น เพื่อป้องกันข้อมูลรั่วไหลโดยไม่ตั้งใจผ่าน HTTP ถ้าเผลอติดต่อผ่าน HTTP จะเกิด Error ว่า Domain Name Error อะไรทำนองนี้

ถึง ATS จะเขียนอยู่ในส่วนของ iOS 9 แต่พอเอาเข้าจริง ตอนเอาแอพฯไปรันบน iOS 8 ก็ติดปัญหาไม่สามารถติดต่อ Server ผ่าน HTTP ได้เหมือนกัน เลยคิดว่า ATS อาจจะไม่ได้อยู่กับ iOS 9 Runtime แต่มากับ iOS 9 SDK ทำให้ทุกแอพฯที่คอมไพล์ด้วย iOS 9 SDK น่าจะพ่วง ATS ไปด้วย

สุดท้ายเลยต้องไปปิด ATS ด้วยการเพิ่มบรรทัดด้านล่างนี้เข้าไปในไฟล์ Info.plist

<key>NSAppTransportSecurity</key><dict>	<key>NSAllowsArbitraryLoads</key>	<true/></dict>

เรียบร้อย จากนั้นแอพฯก็จะสามารถติดต่อกับ Server ผ่าน HTTP ได้อย่างไม่มีปัญหาแล้วครับ

ข้อมูลจาก Five Minute Watch Kit

แก้ปัญหา Pods Framework ไม่ Deploy ไปด้วย

จริงๆเป็นปัญหาคลาสสิก แต่ก็จดไว้ ชอบลืมว่าต้องแก้ยังไง -_-

ถ้าเจอปัญหา dyld ขึ้นว่าโหลด Pod Framework ไม่ได้ แปลว่า Xcode ไม่ได้ก็อปไฟล์ที่คอมไพล์ในโปรเจค Pod เอาไปลงในเครื่องจริงนั่นเอง มันก็เลย Dynamically Load ไม่ได้ วิธีแก้ก็คือบังคับให้มันก็อปปี้ด้วยการเพิ่มขึ้นตอน Copy Files ลงไปใน Build Phases ของโปรเจคแอพฯเรา(ไม่ใช่โปรเจค Pod) ตามนี้

อย่างไรก็ตาม ใครไม่ได้ใช้ Dynamic Framework (ก็คือไม่ได้ใส่บรรทัดว่า use_frameworks! ไว้ใน Podfile) ก็ไม่ต้องทำขั้นตอนนี้ครับ เพราะมันจะเข้าไปเป็น Static Libraries พ่วงไปกับแอพฯอยู่แล้ว =)

ผลการทำงาน

Port มายัง Swift 2.0 สำเร็จลุล่วงไปด้วยดี ก็เลยลองฟีเจอร์ใหม่ที่สามารถ Sideload แอพฯลงเครื่องจริงโดยไม่ต้องจ่ายเงินค่า Developer Account ปีละ 3,590 บาทละ ซึ่งไม่ได้มีอะไร แค่สั่งรันแล้วก็กำหนด Team ให้กับทุก Library ที่ดึงเข้ามา ทุกอย่างก็เป็นไปด้วยดี ลื่นสบาย =)

อันนี้ลองบน iPod Touch สเปคโบร้าณณณโบราณ เค้าบอกว่าถ้าลื่นบทรุ่นนี้ รุ่นอื่นก็จะไม่มีปัญหา ... ไว้มีโอกาสจิไปลองกับรุ่นอื่นดูมั่ง

พร้อมยังกับภาษา Swift ในการเขียนแอพฯจริง?

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

แต่สิ่งที่ต้องเตรียมรับมือคือการเปลี่ยนแปลง

คำแนะนำที่ตกตะกอนจากการเขียนแอพฯด้วย Swift แล้วผจญการเปลี่ยนแปลงจาก Apple มาโดยตลอดก็เลยคือ

- อย่าใช้ไลบรารี่ที่เขียนด้วย Swift ไม่ว่ากรณีใดๆ

- โค้ดที่เขียนด้วย Swift ทั้งหมดต้องเป็นโค้ดที่เราควบคุมเองได้

- ดังนั้นคุณต้องเข้าใจภาษา Objective C อยู่ดีครับ ถึงจะเขียนไม่ได้ เขียนไม่คล่อง แต่ก็ต้องอ่านออกและเข้าใจ เนื่องจากตอน Bridge Library มาใช้บน Swift คุณจะได้ใช้ถูก (แต่ตอน Bridge มันจะมี Auto Suggest ในรูปแบบของ Swift อยู่แล้ว เพียงแต่ Document บนเว็บจะยังเป็น Objective C อยู่)

- พอโครงสร้างเป็นแบบที่ว่าแล้ว หากอนาคตมีการเปลี่ยน Syntax เป็น Swift 2.1, 2.2 หรือ 3.0, 4.0 ก็จะไม่ใช่ปัญหาแล้วเพราะโค้ดที่ต้องแก้ไขจะเป็นโค้ดที่เราเป็นคนเขียนขึ้นมาเอง และเอาจริงๆตั้งแต่ 1.2 มา Syntax ก็ถือว่านิ่งแล้ว ที่เปลี่ยนไปจะเป็นแค่สิ่งเล็กๆน้อยๆ หรือถ้าเปลี่ยนใหญ่ก็มี Workaround ในการพอร์ตไม่ยาก (เช่น do-try-catch อันนี้ถือว่าเปลี่ยนใหญ่)

- ใช้ ? ให้เยอะๆ ใช้ ! ให้น้อยๆ ...

สำหรับแอพฯ nuuneoi.com for iOS ก็ ... รอไปก่อนครับ ยังหาเงินสมัคร Developer Account ไม่ได้ 555 (เมื่อไหร่จะยกเลิกค่ารายปีฟระ ดูแอนดรอยด์เป็นตัวอย่างโน่นนนน)

และหลังจากกลับมาเขียนแอพฯ iOS แบบจริงจังก็สามารถบอกได้ละว่า iOS ทำ Dev Tools มาแย่กว่า Android แล้ว ตอนนี้ Android Studio ดีกว่า Xcode อย่างสัมผัสได้ รวมถึงการออกแบบ UI เพื่อแก้ปัญหา Fragmentation อย่างเรื่อง Auto Layout / Adaptive UI ก็ทำมาแย่กว่า Android อีกเช่นกัน ระบบ Constraint/Size Class ไม่ Friendly เลย พอทำอะไรเยอะๆซับซ้อนๆก็อ่านไม่รู้เรื่องแล้ว เทียบกับระบบ Layout/Configuration Qualifier ของ Android แล้วเรียกว่าฟ้ากับเหวเลย Apple ทำมาได้แย่กว่า Android เป็นเท่าตัวครับเรื่องนี้ แต่ก็อย่างว่าแหละ Android มี Pain เรื่อง Fragmentation มาตลอดนี่นา ตอนนี้ก็แค่มี Solution ที่ Solid สวยงามแล้ว =)

และอย่างนึงที่บอกได้อีกเช่นกัน ... สอง OS มาชนกันแบบเกือบ 100% ละทางเรื่อง Development Technique บน Android มี Fragment แปะบน Activity บน iOS ก็ใช้ Structure เดียวกันได้คือการแปะ ViewController ลงบน Container View ที่อยู่บน ViewController เพื่อให้โค้ดแยกเป็นส่วนๆอย่างมีระเบียบ โครงแอพฯ nuuneoi.com for iOS ตอนนี้ใช้ Structure เดียวกับเวอร์ชั่นแอนดรอยด์ทุกประการเลย (ผลของการรื้อไป 5 รอบ)

เริ่มเห็นช่องทางทำอะไรสนุกๆ ... ไว้เจอกัลลล

บทความที่เกี่ยวข้อง

Sep 5, 2015, 16:19
108802 views
กรณีศึกษา แอพฯ MakeupPlus กับการแอบอัพโหลดภาพขึ้น Cloud จีน
Jun 17, 2015, 14:47
7687 views
บันทึกการพอร์ตแอพฯจาก Swift 1.2 บน Xcode 6.3 ไปเป็น Swift 2.0 บน Xcode 7
0 Comment(s)
Loading