練手 Cocoa 開發,開發並開源了一款 fir.im 的 Mac 客戶端,此篇主要記錄其開發歷程,總結一下遇到的問題
緣由
某一天突然翻到自己 GitHub 下有一個空倉庫,名字叫 fir-mac,提交時間在 2016 年 6 月 22 號,恍然大悟原來又是棄坑專案之一。
由於從事 iOS 開發後一直對 macOS 開發比較感興趣,感覺 Mac 應用有一種天生的美感,更是在 OS X 10.10 Yosemite 之後的扁平化、毛玻璃、各種視窗細節的優化感到和往日使用 Windows 的差異;那既然都已入蘋果的坑,何不順勢玩玩 Cocoa 桌面開發呢, 業餘時間前後寫了 MyMacMusicPlayer 和 CubicBezier,也算小入了一點點 Cocoa 的門,想繼續找專案在實戰中磨練自己,然後因為有段時間用 fir.im 比較多,隧萌生了為其做一個 Mac 客戶端的想法。
準備
開發之前首先得想想會遇到最大的問題是什麼?那應該是 fir 的介面,官方有沒有開放介面?介面有沒有限制?這是我作為一個第三方開發人員無法控制的事情,所辛 fir 在很早的時候就開放了他們的 API 及其文件,並且藉口都比較齊全,證明他們對此是保持開放態度,那麼介面的障礙沒有了還有其他問題嗎?
這時我就預估一下了應用的整體功能模組,以及大致實現方法,把專案難點提前篩出來看看,fir-mac 作為一個基於 Mac 系統的 GUI 客戶端,首先是 UI 的搭建,最初想法,用一個列表(NSTableView)呈現賬戶下所有的應用,然後點選應用可以展開其詳細資訊,感覺應該可以實現,當然 UI 動畫、優化什麼的暫且先不提,畢竟先得把功能做出來才是第一步。
功能架構
功能上主要參考 fir web 端,包括上傳應用、檢視應用列表、檢視應用詳情,差不多就這樣,簡簡單單實實在在。
後來有朋友在微博上評論說想要掃二維碼下載的功能,很快也增加進去了。
如果你是本軟體的使用者,有什麼功能上的意見或想法歡迎與我聯絡哦。
UI設計
UI 最終成品基本上也繼承與最初的想法而來,簡單的 List,彈開應用詳情,採用 Vertical Split View Controller 分欄設計,把左右兩邊分開並各自獨立 NSViewController,從而使業務邏輯分離開來,在 Storyboard 上看起來是這個樣子:
右邊的塊區域用了 NSBox 作收納子元素和框樣式,隱藏掉了 Title,想想在 iOS 上搞這個還得弄個 UIView 設設圓角什麼的。(有時候總會想到 CocoaTouch 裡的東西去)
Cocoa 裡原生提供了三種列表組建,分別是 TableView、Outline View、Source List,而 Outline View、Source List 都是基於 NSOutlineView
的定製,而 NSOutlineView
則 NSTableView
的定製,所以歸其總都是 NSTableView
一個東西。
fir-mac 左邊側邊欄的列表則用的 TableView,自定義其 Cell 來展示的:
最後的實際執行效果如下:
可以看到其實和 Storyboard 相似度很高,開發此類小應用使用 Storyboard 構建 UI 效率非常高,並且因為視窗設計成固定大小,要麼縮小要麼關閉,不能縮放,所以也不用去拉 AutoLayout,真的省心。
困難1:介面的除錯
從最開始本以為很簡單的 form-upload 操作,廢了兩晚上時間才搞定,因為 fir 的應用包是存在靜態檔案伺服器上的,而且後端採用了不同的雲端儲存商,所以上傳檔案需要兩個步驟,第一步先獲取憑證,第二步再用拿到的憑證進行表單上傳,因為應用二進位制和應用圖示是分開上傳,所以一次提交應用的操作其實會發三個請求,[拿 Token] -> 上傳圖示 -> 上傳應用。
有點不明白的是應用圖示本身也是從其應用檔案中提取出來的,為什麼不把這一步拿到服務端做呢?為了節省伺服器資源嗎?還有其應用資訊,感覺放到服務端做會更可靠,因為 fir 不止一個客戶端工具去做上傳,比如 Website、Command Line Tool,各種 IDE 外掛,每個端都需要去編寫這套包解析和兩次上傳請求,首先維護成本增高,解析提交的資料還不一定是可靠內容,造成髒資料提交到服務端。
好吧先不糾結這個介面設計了,想說的是為啥在一個上傳檔案的功能上繞了彎路,最後找到問題是因為一個引數的引數名寫錯了,並且服務端沒有錯誤反饋,仍然返回的請求成功,這我就懵了,然後抓了一下 web 所請求的引數值,拿過來用錯誤的引數名提交居然也成功了。這就是為啥蒙圈在這這麼久的原因。
困難2:包解析
本以為 fir 的介面如上文所說是由服務端解析應用包,提取出應用資訊比如應用名、版本號等等,結果發現其實是本地解析拿到這些資訊然後再提交給介面,拿 iOS 的包來說,字尾為 .ipa
,在 macOS 本地的流程大致如下:
- 用
mktemp
命令建立臨時目錄 - 用
unzip
解壓.ipa
到臨時目錄 - 逐級目錄查詢到
Payload
資料夾 - 讀取
Info.plist
檔案 - 根據
CFBundleIdentifier
、CFBundleShortVersionString
… 提取出應用資訊和 ICON 圖示 - 刪除之前生成的臨時目錄
mktemp
是一個 linux 命令會在系統層級建立一個臨時目錄,路徑是隨機名大致如下 /var/folders/r3/x98gzjpn7rxct9y4rtnjsv5m0000gn/T/
,不會重複保證一定安全性
unzip
是 linux 下解壓 zip 檔案的命令,通過引數 -d
來指定解壓目的地為之前生成的臨時目錄
讀取 Info.plist
檔案本打算用 defaults
命令來完成,後來發現實在不如讀取進 Dict 操作方便
以上便是使用者選擇一個 .ipa
檔案提交上傳前所需做的工作,由於 Android 包 .apk
資料結構又不一樣,所以支援 Android 應用上傳需要另外編寫一套解析邏輯,故目前 .apk
上傳還未支援。(話說寫這個真累,專案開源歡迎有志之士共享一份力量!)
引用
由於應用主要建立在與 fir.im 介面的 HTTP 通訊上,所以引入了 Alamofire 來做所有的網路請求,包括資料拉取和檔案上傳;然後因為介面返回所有資料格式均為 JSON,為了人性化程式設計又引入了 SwiftyJSON;專案列表還存在網路圖載入,為了省事也直接用了 Kingfisher;因為有了這些專案的積澱,我們才能把更多的注意力放在其他事情上,避免很多重複而基礎的工作,感謝開源!
開源協議
關於選擇開源協議,一直都沒有太深刻的理解,只知道 MIT 最開放自由,那就 MIT 咯,後來看到微博上大家評論說 MIT 難道不怕又被翻去上架賣錢嗎?之前就有 PPRows 的事情,PPRows 的作者也來跟我說讓我換掉開源協議。
翻看了一下,像開發元件大部分都採用 MIT,寬鬆,但是很多完整專案都採用了 GPL-3.0,比如 PHP CMS 系統 Drupal ,播放器 iina、SQLite 資料庫瀏覽器 sqlitebrowser 等等,他們採用 GPL-3.0 更是為了限制商業使用。作為一個開源作者的心願首先肯定是希望自己的專案能發展起來,專案被他人商業化估計很多作者都是不願看到的事,開源界關於協議版權的鬧的事情已經很多了,希望那些不勞而獲通過開源專案結合流量與市場榨取使用者的人,能把更多注意力放在做事情本身,而不是一點蠅頭小利和旁門左道。
一不小心吐槽了一番,本文差不多就這樣結束了,如果你還沒有 star 這個專案,趕緊來吧:github.com/isaced/fir-…