Private CocoaPods — ซอยโปรเจคใหญ่ ออกเป็นโปรเจคย่อยๆ

Khemmachart Chutapetch
Panya Studios
Published in
4 min readJul 13, 2017

เมื่อเราพัฒนาโปรเจคของเราไปซักพัก เราก็จะพบว่าโปรเจคของเราเริ่มบวมขึ้นเรื่อยๆ ยกตัวอย่างปัญหาที่เห็นได้ชัดเจนก็คือจำนวน Line of Code ที่มากขึ้น ซึ่งเราสามารถนำ Design Pattern หรือ Object Oriented Design ดีๆ มาช่วยแก้ปัญหาในส่วนนี้ได้

Image from uxmag.com

แต่อีกปัญหา ที่เราจะพูดถึงกันในบทความนี้ก็คือ เมื่อการพัฒนาโปรเจคไปนานๆ ทำให้จำนวนไฟล์มากขึ้นเรื่อยๆ ไม่ว่าจะเป็นไฟล์ .swift, .storyboard, .xib, รวมถึงไฟล์รูปภาพต่างๆ .. หรือพูดง่ายๆ คือ เมื่อโปรเจคใหญ่ ขึ้นจำนวนไฟล์ก็เพิ่มมากขึ้นด้วยเช่นกัน — เรามีทางแก้ปัญหานี้ยังไง?

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

หากผู้อ่านเคยมีคำถามเหล่านี้เกิดขึ้นในใจ ยินดีด้วยครับ บทความนี้จะช่วยแก้ปัญหากวนใจเหล่านั้นและทำให้คุณมีความสุขกับการพัฒนาโปรเจคได้มากขึ้น เย้

มีปัญหาก็ต้องมีทางออก — เอาโค้ดที่ไม่เกี่ยวข้องออกไปสิ !!

Example features

หลายๆ คนอาจจะคิดไม่ออกว่าจะโค้ดที่ไม่เกี่ยวข้องออกไปยังไง? ก่อนอื่นอยากให้ลองจินตนาการว่า เรากำลังทำแอปพลิเคชั่นขนาดใหญ่มากๆ แอปนึง ซึ่งมีฟีเจอร์ต่างๆ คล้ายกับ Instagram โดยจะประกอบไปด้วยฟีเจอร์หลักๆ ที่สามารถสังเกตุได้ใน TabBar คือ NewsFeed, Explore, Upload, Activities, และ Profile

การที่ฟีเจอร์เหล่านี้เชื่อมกันด้วย TabBar นั่นหมายความว่าฟีเจอร์ต่างๆ นี้มีความเกี่ยวข้องกันค่อนข้างน้อย หรืออาจจะไม่มีความเกี่ยวข้องกันเลย สามารถพัฒนาแยกกันได้ — ดังนั้นเราสามารถแยกฟีเจอร์เหล่านั้น ให้ออกมาเป็นโปรเจคย่อยๆ ได้ โดยที่ผมจะเรียกฟีเจอร์ที่ถูกแยกออกมาแล้วว่า “โมดูล”

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

ตัวช่วยในการแก้ปัญหา

สำหรับพระเอกของบทความนี้คือ CocoaPods — Dependency Manager เอาไว้จัดการกับ 3rd Party Library ต่างๆ ทำให้เราสามารถนำโค้ดที่คนอื่นเขียนเข้ามาใช้งานในโปรเจคของได้ ง่ายมากๆ เพียงกดไม่กี่คำสั่ง

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

1. แบ่งโปรเจคออกเป็นโปรคเจคย่อยๆ

ก่อนอื่นเราต้องมาคิดว่าจะแบ่งฟีเจอร์อะไรของเราไปเป็นโปรเจคย่อยบ้าง? ถ้ามองจากตัวอย่างข้างต้นก็คือ จะแบ่งตาม TabBar Items ซึ่งแบ่งออกได้เป็น NewsFeed, Explore, Upload, Activities, และ Profile ตามลำดับ

แต่อย่าลืมว่าแต่ละโมดูลนั้นจะทำงานได้ต้องมีหน้ากากหรือ Interface ต่างๆ เช่น Login, TabBar, Navigation Controller, Popup เป็นต้น เราไม่สามารถรันโมดูลย่อย ได้หากไม่มี Interface — ซึ่ง Interface เหล่านี้จำเป็นต้องมีอยู่ในทุกๆ โมดูลเพื่อให้แต่ละโมดูลสามารถทำงานอยู่ด้วยได้ด้วยตัวมันเอง เราจะเรียก Interface พวกนี้ว่า โมดูล Common

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

2. สร้างโปรเจคย่อยๆ ของเราขึ้นมา

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

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

ผมขอเปรียบเทียบโปรเจคของผมเป็นโมดูลที่กล่าวมาข้างต้นละกันเนอะ โดยที่ผมจะสร้างโปรเจคใหม่ขึ้นมาอันนึง ซึ่งเป็นโปรเจคที่เอาไว้สำหรับทำ Animation Fade-in, Fade-out โดยหน้าตาหล่อๆ ของมันก็จะเป็นประมาณนี้

UIVIewExtension.swift

ที่สำคัญคือ Access control ทั้งหมดที่เราต้องการให้โปรเจคอื่นนำไปใช้ได้นั้น ไม่ว่าจะเป็น function หรือ variables ก็แล้วแต่ จะต้องเป็น public นะครับ และถ้าหากว่าเราต้องการให้โปรเจคอื่นสามารถนำไป Override หรือ Extend ได้นั้นเราจะต้องตั้ง Access control เป็น open นะครับ — ถ้าหากเป็น default หรือ internal แล้วโปรเจคอื่นจะไม่สามารถเรียกใช้ได้เลย ตรงนี้เป็นส่วนที่พลาดกันบ่อยมาก

Image from Swift 3 Access Control article — Ryuichi Saito

ผู้อ่านสามารถอ่านเพิ่มเติมเกี่ยวกับ Swift 3 Access Control ได้ที่นี่

เมื่อเราได้จัดการ Source Code ทั้งหมดที่จำเป็นแล้ว ต่อมาก็คือการเปลี่ยนมันเป็น Pods นั่นเอง ซึ่งการทำ Private Pods เนี่ยง่ายกว่าการทำ Public Pods มากๆ เพราะเราไม่ต้องกรอกรายละเอียดอะไรมากมายมายเลย ซึ่งถ้าใครอยากทำ Public Pods นั้นสามารถอ่านได้ที่ References ใต้บทความครับ

3. ทำโปรเจคของเราให้เป็น Private Pods

การทำโปรเจคของเราให้เป็น Private Pods ก็ไม่ยากเลย ใช้ข้อมูลน้อยกว่าและมากกว่า Public Pods มากๆ เพราะเราไม่จำเป็นต้องส่ง Pods ของเราไปให้ทาง CocoaPods นั่งเอง

อันดับแรกเราต้องสร้าง Podspec ขึ้นมาก่อน โดยที่ไฟล์ Podspec นี้จะเป็นไฟล์ที่กำหนดคุณสมบัติให้กับ Cocoapods ของเรา เช่น ชื่อ, เวอร์ชั่น, รายละเอียด, ชื่อผู้เขียน, license เป็นต้น — พูดง่ายๆ ก็คือถ้าไม่มีไฟล์นี้เราก็สร้าง Pods ไม่ได้ สำหรับวิธีการสร้างก็คือเปิด Terminal และไปที่ Directory ของโปรเจคของเรา จากนั้นพิมพ์

$ pod spec create [PROJECT NAME]

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

และข้างในนั้นจะเต็มไปด้วยคอมเม้นท์เอาไว้บอกรายละเอียดต่างๆ ของไฟล์ แต่การที่เราทำ Private Pods นั้นเราไม่จำเป็นต้องใส่ใจกับรายละเอียดอะไรมาก สิ่งที่เราต้องให้ความสำคัญหลักๆ ก็จะมี

  • s.name ชื่อ ของ pods ของเรา ไม่จำเป็นต้องชื่อเดียวกับ Repository ก็ได้ครับ
  • s.source คือ url ที่เอาไว้เก็บไฟล์ pods ของเรา ยกตัวอย่างเช่น ผมสร้าง Repository ใน GitHub ว่า FadeAnimation ดังนั้นเจ้าตัว source ของผม
s.source = { :git => 'https://github.com/khemmachart/FadeAnimation.git', :tag => s.version.to_s}
  • s.source_files คือ directory ของ source code ของเรา ซึ่งผมแนะนำว่าควรจะเป็นไฟล์ source code ทั้งหมดให้อยู่ใน directory เดียวกันจะดีที่สุดครับ
  • s.resources คือ directory ของ media ทุกอย่างที่ไม่ใช่ source code เช่น xcassets, storyboard, xib เป็นต้น เช่นกันครับ ควรเก็บอยู่ใน directory เดียวกัน (แต่แยกกับ source code นะครับ)

ซึ่งอันอื่นนั้นไม่ค่อยจำเป็นเท่าไหร่ครับ เช่น ตัว s.version จะมีความจำเป็นเมื่อเราต้องการทำเป็น Public Pods นั่นเอง — หลังจากลบคอมเม้นท์ที่ไม่ใช้และสิ่งที่ไม่จำเป็นออกแล้วก็จะได้หน้าตาหล่อๆ ของ podspec ของเราประมาณนี้

Example of podspec file

4. การใส่ Dependency อื่น

ถ้าหากว่า Pods ของเรามีการเรียกใช้งาน Pods อื่น หรือต้องเรียกใช้งาน Dependency อื่นหล่ะ จะต้องทำยังไง? เราเพียงแค่ใส่ s.dependency แล้วก็ชื่อ dependency เข้าไปเหมือนที่เรียกใช้งานใน podfile เลย หากต้องการหลายอันก็ใส่ได้หลายอันครับ

s.dependency   'MBProgressHUD', '~> 1.0.0'
s.dependency 'Alamofire' '~> 4.4'

แต่ถ้าหากใน Private pod ของเรามีการเรียกใช้งาน Private pod จะแปลกนิดนึงนะครับ โดยที่ใน podspec นั่นต้องใส่แค่ชื่อของ dependency แค่นั้นพอนะครับ เช่น

s.dependency    'SNMovableTableView'

จากนั้นให้ไป เรียก Pod SNTMovableTableView ใน Podfile ของ Project หลักอีกทีหนึ่ง เช่น

pod ‘SNTMovableTableView’, :git => 'https://examplehub.com/SNMovableTableView-iOS’, :branch => 'develop'

5. ตัวอย่าง Podspec ที่พร้อมสำหรับการใช้งาน

Completed podspec

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

6. เมื่อทุกอย่างพร้อม

จากนั้นก็ Commit ไฟล์ทั้งหมดของเราและ push ขึ้นไปยัง GitHub หรือ Version Control ที่ผู้อ่านใช้อยู่ได้เลยครับ ซึ่งต้องตรงกับ s.source ที่เราระบุไว้ใน podspec นั่นเองครับผม

ผู้อ่านสามารถเข้าไปดู Source Code ของผมทั้งหมดที่ใช้ทำ Private Pods ได้ใน GitHub ของผมครับ

7. ทดสอบ Private Pods ของเราซักหน่อย

แล้วก็มาถึงขึ้นตอนสุดท้ายคือการทดสอบ Private Pods ของเรานั่นเองครับ โดยการ สร้างโปรเจคใหม่ ขึ้นมาจากนั้นสร้าง Podfile ง่ายๆ แบบข้างล่างนี้ โดยตัว pod ที่เราใส่นั้นจะชี้ไปยังที่อยู่เดียวกับ s.source ที่เราใส่ไว้ใน Podspec นั่นเอง

โดยหากเราใช้ Version control เราสามารถระบุ branch ได้ ซึ่งมันเป็นประโยชน์มากๆ เพราะจะได้ไม่ต้องมากด Release และก็เปลี่ยน Version บ่อยๆ ครับ สามารถทำใน Develop ได้เลยย

Example of podfile

จากนั้นก็สั่ง pod install หรือ pod update เพื่อดูผลลัพธ์ จากตัวอย่างที่ผมทำขึ้นมานั้นก็จะมีการ install pod ทั้งหมด 3 อันครับ (ยกเว้นหากว่ามันซ้ำกับ pod ใน podfile อยู่แล้วก็จะไม่ถูก install ทับกันครับ) และลองเข้าไปดูใน Directory ของโปรเจคเรา หากมีไฟล์ pods ของเราโผล่ขึ้นมาก็นับว่าสำเร็จเสร็จสิ้น เย้

Analyzing dependenciesPre-downloading: `FadeAnimation` from `https://github.com/khemmachart/FadeAnimation.git`, branch `develop`Downloading dependenciesInstalling Alamofire (4.4.0)
Installing FadeAnimation 0.1.0 (was 0.1.0)
Installing SDWebImage (3.8.2)
Generating Pods projectIntegrating client projectSending statsPod installation complete! There is 1 dependency from the Podfile and 3 total pods installed.

References:

Special thank to Thongpak Pongsilathong, iOS Developer at Nextzy Technologies for advices. If you want to make a public Library you can read the his article below.

--

--