SwiftLint

SwiftLint

去年(2018)年末的時候,在 Twitter 上看到一些朋友們在討論著 SwiftLint 的使用,於是便也嘗試在目前公司的專案中導入,來解決 Coding Style 的問題。

Coding Style 的問題

剛進入到這家公司時,最痛苦的事情莫過於毫無章法的 Coding Style,這部分就不一一細數了,詳情可以看前陣子我的 Twitter 動態

專案裡頭每個人寫的格式不同,會造成其他人在閱讀專案時,大幅增加理解彼此想法的成本

簡單來說就是浪費一堆時間在猜你在寫什麼

為了讓專案裡頭的大家有差不多的 Coding Style,可以選擇使用 SwiftLint 來處理這個情況。

實際嘗試

首先先以 SwiftLint 的官方教學來安裝和建構環境,並利用 EthanSwiftLint rules 當作基底來調整,先以別人的規範來看看差異性。

一跑下去便是直接噴了 3000+ issues⋯⋯

可以透過 Rules.md 來逐條看看定義並透過 example 來看怎麼算是 Non TrggeringTriggering;再來一條一條調整改進。

一些心得

目前公司的專案從 3000+ ➡️ 17 warings,而這 17 條是我還沒 refactor 到的 features,所以就還沒那麼急著去調整。

private_outlet 和 private_action

而其中一條規則 private_outlet 給我的感觸挺深刻的,由於我大多數的工作經歷是獨立開發居多,而每個 IBOutlet 都必須為 private 是第一份工作時所踩到的坑;當時公司共有兩位 iOS 工程師,彼此皆為 Junior 的程度,故沒什麼規範和概念,於是便會出現一些神秘的情境⋯⋯

像是不知道為什麼你負責的 UIViewController 刻出來的畫面就是和你想的不太一樣,才發現另一個地方(別人寫的 code)在直接修改畫面的 Layout / value⋯⋯

後來就體會到物件的每個變數和 functions 的 access 問題,而這條規則便是解釋著 IBOutlet 不應該可以直接從其他地方呼叫、修改,像是被這麼做:

fooViewController.fooLabel.text = "Test"

從那時候開始養成的習慣到現在,當發覺其實有其他人也是這麼做,並將它視為一條 rule 的時候真的覺得有點小感動!

整體來說

你可以透過這個 SwiftLint 來反覆思考一些寫程式上的問題,像是 function 的長度、class 的長度以及 Swift 檔案的長度等;如何切割每個物件和 function 等,都是相當值得去思索的習慣問題。

不過也不需要逐條都導入到專案之中,建議是花一些時間找到你最認同的那幾條 rules 來遵循即可!

ignore Pods warning

在使用 CocoaPods 的時候難免會碰上 warning,原因不外乎就是使用的套件本身在 build 的時候所產生的警告,但不建議直接 unlock Pods 去修改,請以那專案修改,並在處理完後發送 Pull Request 造福大眾。
不過現階段來說,我們可以做的事情是忽略 Pods 的 Warning,
只要在 Podfile 裡頭加上

inhibit_all_warnings!

即可在目前的專案中忽略 Pods 相關的 warning。

Pods 到底需不需要放在 .gitignore?

若有使用 GitHub 所預設的 Swift .gitignore,你會發現在 CocoaPods 的部分寫著

# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/

用意如同字面上的意思,GitHub 所提供的預設會建議你上傳 Pods 的內容到 git server 上,
而我個人也認同這種做法,目前所想的原因如下:

  • 可以完整地打包套件當前狀態到 git 上,以避免套件有異動
  • 若你們有直接修改 Pods 裡頭的 Source code 的話,更加得上傳(不過完全不建議這麼做)
  • git clone 下來之後可以不用再 pod install 一次

不過也是有些壞處,如套件越多時,造成 git 上的專案肥大等。
但是像是 Carthage 的話,就會建議加入到 .gitignore,畢竟它的用意就是要去中心化,
若將自己 build 出來的套件放上去就和中心概念背道而馳了!

FirebaseDatabase – Read

之前有寫過 FirebaseDatabase REST API的文章,
而這篇則會是在 iOS 上的使用。

安裝套件

由於 Google 認為 Carthage 的方式不符合他們的使用模式,
畢竟 Firebase 的 framework 並非是開源的,
所以只有提供 CocoaPods 的安裝方式或是直接下載檔案;
而我這邊就以 CocoaPods 來安裝 Firebase 相關的套件,其他則用 Carthage 來管理。

設定

我們在 Firebase console 那先建立好專案並匯入 GoogleService-Info.plist,
如果你有多個 Target 要使用的話,建議放在不同的資料夾,並且設定好 Target Membership。
並且要注意 Firebase console 內的 Database rules,
若沒有做 auth 相關內容的話,記得要調整;
如我開放給 App 讀取但不可寫入的話:

{
  "rules": {
    ".write": "auth != null",
    ".read": true
  }
}

接著在 AppDelegate.swift 中加入

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    FirebaseApp.configure()
    // --**--
}

順道提醒一下,若要讓 Database 的資料在離線也能使用上一次的 cache 的話,
需要在 AppDelegate.swift 裡頭加入

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // --**--
    Database.database().isPersistenceEnabled = true
    // --**--
}

官方文件中沒有特別註記,但若在其他地方執行這段程式,則會直接報錯。

讀取資料

Firebase Database 所提供的是一個可監聽的資料庫,
來做到 Realtime Database 的效果;
Reference 便是 path 的概念,
假設我的資料長這樣:

{
    "test": {
        "user": {
            "name": "Archie"
        }
    }
}

則 ref 有幾種設法;

  • 觀察全部的資料: Database.database().reference.observe
  • 只看 test 下的異動: Database.database().reference.child("test").observe
  • 只看 user:Database.database().reference.child("test").child("user").observe

隨著資料的結構,我們可以讓每個地方只專注它需要監聽的部分就好。

CocoaPods

CocoaPods

上一篇介紹 Carthage,這篇寫一下 CocoaPods。
其實我個人是先使用 CocoaPods,後來才部分改用 Carthage 作為主要的管理方式;
而 CocoaPods 的安裝方式也很簡單,可以透過 gem 來安裝:

安裝

sudo gem install cocoapods

不同於 Carthage 的 Cartfile,CocoaPods 的是使用 Podfile
格式有些不同,不過都可以在資料夾下使用 Terminal 輸入初始化的指令:

pod init

系統就會自行建立一個 Podfile,並用 pod search 的指令來找找你要什麼東西。
弄完 Podfile 之後,就可以執行安裝進專案:

pod install

並打開它幫你建立的 .xcworkspace 就好了。

一些分享

  • 可安裝的版本和 GitHub 上寫的不同?

CocoaPods 會建立一個 local 的所有 repo 資訊,若找不到的時候,可以先試著更新它:

pod repo update
  • Carthage 和 CocoaPods 是可以混用的

Carthage

Carthage

Carthage 是一個套件管理的開源軟體,可以使用 Homebrew 安裝:

brew install carthage

和 CocoaPods 的差異在於它並不會將第三方的 Framework 直接加入專案,
而是獨立在外,並在 build 的時候直接將 framework 複製進去到 App 內;
CocoaPods 的話則會在每次 build 的時候同時 build 你所匯入的 framework,
而隨著專案使用到的套件內容越多,則會花越多時間。

去中心化

這個詞最近隨著區塊鏈的爆紅,也常常可以看見。
不過在這邊的情境是,CocoaPods 是由它那邊搜集很多個 repo,提供給我們來安裝;
所以若 CocoaPods 的 repo 資料沒更新,或是作者只放在 GitHub 上,沒有提交到 CocoaPods 的話,
則無法使用。
而 Carthage 則沒有這方面的問題,可以自行將 GitHub 上的專案加入到 Cartfile

github "ReactiveX/RxSwift"

不過這也是比較麻煩的地方,我們就沒有辦法像 CocoaPods 一樣直接在 Terminal 下 pods search RxSwift 來取得資訊。

個人的使用方式

由於我自行在開發,同時會有很多個專案用到相同的 framework(e.g RxSwift、Siren),
原本若是使用 CocoaPods 的話,則會在很多資料夾內都有一樣的東西;
而 Carthage 的話,則是將 FRAMEWORK_SEARCH_PATHS 都指定到同一個資料夾即可,
並在 Build phase 加上 Carthage 的 Run script。
而我大多數只會開發 iOS 的 App,Carthage update 的時候則可以只更新 iOS 的部分:

carthage update --platform iOS

Siren – 通知使用者更新

我們時常可以在 App 之中看到,「目前有新版本可以提供下載」等相關的訊息;
而實作通知使用者更新的方法很多,這篇則是介紹一個開源的 Framework:

Siren

Siren 的運作邏輯是,你可以透過版號來決定跳出什麼通知來提醒使用者,
並且提供多語系的訊息內容。
它會透過 Bundle Identifier 去 App Store 上尋找資訊,
再來比對版號執行後續動作。

版號的定義

1.0.123.5678

  • 1:major
  • 0:minor
  • 123:patch
  • 5678:revision

一般我個人的習慣是:
major 會是在商業模式改變或是重大功能發布時,才會動到的;
而 minor 則是有必要的更新,像是嚴重的 bug 或是無法向下相容的異動。
patch 是更新一些 issue 或是修正 bug;
revision 則讓它跟著 commit 的數量。

Siren 的設定

舉個例子,在 major、minor 有提升時;
像是從 1.0.0 -> 2.0.0 或是 1.0.0 -> 1.1.0,
我會希望舊的使用者一定要更新 App 才能使用,
則會設為強制更新(.force)。
而 patch 則讓使用則決定要不要更新,或是可以跳過此次更新。

Siren.shared.majorUpdateAlertType = .force
Siren.shared.minorUpdateAlertType = .force
Siren.shared.patchUpdateAlertType = .skip
Siren.shared.revisionUpdateAlertType = .none

還沒上架前的測試

Siren 建議可以先將 Bundle Identifier 更改為 iTunes Connect Mobile 的 Bundle Identifier:com.apple.itunesconnect.mobile
並把 Siren 的 debugEnabled 調整為 true。

Firebase Database REST API

這篇主要的內容會是簡單地記錄一下 Firebase Database RESTful API
所提供的相關內容和使用方式。

Firebase Database

Firebase database 的儲存資料方式是屬於 NoSQL 的方式,
利用一組 key 配對一組 value 的模式來建構資料庫;
而在 Database 的介面中,我們可以清楚地看到資料是以 JSON 的格式呈現。

在 iOS 開發的過程中,如果要使用 Firebase 的相關內容,
可以使用官方所提供的 Firebase iOS SDK
或是在使用 Python 開發的時候,我會選擇使用官方推薦的 Pyrebase
那若你目前的開發方式沒有相對應的 SDK 或是 third party 可以使用的話呢?
那麼你就只能一起用 REST API 來完成要做的事情了!

REST API

Firebase 提供了五種 Http method

  • GET
  • PUT
  • POST
  • PATCH
  • DELETE

其中 GET 和 DELETE 就沒什麼特別好說的,你就是取得一個 JSON 或是刪除一個。

PUT

PUT 就和平常使用 PUT 的方式一樣,
它會把整個 JSON 覆蓋成你目前丟上去的 JSON。

POST

POST 的話就有些不一樣,當你 POST 一個資料到某個 JSON 的時候,
它會自行建立一組 key 並回傳 name 回來。
而你所丟的資料會是那組 key 所對應的 value,
所以簡單來說 POST 就是在做新增物件時使用,且 ID 是由它所建立的。

PATCH

PATCH 則是負責更新內容,它會先找到匹配的 key,再更新其 value;
若沒有找到相對應的 key 的話,則會建立一筆新的 key-value。

如果有其中一個無法使用呢?

若遇到你所使用的開發語言或是瀏覽器等等,無法使用其中一項 method 時;
舉個例子,DELETE 無法使用的話,Firebase 有提供你覆寫 method 的功能,
method 使用 POST,而在 header 加上:
X-HTTP-Method-Override: DELETE
便可以等同於上述所說的 DELETE。
或是加在 url 裡頭也可以:
https://[PROJECT_ID].firebaseio/[JSON_NAME].json?x-http-method-override=DELETE
 

Firebase ETag

Firebase 也有支援 ETag,
在 header 上加上 X-Firebase-ETag: true,它便會在回傳的 headers 中加上
Access-Control-Expose-Headers = ETag
ETag = kmHkuKx9sCx742tosJOV4oH+JBQ=
而我們可以下一次的 request 中,在 header 放上 if-match:[ETAG_VALUE]
伺服器端便會驗證是否可以執行這次的要求;
若最後一次的 ETag 和 if-match 的值相符的話,Firebase 便會回傳 412 Precondition failed。
而 ETag 相關的資訊可以看這邊
 

最後

整理完這些資訊後,就可以著手寫一些 database 的存取方法到 Vapor 的專案了!

Vapor

Vapor

在 Swift 開源之後,開發者們便開始將這語言往更多層面去發展,而 web app 便是其中一項。
Vapor 是一款以 Swift 作為主要語言的 Server 建構服務,讓我們可以透過它來開發 Web 相關的應用;
對於 iOS 開發人員而言,Swift 可以用來開發 web app 是一件有趣的事情,
其代表著我們可以使用同一個語言來開發一個產品,從 server 到 client。

安裝方式

不同於在安裝 iOS 第三方套件的流程,Vapor 需要透過 terminal 來安裝相關內容:
首先,得先安裝 Vapor 的 CLI

curl -sL toolbox.qutheory.io | sh

安裝完之後,便可以在 terminal 底下使用 vapor 的指令。

建立新專案

我們可以透過下方的指令,來建立一個新專案:

vapor new ProjectName

其中第三方套件的管理方式,是使用 Swift Package Manager
所以我們會在目錄下看到一個 Package.swift 和 Package.pins
這方面倒是有些像 CocoaPods 所使用的 Podfile 的概念。

使用 Xcode 開發

有了專案後,你可以選擇直接打開 main.swift 來進行開發,
但如果比較喜歡使用 Xcode 開啟一個專案,而非單一檔案,
則可以透過下方指令才建立 .project。

vapor xcode

這樣系統就會幫我們產生 .project 檔,開發起來就和原本寫 iOS 差不多了。

Server 的相關設定

Vapor 的 server 相關設定會放置在 Config 的資料夾底下,
其中包含五個 .json 檔案:

  • app.json
  • crypto.json
  • droplet.json
  • fluent.json
  • server.json

像是我們可以在 server.json 裡頭看到 host、port 的相關內容。

Build & Run

若你是開啟 Xcode 專案的方式,那麼 build 和 run 的方式就和以往開發 iOS 的方式一樣;
但若是沒有建立 .project 的話,也可以透過 vapor build 以及 run 的方式,就是在目錄下執行:

vapor build && vapor run

Droplet

在看到 Vapor 的 icon 之後,不難想像為何其主要的核心物件名稱為 droplet。
在初始時,main.swift 會有基本的 demo code,
我們可以直接在 try drop.setup() 下方加入一些內容。

這樣便會在 0.0.0.0:8080/ 時,得到 Hello World!
而也可以透過路徑來帶入參數,像是 0.0.0.0:8080/name/Archie 的寫法:

當連過去時,便會顯示 Hello Archie!

Views

Vapor 將 Views 放置在 /Resources/Views 裡頭,並可以使用 drop.view("檔案名稱") 的方式回傳畫面:

以上便是 Vapor 的一些基本的簡單介紹,有興趣的人可以一起研究研究!