Socket.IO

這篇文章會介紹有關於 Socket.IO 在 server 端以及 iOS 端的一些實作分享。

Server

首先我們可以先從 Socket.IO 的 Get started 開始,它是以 Node.JS 所編寫的,所以先在資料夾位置執行

npm init

邊可以取得基本的一些檔案和 package.json。
接著安裝 Socket.IO 所需要的 express

npm install --save express@4.15.2

然後新建一個 index.js 的檔案,貼上

var app = require('express')();
var http = require('http').Server(app);
app.get('/', function(req, res){
    res.send('<h1>Hello world</h1>');
});
http.listen(3000, function(){
    console.log('listening on *:3000');
});

這樣待會執行 node index.js 的時候便可以從 http://localhost:3000 來連上這個 server。
確認 server 目前是可以連上後,便開始安裝 Socket.IO 到其中

npm install --save socket.io

並在 index.js 裏頭加上 Socket.IO 的 code:

var io = require('socket.io')(http);
io.on('connection', function(socket){
    console.log('a user connected');
});

這樣便完成監聽 client 連接上 server 的事件了。
'connection' 是 socket.io 所定義的 event,而這個 event 會取得 socket 回來,所以以 function(socket) 的方式去接收並處理後續。
不過這裡的 io 可以想成是一個 server,所以每收到一個 client 的 connection 事件,都會執行 function(socket);而我們若要監聽個別連線的事件,則是使用 socket 來處理:

socket.on('disconnect', function(){
    console.log('user disconnected');
});

也就是說,像是登入、傳送訊息等,和個別使用者相關的動作,我們都是以 socket 來處理。
而除了監聽(on)外,發送(emit)的動作也是如此,像是我想推一段文字給特定的使用者:

socket.emit('new message', '嗨!');

在 client 以及 server 端先彼此定義好 event 名稱,這樣就可以知道要監聽的事件為哪些。
什麼時候該用 io 發送,什麼時候該用 socket 呢?
舉個例子,我們建立一個 Socket.IO 的大聊天室,任何人只要連上這個網址,便等同於加入這個聊天室。

  • 使用者上、下線會顯示提醒
  • 說髒話的人會個別收到「此訊息無法傳送」的訊息
io.on('connection', function(socket) {
    io.emit('new user', '有人加入了這個聊天室!');
    socket.on('add message', function(from, message) {
        if (message.indexOf('馬的') > -1) {
            socket.emit('new message', '此訊息無法傳送');
        } else {
            io.emit('new message', from + ' 說:' + message);
        }
    })
    socket.on('disconnect', function() {
        io.emit('user leave', '有人離開了這個聊天室!');
    });
});

上面便可以得知哪種時候應該要大家都可以接收到、而哪種是只會有個別的使用者接收到。
再來介紹一下 ack,在發送一個 event 時,可以在後面補上一個 ack,而當對方收到的時候,可以透過 ack 來傳遞 data,而非再發送一個新的 event。
有點像是 HTTP request 的概念,發送一個 request(emit),接著對方會回傳 response(ack),不過這並不一定會有,也就是說你發送(emit)了一個 event 過去,雖然有夾帶著 ack,但是對方若沒接收那個 ack 的話也是沒用。
這邊是一個例子,我們從 server 給沒有收過這則廣告的使用者傳送一則過去,並希望使用者真的有收到,若沒收到下次就再傳一次:

// 判斷使用者是否有收到過廣告,若沒有的話執行
socket.emit('new advertise', '恭喜您獲得 $1,000 元折扣!', function(userID) {
    console.log('使用者(' + userID + ')收到廣告了!');
    //去資料庫更新,下次不用再推給這個 userID
)};

其中,function(userID) 便是一個 ack 或者可以說是 callback。
相對的,收到訊息的時候,server 也可以回傳 ack 回去給 client 告知:

socket.on('add message', function(text, ack) {
    console.log('收到: ' + text);
    ack('伺服器收到你的訊息了!請放心!);
});

如此一來,便會在收到訊息之後,以 ack 的方式回傳訊息回去。
而 ack 和 emit 所發送出去有什麼不同?
在 Socket.IO 的 protocol 裏頭有定義:

  • Packet#CONNECT ( 0 )
  • Packet#DISCONNECT ( 1 )
  • Packet#EVENT ( 2)
  • Packet#ACK ( 3 )
  • Packet#ERROR ( 4 )
  • Packet#BINARY_EVENT ( 5 )
  • Packet#BINARY_ACK ( 6 )

所以其實雖然動作類似,但 Socket.IO 可以辨別其中的差異,進而可以在 Socket 之中達到 Request、Response 的概念。

Client

Client 的部分,可以使用 Socket.IO 所提供的 Swift framework,它是基於 Starscream 所開發出來的,如同上述說的有使用到 WebSocket 來連接。
以 Swift 來說:

let manager = SocketManager(socketURL: URL(string: "http://localhost:3000")!, config: [configs])
let socket = manager.defaultSocket

這邊介紹幾個我所使用到的 configs

  • .log(true):開啟 LOG 的功能。
  • .forceWebsockets(true):若沒有使用這個的話,會以 HTTP polling 的方式連接,從 header 來看的話,就會顯示 connection: keep-alive;而使用了 .forceWebsockets(true) 的話,則會使用 WebSocket 來連線,則會顯示 connection: upgrade。
  • .reconnectAttempts(int):重新嘗試連線 n 次,超過就放棄。
  • .connectParams([String: Any]):這邊可以放 token 來做 Authentication。

為什麼不在 header 裏頭加上 Authentication 的欄位?
這邊 Socket.IO 有做解釋,為什麼不建議在 extraHeaders 加東西。
而 on / emit / emitWithAck 這幾個的用法就和 server 的概念一樣,這邊就不再多做解釋,
之後實作有遇到什麼事情再來補充(或是新文章)!

Xcode beta with CocoaPods

Xcode 10 beta 的 Swift 版本為 4.2,而若你目前所使用的 Pods 多數為 Swift 4 的話,該怎麼辦呢?
你可以在 Podfile 裏頭加上全域的參數來規範所有 Pods 的 Swift version:

post_install do |installer|
    installer.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
            config.build_settings['SWIFT_VERSION'] = '4'
        end
    end
end

如此一番便可以輕鬆地在 Pods 還沒全面支援 Swift 4.2 時就可以使用 Xcode 10 Beta 開發👏

Git LFS ( Large File Storage )

今天在更新 BlayPods 時,發現 Realm 的某個檔案(60.04 MB)超出了 GitHub 的上限(50.00 MB),所以無法順利地將檔案推上去 GitHub,而 Git 也自行 untracked 那個檔案,所以在 git status 上便失去了蹤影。
至於為什麼會將 Pods 的檔案全推到 GitHub 上呢?
而在 git push 的時候,有顯示解決的方法,便是今天的主題:

Git LFS

首先我們透過 brew 來安裝 git lfs

brew install git-lfs

接著繼續在 git 裡頭安裝

git lfs install

再來我們就來定義哪些檔案需要被 lfs track,像我這邊是這樣:

git lfs track 'Pods/Realm/core/librealmcore-ios.a'

然後可以透過指令來確認是否有被加入到 track 的名單

git lfs track
git status

現在就可以從 git status 之中再次看到剛剛沒推成功的檔案被 track 了!
git lfs track 的內容會被記錄到 .gitattributes 裡頭,所以也一併推上 GitHub 即可完成!
git push
這樣便可以在 GitHub 上處理單個檔案超過 50.00 MB 的問題,不過免費流量為 1GB / month。
 

DeviceSupport

每當 iOS Beta 更新時,原先的 Xcode  便會無法支援,需要透過從 Xcode Beta 的 DeviceSupport 複製新的版本到正式版之中才能使用;
反正都會做這件事,不如就將 Xcode Beta 裡頭的 DeviceSupport 上傳到 GitHub 上提供給 iOS 有更新,但還沒下載新的 Xcode Beta 的人使用吧!
傳送門點我

ignore Pods warning

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

inhibit_all_warnings!

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

upgrade git

前言

git 最近被發現有重大的漏洞,以我們能做的事情就是更新自己的 git!

在 macOS 上更新

macOS 上,預設的 git 為 git version 2.15.1 (Apple Git-101)
所以我們需要先透過 brew 來安裝 git
首先,我們先來更新 brew,這點和 CocoaPods 類似,需要更新本機端的項目;
順道升級 brew 目前裡頭的項目。

brew update && brew upgrade

接著就透過 brew 來安裝 git

brew install git

最後再將 Symbolic link 處理一下就好了!

brew link --force git

現在我們確認一下 git 是否已經為新版本(git version 2.17.1)!

git --version

HEROKU

前言

這幾個月透過家裡頭的桌機來定時跑爬蟲以及更新 Firebase 資料庫,不過電費也是一筆固定支出,所以趁今天想到這件事,便來搬移到免費的 HEROKU 來做這些事。
 

HEROKU

是一個有提供免費伺服器方案的平台,支援的語言算多,而我的爬蟲是以 Python 3.6 作為開發語言的,故選擇它來玩玩。
在免費的方案之中有一些限制,如 24 小時內得休息 6 小時之類的;
不過在這邊,我需要的只是一個可以執行 Cron Job 的地方,所以不需要升級主機的部分。
目前我的 HEROKU 裡頭包含著三個動作:

  • 每天台灣時間 00:00 時,到星座網爬蟲並記錄當日運勢到 Firebase
  • 每天台灣時間 09:00 時,到 PTT CodeJob 以及 Soft_Job 版搜尋 iOS 相關的文章,並 mail 到我的信箱
  • 每天台灣時間 12:00 時,爬 PTT 一些熱門看板的文章並發佈到機器人部落格以及機器人推特之中

我分成三個 .py 檔案,下方會說明我的作法。

簡單的建構步驟

首先,先到 HEROKU 的 dashboard 建立一個新的 App,接著照著它的敘述完成 Git 的建置。
我在資料夾當中,放入了下列的檔案:

  • PTT.py - 12:00 該做的事
  • iOS.py - 09:00 該做的事
  • Astro.py - 00:00 該做的事
  • requirements.txt - 提供給 HEROKU 知道要在這台機器上安裝哪些 Python 的套件
  • serviceAccountCredentials.json - 存取 Firebase 所需要的 credential 文件
  • Procfile - 用來告知 HEROKU 關於這個 App 的一些參數
  • clock.py - 利用 apscheduler 來完成 cron job 的撰寫

clock.py

import os
from apscheduler.schedulers.blocking import BlockingScheduler
sched = BlockingScheduler()
@sched.scheduled_job('cron', hour=0)
def scheduled_job():
    print('This job is run every day at UTC+8 8am.')
    os.system("python astro.py")
@sched.scheduled_job('cron', hour=1)
def scheduled_job2():
    print('This job is run every day at UTC+8 9am.')
    os.system("python ios.py")
@sched.scheduled_job('cron', hour=4)
def scheduled_job3():
    print('This job is run every day at UTC+8 12pm.')
    os.system("python ptt.py")
sched.start()

Procfile

clock: python clock.py

接著透過下方指令推送到 HEROKU 上頭部署

git push heroku master

便可以在 Free Dynos 的頁面看到我們剛剛定義的 clock python clock.py,接著把它打開即可。
螢幕快照 2018-05-18 下午5.12.43

Commitizen

閒聊一下

最近處於求職的階段,評估一份職缺的觀點也和過往不同;
以這篇文章來說,Git Commit Style 便是其中一點,詢問對方是否有固定的 commit 格式,
就可以大致上瞭解團隊的風格是哪種。
若還沒有個規範,或是想要參考的,可以繼續閱讀下去。

Git Commit

是以 AngularJS 的格式,區分成

<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>

其中 typescopesubjectbodyfooter 等,有一定的規範,
詳細的內容可以在 angular.js/DEVELOPERS.md 看到。
這篇文章主要要介紹的是一個可以讓 commit 符合上述規範的工具 - Commitizen

Commitizen

Commitizen 是以 Node.js 所開發出的一套工具,使用者可以透過它來處理 commit 的風格一致性。

安裝

我們可以透過 npm 的方式來取得

npm install -g commitizen

並且安裝 cz-conventional-changelog

npm install -g cz-conventional-changelog

在電腦中安裝完上述兩者之後,先切換到你所開發的 iOS 專案資料夾底下,
由於 Commitizen 是以 Node.js 開發,你需要在 iOS 專案裡頭建構一些 npm 所需要的環境,
故記得補上 npm 初始化以及使用 convertional-changelog

npm init
commitizen init cz-conventional-changelog --save --save-exact

輸入完專案相關資訊後,便可以使用 Commitizen 了!

使用

之後,就以 git cz 的方式來取代原本的 git commit
若所在的專案並沒有使用 Commitizen 的話,系統則會自動以原先 git commit 的方式來進行。
執行 git cz 之後,會以選單的方式來一步步符合剛剛訂的規範。

自動驗證 Commit 是否符合 AngularJS

在多人開發的時候,我們可以透過 commit 之前先執行一段 JavaScript 來確保 commit 的格式符合要求,來避免專案之中的 commit style 有不一致的情況發生。
首先我們得先安裝一些套件

npm install semver-regex --save-dev
npm install ghooks --save-dev

並加入驗證的 validate-commit-msg.js 檔案以及在 package.json 之中宣告。

"config": {
    "ghooks": {
        "commit-msg":"./validate-commit-msg.js"
    }
 }

若發生了權限上的問題,如

/bin/sh: ./validate-commit-msg.js: Permission denied

則可以透過 chmod 來調整 validate-commit-msg.js 的存取權限。

chmod 755 validate-commit-msg.js

這樣一來,便大功告成了!
若有人在專案中下了不符合標準的 commit,如

git commit -m "Test"

則會無法順利完成動作,且得到錯誤訊息:

INVALID COMMIT MSG: does not match "<type>(<scope>): <subject>" !
test

CHANGELOG

CHANGELOG 的部分,在此規範下會以 Bug FixesFeaturesBREAKING CHANGES 這三種 type 來產出,意思是指其他的 type 並不會被寫進 CHANGELOG.md 裡頭。
我們可以執行

conventional-changelog -p angular -i CHANGELOG.md -w

來生成 CHANGELOG.md 檔案,也可以在 package.json scripts 之中加入

{
    "scripts": {
        "changelog":"conventional-changelog -p angular -i CHANGELOG.md -w"
    }
}

這樣,之後只需要執行

npm run changelog

後記

在第一份工作時,便有嘗試引用進入到專案之中,不過那時候沒有完整地使用所有功能;
如驗證 commit、自動產生 CHANGELOG.md 等,
最近在整理 iOSTaiwan/resource 時,便認真的走過一次流程。
相關的檔案,如 .gitignorevalidate-commit-msg.jspackage.json 都可以在此專案中找到。
最後感謝 pofat 當初的分享,可以在 iOSTaiwan/resource 裡頭找到相關資源,
讓這次的建構流程跑起來算是順利,也祝福他新婚快樂💒

iOS Taiwan jobs

iOS Taiwan jobs

緣起

最近剛把手上的專案都告一個段落,便開始尋找工作上的合作機會。
恰巧看到 f2etw/jobs,透過 GitHub Issues 的方式提供工作資訊,
感覺挺好的,且是個大家都可以共同編輯的地方,於是便有了建立一個 iOS 版本的念頭。

和 f2etw/jobs 差異性

issue title

iOSTaiwan/jobs 僅提供 iOS 的工作機會,且技術較為單純(Objective-CSwift),
所以便選擇在 issue title 上僅提供公司名稱而已;
職稱以及使用的語言則使用 label 來作為區分依據,如 Intern、Junior、Senior、Swift 等。

issue template

我先是參考了 f2etw/jobs 的範本,並加入部分 PTT Soft_Job 版上的徵才格式,如

  • 員工是否需自備工具? (是/否)
  • 公司地址(填寫詳細至號)

 

後記

並在 README 之中,附上一些求職的網站,如 Yourator、indeed 等;
裡頭也都先下好關鍵字,點擊連結可以直接看搜尋結果。
希望可以透過這個專案讓台灣的 iOS 工程師在求職路上有個幫助!
 
之後再陸續整合一些資源到 Archie.tw 以及 iOSTaiwan,仿效 f2etw 的模式,
有興趣的朋友歡迎多加利用👏

Bitnami