Skip to main content

Làm sao để tạo CocoaPods library?



Đối với lập trình viên iOS, việc làm việc cùng các thư viện ngoài trên CocoaPods không còn xa lạ, chúng ta đã quá quen thuộc với cài đặt thư viện và gọi các hàm từ chúng. Tuy nhiên, đã bao giờ bạn mong muốn tạo ra những thư viện cho riêng mình hoặc chia sẻ chúng với những đồng nghiệp khác cùng đóng góp và sử dụng chưa? Trong bài này chúng ta sẽ cùng nhau tạo 1 pod đơn giản và sử dụng Travis CI, Codecov để tăng độ tin cậy trên CocoaPods.

Tổng Quan

Có nhiều cách khác nhau để tạo pod, cách phổ biến nhất là theo tài liệu chính thức của CocoaPods, về cơ bản, bạn có thể sử dụng lệnh sau để nhanh chóng khởi động Xcode workspace để phát triển pod:
pod lib create [pod name]
Trong workspace, một điều tôi không thích là code liên quan đến Unit Test được lồng bên trong example target, nó thực sự ko có ý nghĩa lắm. Sau một số thử nghiệm, tôi đã tìm ra một cách để tạo một pod từ đầu và tổ chức tốt các mã unit test và examples


Dưới đây là tổng quan nhanh về tất cả các bước để tạo pod:
  1. Thiết lập Xcode project và các target cần thiết
  2. Liên kết với Github
  3. Implement the pod
  4. Viết unit tests
  5. Cấu hình Travis CI và Codecov
  6. Publish the pod
Nào, chúng ta cùng bắt đầu!

Thiết lập Xcode project và các target cần thiết

Trước khi tạo project, một nhiệm vụ quan trọng cần phải làm đó là đặt tên cho pod của chúng ta (VD: SwiftyLib) và đảm bảo tên này chưa được sử dụng trên CocoaPods.

Tạo một Cocoa Touch Framework Xcode project

Một CocoaPods library bản chất là một Cocoa Touch framework library. Mở Xcode và chọn File > New > Project để tạo một project mới, sau đó chọn Cocoa Touch Framework.

Nhập tên SwiftyLib, chọn Include Unit Tests. Ở trang tiếp theo, chọn project location và không check "Create Git repository on my Mac" vì chúng ta sẽ tạo sau.

Sau khi tạo xong, workspace của chúng ta sẽ trông như sau:

Chuẩn bị targets

Như các bạn thấy, chúng ta giờ đã có một project là SwiftyLib và nó có 2 targets:
  1. SwiftyLib: Tất cả implementation sẽ được đặt ở đây
  2. SwiftyLibTests: Viết các phần test
Trong Xcode, mỗi target có 1 scheme để quản lý hành vi build hoặc test. Như bạn thấy ở SwiftyLib scheme, khi chúng ta thực hiện lệnh Test, nó sẽ kích hoạt các kịch bản test được viết trong SwiftyLibTests target.

Trong nhiều CocoaPods projects, nhà phát triển thường để một vài demo hoặc example như là 1 phần của pod, chúng sẽ giúp người khác hiểu hơn cách sử dụng pod. Để làm như vậy, hãy thêm 1 target mới (SwiftyLibExamples): File > New > Target…, chọn Single View App và chọn Next.

Nhập tên SwiftyLibExamples và Finish.

Bây giờ thanh điều hướng sẽ trông như sau:

SwiftyLibTests đã không bị lồng trong SwiftyLibExamples. Sau này chúng ta sẽ implement cho example để sử dụng pod, nhưng bây giờ chúng ta sẽ kết thúc bước đầu là khởi tạo và thiết lập.

Liên kết với Github

Không cần phải nói, Github là giải pháp tốt nhất quản lý các dự án open-source. Quan trọng hơn, khi chúng ta publish CocoaPods library, CocoaPods sẽ yêu cầu địa chỉ mà source code có thể lấy và Github là sự lựa chọn hoàn hảo.

Tạo repository mới

Truy cập vào tài khoản Github và tạo một project mới là SwiftyLib.

Tiếp theo, chúng ta sẽ sử dụng thông tin sau để liên kết với Xcode project của chúng ta:

Liên kết với project

Mở Terminal, navigate đến SwiftyLib Xcode project folder và thực hiện các lệnh sau
git init
git remote add origin [email protected]:{USERNAME}/SwiftyLib.git
git add .
git commit -m "Initial project setup"
git push -u origin master

README và MIT License

Chúng ta đã upload source codes lên Github, trước khi kết thúc phần này, chúng ta tạo file README.md và MIT license file từ trang Github. Để tạo README.md file hãy click vào nút "Add a README", để thêm MIT license file hãy click vào "Create new file", thực sự đơn giản phải không.

Sau khi tạo xong file, hãy đồng bộ với local
git pull remote_name branch
...
2 files changed, 24 insertions(+)
create mode 100644 LICENSE
create mode 100644 README.md

Implement the pod

Giờ là lúc chúng ta sẽ implement cho Pod. Tất cả gì pod của chúng ta có thể thực hiện sẽ được implement trong SwiftyLib.

Chúng ta sẽ thêm 1 chức năng đơn giản cho pod bằng cách implement trong file SwiftyLib.swift:
//
//  SwiftyLib.swift
//
public final class SwiftyLib {

    let name = "SwiftyLib"
    
    public func add(a: Int, b: Int) -> Int {
        return a + b
    }
    
    public func sub(a: Int, b: Int) -> Int {
        return a - b
    }
    
}
Library rất đơn giản, nó chỉ có 2 functions tính toán. Xcode sẽ tự động lưu lại file khi chúng ta implement, hãy nhớ sau khi đã implement xong cho SwiftyLib target cần build lại để các target khác có thể sử dụng được.

Viết unit test

Unit test là một phần quan trọng để chất lượng của library được đảm bảo, vì library của chúng ta rất đơn giản nên unit test cũng vậy 😃
//
//  SwiftyLibTests.swift
//
import XCTest
@testable import SwiftyLib

class SwiftyLibTests: XCTestCase {
    
    var swiftyLib: SwiftyLib!

    override func setUp() {
        swiftyLib = SwiftyLib()
    }

    func testAdd() {
        XCTAssertEqual(swiftyLib.add(a: 1, b: 1), 2)
    }

}
Hiện tại chúng ta chỉ có add() test function. Chạy unit test bằng cách click Test task như dưới đây:

Bài test đã vượt qua, chuyển tới phần report và kiểm tra:

Như các bạn thấy, unit test của chúng ta mới chỉ cover được 50%, bởi vì chúng ta chưa viết unit test cho sub() function, chúng ta sẽ implement luôn cho nó như sau:
//
//  SwiftyLibTests.swift
//
import XCTest
@testable import SwiftyLib

class SwiftyLibTests: XCTestCase {
    
    var swiftyLib: SwiftyLib!

    override func setUp() {
        swiftyLib = SwiftyLib()
    }

    func testAdd() {
        XCTAssertEqual(swiftyLib.add(a: 1, b: 1), 2)
    }
    
    func testSub() {
        XCTAssertEqual(swiftyLib.sub(a: 2, b: 1), 1)
    }

}
Bây giờ hãy thử chạy lại bài test 1 lần nữa và nó sẽ cover được 100%

Lúc này chúng ta đã có được 1 pod với chất lượng code được đảm bảo 😃 Hãy lưu lại những thay đổi và push chúng tên Github thôi.

Cấu hình Travis CI và Codecov

Bất cứ khi nào chúng ta push những thay đổi mới lên Github, chúng ta đều cần test pod và chắc chắn các test case đều vượt qua.
Travis CI là một dịch vụ tích hợp liên tục được lưu trữ, phân tán sử dụng để xây dựng và test các dự án phần mềm được lưu trữ tại GitHub.
Chúng ta có thể tận dụng Travis để chạy các test case và gửi báo cáo cho Codecov.
Codecov là một công cụ báo cáo nhằm xử lý bất kỳ định dạng coverage report nào thành định dạng theo tiêu chuẩn trên Codecov. Bạn có thể thay đổi cấu hình về cách Codecov xử lý báo cáo và thể hiện thông tin coverage.
Để Travis nhận bất kỳ thay đổi nào trong repo Github của chúng ta, hãy đồng bộ hóa tài khoản Github của bạn trên Travis và kích hoạt kho lưu trữ SwiftyLib.

Tạo 1 .travis.yml file trong project folder:
language: objective-c

osx_image: xcode10.1
env:
  matrix:
    - TEST_SDK=iphonesimulator12.1 OS=12.1 NAME='iPhone XR'
    - TEST_SDK=iphonesimulator12.1 OS=12.1 NAME='iPhone 7'

script:
  - set -o pipefail && xcodebuild test -enableCodeCoverage YES -project SwiftyLib.xcodeproj -scheme SwiftyLib -sdk $TEST_SDK -destination "platform=iOS Simulator,OS=$OS,name=$NAME" ONLY_ACTIVE_ARCH=YES
Về cơ bản, nó hướng dẫn Travis chạy unit test trên hai thiết bị khác nhau chạy các phiên bản iOS khác nhau. Bạn có thể yêu cầu Travis thử nghiệm trên nhiều thiết bị hơn, nhưng hãy đảm bảo phiên bản giả lập iPhone hỗ trợ các thiết bị đó. Lưu tệp .travis.yml và đẩy nó vào Github, kích hoạt build trên Travis, bạn sẽ thấy Travis chạy các test case cho commit bạn đã gửi.

Tiếp theo, hãy yêu cầu Travis gửi test report cho Codecov. Trước khi tiếp tục, hãy xem Travis đã làm gì cho đến nay. Travis thực thi đoạn script sau để chạy thử nghiệm:
xcodebuild test \
  -enableCodeCoverage YES \
  -project SwiftyLib.xcodeproj \
  -scheme SwiftyLib \
  -sdk $TEST_SDK \
  -destination "platform=iOS Simulator,OS=$OS,name=$NAME" \
  ONLY_ACTIVE_ARCH=YES
Chú ý rằng test report được tạo bởi lệnh xcodebuild test. Nếu theo hướng dẫn của Codecov, chúng ta đơn giản push coverage report tới Codecov với dòng lệnh:
bash <(curl -s https://codecov.io/bash) -t {CODECOV_TOKEN}

Publish the pod

Cuối cùng, đã đến lúc publish pod của chúng ta! Đầu tiên, cần cài đặt CocoaPods
gem install cocoapods
Sau đó tạo một podspec file để define pod của chúng ta,... nơi có thể tìm thấy source
pod spec create SwiftyLib
...
Specification created at SwiftyLib.podspec
Chỉnh sửa SwiftyLib.podspec file:
Pod::Spec.new do |spec|

  spec.name         = "SwiftyLib"
  spec.version      = "0.0.1"
  spec.summary      = "A CocoaPods library written in Swift"

  spec.description  = <<-DESC
This CocoaPods library helps you perform calculation.
                   DESC

  spec.homepage     = "https://github.com/jeantimex/SwiftyLib"
  spec.license      = { :type => "MIT", :file => "LICENSE" }
  spec.author       = { "jeantimex" => "[email protected]" }

  spec.ios.deployment_target = "12.1"
  spec.swift_version = "4.2"

  spec.source        = { :git => "https://github.com/jeantimex/SwiftyLib.git", :tag => "#{spec.version}" }
  spec.source_files  = "SwiftyLib/**/*.{h,m,swift}"

end
Kiểm tra SwiftyLib.podspec xem đã đúng chưa, nếu chưa thì sẽ sửa lỗi cho nó.
pod lib lint
...
-> SwiftyLib (0.0.1)
SwiftyLib passed validation.
Sau khi đã validate, hãy lưu lại và push lên Github.
git add .
git commit -m "Added SwiftyLib.podspec"
git push

Distribute our library

Thêm tag cho version
git tag 0.0.1
git push origin 0.0.1
Sau đó push tiến hành push
pod trunk push
...
Updating spec repo `master`
---------------------------
🎉  Congrats
🚀  SwiftyLib (0.0.1) successfully published
📅  February 24th, 15:49
🌎 https://cocoapods.org/pods/SwiftyLib
👍 Tell your friends!
---------------------------
Lưu ý rằng số phiên bản phải khớp với spec.version được xác định trong SwiftyLib.podspec.
Xin chúc mừng! Chúng ta vừa xuất bản thư viện CocoaPods cho cộng đồng mã nguồn mở!
Bài viết được dịch từ: https://medium.com/flawless-app-stories/create-your-own-cocoapods-library-da589d5cd270
Hy vọng bài viết sẽ hữu ích với bạn!

Comments

Popular posts from this blog

Swift Tool Belt, Part 1: Adding a Border, Corner Radius, and Shadow to a UIView with Interface Builder

During my iOS work, I’ve assembled a set of code that I bring with me on every iOS project. I’m not talking about large frameworks or CocoaPods here. These are smaller Swift extensions or control overrides that are applicable to many projects. I think of them as my tool belt. In this post, I’ll show you an extension that will add a border, a corner radius, and a shadow to any UIView, UIButton, or UILabel and allow you to preview what it will look like in Interface Builder. Back in 2014, I wrote a blog post on Expanding User-Defined Runtime Attributes in Xcode where I added a border, corner radius, and shadow to a UIView using Interface Builder’s user-defined runtime attributes. This solution had no type checking—you had to type the property you wanted to modify by hand and often had to look up what it was called. You also had to run your project in order to see the effect of the runtime attribute. Starting with Xcode 6 , there is a new mech...

Alamofire vs URLSession

Alamofire vs URLSession: a comparison for networking in Swift Alamofire and URLSession both help you to make network requests in Swift. The URLSession API is part of the foundation framework, whereas Alamofire needs to be added as an external dependency. Many  developers  doubt  whether it’s needed to include an extra dependency on something basic like networking in Swift. In the end, it’s perfectly doable to implement a networking layer with the great URLSession API’s which are available nowadays. This blog post is here to compare both frameworks and to find out when to add Alamofire as an external dependency. Build better iOS apps faster Looking for a great mobile CI/CD solution that has tons of iOS-specific tools, smooth code signing, and even real device testing? Learn more about Bitrise’s iOS specific solutions! This shows the real power of Alamofire as the framework makes a lot of things easier. What is Alamofire? Where URLSession...

Frame vs Bounds in iOS

This article is a repost of an answer I wrote on Stack Overflow . Short description frame = a view’s location and size using the parent view’s coordinate system ( important for placing the view in the parent) bounds = a view’s location and size using its own coordinate system (important for placing the view’s content or subviews within itself) Details To help me remember frame , I think of a picture frame on a wall . The picture frame is like the border of a view. I can hang the picture anywhere I want on the wall. In the same way, I can put a view anywhere I want inside a parent view (also called a superview). The parent view is like the wall. The origin of the coordinate system in iOS is the top left. We can put our view at the origin of the superview by setting the view frame’s x-y coordinates to (0, 0), which is like hanging our picture in the very top left corner of the wall. To move it right, increase x, to move it down increase y. To help me remember bound...