Manage build scheme in Xcode

iOS env using custom build scheme

Yuttana Kungwon
Jitta Engineering
3 min readAug 7, 2017

--

หลายปีที่ผ่านมาคงจะปฏิเสธไม่ได้ว่า การสร้าง Mobile Application นั้นมีความหลากหลายมากขึ้น ไม่ยึดติดในเรื่องของตัวภาษา (Native Language) อีกต่อไป และอีกทั้งยังเป็นยุคที่มีบทบาทสำคัญในการสร้างสิ่งที่เรียกว่า Cross Platform อีกด้วย เรียกได้ว่าเทคโนโลยีได้เปลี่ยนแปลงหลายๆอย่าง ทำให้เราทำงานได้สะดวกขึ้น เร็วขึ้น ง่ายขึ้น แต่ยังแฝงไปด้วยความเจ็บปวดในการพัฒนาแอพที่ดีมากขึ้นอีกด้วย

สิ่งที่เกริ่นนำมาทั้งหมด ไม่ใช่ Xamarin หรือ Ionic framework ฯลฯ แต่ยังคงเป็น React Native นั่นเอง และนั่นคือสิ่งที่เราจะมาพูดถึงกันในบทความนี้

ในบทความนี้ผมจะมาเล่าในสิ่งที่มีความสำคัญในการสร้าง Cross Platform Application อย่างมาก นั่นก็คือเทคนิคในการเปลี่ยน Environment ในการพัฒนา เช่น ตอนนี้เรากำลัง Build มันอยู่ที่ Staging หรือ Development หรือ Production เป็นต้น ซึ่งมันควรจะทำได้สะดวก แล้วก็ง่ายด้วยครับ จะมีประโยชน์มากหากเราใช้ร่วมกับ Automation build tools ต่างๆ และที่สำคัญมันต้องทำงานได้ทั้งสองแพลตฟอร์ม

ก่อนที่จะเริ่ม เราลองมาคิดกันเล่นๆก่อนว่าแอพของเรานั้น ต้องการให้ build ใน environment ใดบ้าง เช่น development, staging, production เพราะสิ่งเหล่านี้เราจะส่งมันไปบอก JS เพื่อให้จัดการต่อ

iOS / Xcode Project Setting

เอาล่ะ… เรามาเริ่มที่ฝั่ง iOS กันก่อน ซึ่งแน่นอนมันจะต้องเริ่มด้วยการ Setting ค่าบางอย่างบน Xcode Project ก่อนที่จะ Bridge และส่งไปให้ฝั่ง JS ได้ใช้

Use Different Build Config

ถ้าเราใช้ react-native cli ในการ generate app เราจะมี Configuration สองตัวก็คือ Debug / Release เป็นมาตราฐาน แต่ในครั้งนี้เราต้องการ Staging environment ด้วย ให้เราเพิ่มเข้าไปก่อนโดยต้อง Duplicate Release มาใช้งานแบบนี้

จากนั้นให้เราเปลี่ยนชื่อมันเป็น Staging ได้เลย …ที่ต้อง Duplicate Release มาก็เพราะว่าเวลา build react native นั้น ตัวมันเองจะ dependent กับ config บางอย่างใน build นั้นๆ ซึ่งครั้งนี้เราต้องการ config ของ Release นั่นเอง

Prepare to Native Module

ตอนนี้เราสร้าง Build Configuration เรียบร้อยแล้ว เพื่อใช้สำหรับกำหนด build config ในการ compile ดังนั้นเราจะใช้ User-Defined setting ของ Xcode ในการเก็บ environment ซึ่งผมจะตั้งชื่อ key ของมันว่า BUILD_ENV เพื่อใช้ในการเก็บ value ของ env ต่างๆ ก็คือ development, staging, production ส่วนวิธีสร้างให้เรามองหา Build Setting ของ Project แบบนี้

เข้าไปที่ Build setting ในส่วนของ Project ไม่ใช่ Target!

ถ้าใครยังไม่ทราบ Xcode นั้นจะแบ่งการเก็บค่าของ Setting ต่างๆไว้สองส่วนคือ ส่วนที่เป็นระดับ Project (มองง่ายๆคือ Global) และส่วนที่เป็น Targets (ในระดับ scheme ต่างๆ เพราะเราสามารถ build apps ได้หลาย scheme หลาย configใน Project เดียว)

ถ้าเปรียบกับฝั่ง Android มันก็จะคล้ายกับไฟล์ build.gradle ของ Project และ build.gradle ของ App นั่นเองครับ

จากนั้นให้กดเครื่องหมาย “+” เพื่อสร้าง User Define Setting ขึ้นมาใหม่ แล้วให้ value ของแต่ละ config เป็นชื่อของ env ที่เราต้องการ (development, staging, production)

กำหนด config value ของ env ต่างๆที่ต้องการ

Create variable in plist

User-Defined settings ที่เราสร้างไว้ข้างต้นนั้น มันจะไม่สามารถ access จาก code ได้โดยตรง (ไม่สามารถเขียน code เพื่อดึง key/val ออกมา) เพราะหน้าที่ของมันเป็นเพียงแค่เก็บ config ที่ใช้ในการ build เท่านั้น

ดังนั้นเราจึงต้องสร้างตัวแปรเก็บไว้ในไฟล์ Info.plist ซึ่งผมจะใช้ชื่อ key เป็น BuildEnvironment และ value เป็น $(BUILD_ENV) การสร้างตัวแปรแบบนี้ใน plist file ซึ่งมันจะถูกนำไป map เข้ากับ config ของเราโดยอัตโนมัติ นั่นหมายความว่าเราจะสามารถเขียน code ให้ดึง variable ออกมาได้ง่าย

สร้าง key และ variable เพิ่มใน info.plist

Create an Native Module

ตอนนี้เรามี key ใน plist ที่ชื่อว่า BuildEnvironment แล้ว ถึงเวลาที่เราต้องสร้าง Native Module ขึ้นมาเพื่อดึง value จาก plist file แล้ว bridge กลับส่งไปให้ JS รับรู้และเรียกใช้ต่อไป

ข้อดีอีกอย่างของ React Native คือเราสามารถสร้าง Native Module ต่างๆเพื่อนำมาใช้งานต่อในฝั่ง React JS ได้ เช่น API บางอย่างที่ยังไม่มีบน iOS หรือการ Custom งานบางอย่างที่ต้องใช้ Objective-C ช่วยทำงาน (เป็นอีกเหตุผลที่คนเขียน React-Native ควรมีพื้นฐานภาษา และเข้าใจ anatomy ต่างๆเหล่านี้มาพอสมควร)

ก่อนอื่นต้องสร้าง Class ใหม่ขึ้นมา ผมแนะนำว่าให้เป็นชื่อกลางๆ เช่น RNConfig.h และกำหนด subclass เป็น NSObject

ในการสร้าง Native Module นั้น facebook ได้แนะนำไว้ให้เราแล้ว ตามนี้ https://facebook.github.io/react-native/docs/native-modules-ios.html ซึ่งจะมีตัวอย่างการ implement แบบนี้

RNConfig Example Class

จาก class ข้างต้นจะอธิบายได้ว่าเราอ่านข้อมูลจาก key ที่อยู่ใน info dictionary ของเราไว้ใน NSString แล้ว return มันออกไปด้วยเมธอต constantsToExport ที่ React จัดเตรียมไว้ให้

Noted: constantsToExport นั้นจะใช้ export ของที่เป็น static ซึ่งมันจะทำงานตอน initialize เท่านั้น หากมีการเปลี่ยนแปลงค่าในขณะ runtime บน JS จะไม่ทำงาน

Implement config in JS

ตอนนี้เราได้ Native Module ที่เอาไว้ใช้เลือก environment แล้ว และสามารถ Import มาใช้ได้แล้ว แต่เราควรสร้าง config file ที่เป็น JSON ไว้ด้วยเพื่อให้มันยืดหยุ่นในการทำงานและแก้ไขในอนาคตได้มากที่สุด และ ในอนาคตเราอาจต้องเพิ่ม key ที่เจาะจง เช่น GA Tracking code หรือ feature toggle ต่างๆ ซึ่งอาจจะทำในลักษณะนี้

จากนั้นเราแค่สร้าง JS file เพื่อที่จะ return key หรือ environment ต่างๆไว้ใช้งานแบบนี้

แค่นี้เราก็สามารถกำหนด Build configuration ได้อย่างยืดหยุ่น และไม่อิงฝั่ง Native จนมากเกินไป เช่น ต้องการเปลี่ยนเป็น Production ก็สามารถทำได้และ archive แอพได้ทันที

เปลี่ยน build config ตอนที่ต้องการ

Where To Go From Here?

ตอนนี้เราสามารถ เปลี่ยน build environment ได้อย่างง่ายๆแล้ว แค่กำหนด build config ก่อนการ build ในแต่ละครั้ง (สามารถทำบน cli ได้ ในกรณีทำ CI ด้วยคำสั่ง xcodebuild -workspace "ios/MYApp.xcworkspace" -configuration "Staging" "clean" "archive" ) ในบทความนี้ถ้าจะนำไปใช้จริง ควรใช้วิธีเพิ่ม build target scheme ขึ้นมาอีกตัว จะได้สามารถแยก Build version ได้อย่างเจาะจงมากขึ้นครับ ส่วนในบทความต่อไปจะพูดถึงวิธีในการ setting ของฝั่ง Android กันบ้าง ว่ามีอะไรบ้าง… รอติดตามครับ :D

--

--