9 days ago

這篇實作主要是練習如何使用 Swift 去實現

  1. Get JSON from URL
  2. Parse JSON

因為我平常有 Ruby 開發相關區塊鏈接 API 的經驗,所以這邊我挑了 CoinMarketCap 來串接,主要是因為他有直接兌換美元的結果,不需要再去計算,像是其他交易所如 Poloniex 只有 BTC 對 USDT ,畢竟 USDT 並不是真正的美元匯率(只是錨定接近而已)

一開始的 Outlet 跟簡單的 Interaction 就不多說了,就是拉一下 UI 畫面,分別設定好 Label 怎麼顯示、Button 會觸發什麼事件。

在這邊我意外發現一個專門講解 Swift 開發技巧的 Youtube 頻道,講的內容都很新,就以我現在 2017/12/02 來看,可以在影片看到上傳日期是 2017/07/01 ,講解的是最新 Swift 4 如何利用 Decodable 去 Parse JSON

Parsing JSON Just Became Super Easy in Swift 4 with Decodable

直接上程式碼

先宣告一個 Decodable 的 struct, 自訂你需要的型別

struct CryptoCurrency: Decodable {
    let name: String
    let price_usd: String
}
...

解析後並設定 label text 的 function

func updateBitcoinPrice() {
    self.cryptoCurrencyLabel.setText("Fetching...")
    
    // Coinmarketcap

    guard let url = URL(string: "https://api.coinmarketcap.com/v1/ticker/?limit=10") else { return }
    
    URLSession.shared.dataTask(with: url) { (data, response, error) in
        guard let data = data else { return }
        
        do {
            let crypto_currency = try JSONDecoder().decode([CryptoCurrency].self, from: data)
            
            let btc_price = crypto_currency.first?.price_usd ?? "0.00"
            
            self.cryptoCurrencyLabel.setText("\(btc_price) USD")
            
        } catch let jsonErr {
            print("Error serializing json:", jsonErr)
        }
    }.resume()

這邊我比較懶的部分是直接拉 Array 的第一筆資料去顯示比特幣價格,因為 CoinMarketPrice 的價格顯示是依照交易量排名,也因此我認為比特幣交易量應該都是在第一名,所以在部份比較偷雞XD,不過還要在學習改進的部分是如何針對一長串的 Array 去找到指定符合的數值(今天只學拿到 data 然後解析完輸出)。

小結

不過稍微研究了一下 Swift 4 新的 Decodable + JSONDecoder 用法除了可以達到更簡單的語法撰寫,同時也能夠支援自訂型別,在以往的解決辦法常常要寫很冗長的程式碼或是使用第三方套件到了 Swift 4 都可以通通丟掉了。

實際作品在我的 Github watchOS-BitcoinPrice

參考資料

 
14 days ago

三個點的符號 ..., 英文的正式名稱是 Ellipsis,中文譯是指省略的意思,不過一般在學習這類運算符,下 google 的關鍵字應該多半都會是 three dots 或是 dot-dot-dot

展開運算符號 (Spread Operator)

這種運算符是一種速寫的語法,一開始可能不好理解,不過用上癮後就會熟能生巧了

const boys = ["Nic", "Kurt", "Jimmy"]
const girls = ["Doris", "Flower"]

console.log([...boys]) // ["Nic", "Kurt", "Jimmy"]


//  兩個 Array 組合

console.log([...girls, ...boys]) // ["Doris", "Flower", "Nic", "Kurt", "Jimmy"]

原本需要用 concat 完成的組合 Array 動作只要用展開運算符拼起來就可以了,而且不受 concat 只能放在陣列後方

替代原本 Array 的 apply 方法

由於擴展運算符可以展開 array, 所以在 ES6 之後不需要用 apply 方法,將 array 轉為函數的參數

// ES5

function sum(x, y, z) {
  return x + y + z;
}

var args = [1, 2, 3]
sum.apply(undefined, args); // 6


// ES6

function sum(x, y, z) {
  return x + y + z;
}

const args = [1, 2, 3]
sum(...args) // 6

展開運算符還有一個特別的功能,會把可迭代(iterable)或與陣列相似(Array-like)的Object 轉變為 Array

在 JavaScript 中內建的可迭代(iterable) Object 有

  • String
  • Array
  • TypedArray
  • Map
  • Set
const myName = "Nic"
const chars = [...myName] // ["N", "i", "c"]

參考資源

 
15 days ago

垂直下拉關閉鍵盤

通常用到 text view 讓人可以鍵盤輸入資訊的時候,其實可以貼心的加入垂直下拉就可以關閉鍵盤的功能,因為很多 APP 打完字是沒辦法關鍵盤的,這其實滿惱人的。

而這個功能也不需要額外寫 function , text view 元件中只要在 Scroll View 裡面將 Bounce Vertically 打勾就可以了。

開啟自動彈出鍵盤

如果你的這個頁面是一載入時,就是需要用戶填寫資訊或筆記的地方,像是內建的備忘錄,其實可以自動彈出鍵盤,不需要等用戶點擊螢幕之後才跑出鍵盤,這是一個比較貼心的方式。

只要在 Text view 元件中的 keyboard 設定為 Dismiss interactively 就可以了

 
15 days ago

必要時,需要依照風格或是設計師不同的風格來配合,所以我們會需要更改 Status Bar
的顏色,至於改的方式有兩種,可以透過設定檔或是直接從 code 裡面動態修改(比方說切換樣式等等)

以下圖來說,是更改後的樣式,如果沒更改就會跟背景一樣是全黑的,完全看不到 Status bar

前置動作

在專案下找到 info.plist 後將 View controller-based status bar appearance 這個設定關閉,如果找不到就自己新建一個然後設定為關閉就可以了。若設定沒關閉就無法改 Status Bar 的顏色,所以這個前置動作一定要記得做。

從設定檔改

在專案目錄下找到 Targets -> General -> Status Bar Style -> Light

從程式碼中改

UIApplication.shared.statusBarStyle = UIStatusBarStyle.lightContent
// 設定 status bar 為白色


UIApplication.shared.statusBarStyle = UIStatusBarStyle.default
// 設定為預設值

參考資源

 
16 days ago

Keyboard does not show up in simulator

Xcode 9.1

因為 Xcode 的預設是把你的電腦 keyboard 當作在模擬器裡面用的主選項,所以需要進去設定模擬器的 hard ware 鍵盤打開,如果沒有打開這個選項就會變成你寫了自動彈出 keyboard 卻在模擬器上沒有彈出來。

具體可以操作 Xcode HardWare -> KeyBoard -> Toggle Software Keyboard

也可以直接按下(⌘ + K),這樣一來 simulator 就會跟實機一樣有鍵盤。

以下是在使用者觸碰螢幕之前,也就是打開某功能或某頁面能夠自動加載鍵盤的方法

在 view did load 指定讓某 outlets 後加入 becomeFirstResponder

Usage:

class ViewController: UIViewController {

    // MARK: - View Did Load

    override func viewDidLoad() {
        super.viewDidLoad()
        subtotalTextfield.becomeFirstResponder()
    }
    
    // MARK: - Outlets

    @IBOutlet weak var subtotalTextfield: UITextField!
}

參考資源

 
16 days ago

ES6 之後可以使用 Set

var uniqArray = new Set([1, 2, 3])

uniqArray
// Set(3) {1, 2, 3}


uniqArray.add(3)
// Set(3) {1, 2, 3}


uniqArray.add(4)
// Set(4) {1, 2, 3, 4}

Javascript: Remove Duplicates From Array Of Objects

如果 Array 裡面是放 Object 就不適用了,網路上查到比較多的答案是有人寫了一個專門去除重複元素的 function

function removeDuplicates(originalArray, prop) {
     var newArray = [];
     var lookupObject  = {};

     for(var i in originalArray) {
        lookupObject[originalArray[i][prop]] = originalArray[i];
     }

     for(i in lookupObject) {
         newArray.push(lookupObject[i]);
     }
      return newArray;
 }

Usage:

removeDuplicates(arrayWithDuplicates, 'size');

Returns:

[
    {
        "color": "red",
        "size": "small"
    },
    {
        "color": "blue",
        "size": "medium"
    },
    {
        "color": "red",
        "size": "large"
    }
]

And:

removeDuplicates(arrayWithDuplicates, 'color');

Returns:

[
    {
        "color": "red",
        "size": "small"
    },
    {
        "color": "green",
        "size": "small"
    },
    {
        "color": "blue",
        "size": "medium"
    }
]

參考來源

 
20 days ago

目標:密碼設置必須要含英文及數字的組合

app/models/user.rb
  validate :password_complexity
  
  def password_complexity
    if password.present?
       if !password.match(/^(?=.*[a-zA-Z])(?=.*[0-9])) 
         errors.add :password, "Password complexity requirement not met"
       end
    end
  end

拿掉位數限制,這部份 devise 本身就有 validation , 甚至必要時可以直接在 devise config 設定 password length

/^(?=.*[a-zA-Z])(?=.*[0-9])/
   |             |          
   |             |          
   |             |          
   |             |
   |             任意字符串后必须要有「数字」
   |                        
   任意字符串后必须要有「英文」

參考資源

 
30 days ago

何謂 race condition ?

競爭危害(race hazard)又名競態條件、竞争条件(race condition),它旨在描述一個系統或者進程的輸出依赖于不受控制的事件出现顺序或者出现时机。 此词源自於兩個訊號試著彼此競爭,來影響誰先輸出。 -- wiki - 競爭危害

AASM 中的 race condition

開發 rails project 很常會使用 AASM 來進行狀態管理,好處是你可以不必寫一堆 if else 來判斷狀態該往何處走。

那麼有一種情況是,當兩個用戶同時更新一筆數據的狀況下,造成 race condition 的情況出現

舉例以交易訂單生成來說

  • order_placed: 是當訂單成立後的狀態
  • notify_paid: 是買家通知付款
  • done: 是完成訂單

那麼有種情況是,買家還沒通知付款,我這邊已經收到帳款,所以可以直接將訂單完成。

但有沒有一種可能是,賣家和買家在同一個時間點點擊狀態變更的按鈕

  • 賣家按下訂單完成
  • 買家通知已經付款

雖然在 AASM 很聰明的幫我們限制了能變更狀態的條件,但是卻會出現在被 object 暫存記憶體的時候,產生 race condition。

class Order < ActiveRecord::Base
  include AASM

  aasm do
    state :order_placed, :initial => true
    state :notify_paid
    state :done
  end
  
  event :notify do
    transitions :from => :order_placed, :to => :notify_paid
  end
  
  event :deliver do
    transitions :from => [:order_placed, :notify_paid], :to => :done
  end
end

這時候我們測試

# 這時候 Order 狀態應該是初始化狀態 order_placed

order_1 = Order.last
order_2 = Order.last

order_1.deliver! # true, 狀態變更為 done
order_2.notify! # true, 狀態變更為 notify_paid

order_2 不是應該被 rollback 拒絕存取才對嗎? order 應該已經變成 done, 沒理由又可以變成 notify_paid 才對呀

但在這個案例中的 object 裡面

order_1, order_2 這兩個 object 已經透過暫存記憶體記住訂單的狀態是 order_placed,所以他們合理的可以轉換狀態,但這樣就不符合我們的邏輯了,難不成在每次 order 執行的時候都要 reload 嗎?

其實不必,我們可以用 AASM 中的 Pessimistic Locking (悲觀鎖定) 來解決。

只需要加上 requires_lock: true,就可以避免 race confidtion

class Job < ActiveRecord::Base
  include AASM

  aasm requires_lock: true do
    ...
  end

  ...
end

讓我們在測試一次

order_1 = Order.last
order_2 = Order.last

order_1.deliver! # true, 狀態變更為 done
order_2.notify! # ROLLBACK, AASM::InvalidTransition: Event 'notify' cannot transition from 'done'.

完美了,可以避免同時間操作產生的 race condition。

小結

不過 Pessimistic Locking(悲觀鎖定)要看情況使用,因為他會影響一些 performance , 他的執行原理是如其名,悲觀的認定每一筆資料存取時,其他的客戶端也會存取同一筆資料,因此對相關的資料進行鎖定,直到自己操作完成才解除鎖定。

這樣的好處是

  • 提高隔離層級
  • 避免 race condition

壞處是

  • 存取效能變差,因為每次都要鎖定

如果你的 project 不會有同時改變狀態的可能發生,可以不用強制一定要在 aasm 上加上 Pessimistic Locking,只需要在適當的時機在用就可以了。

參考資源

悲觀鎖定(Pessimistic Locking)
競爭危害

 
about 1 month ago

If you can not restore backup on high version (like iOS 11.2 beta), maybe you can try my solution.

Situation

currently, I have two Iphone devise

  • Iphone 7 Plus, iOS 11.2 beta2
  • Iphone X, iOS 11.1

I want to restore backup from a newer version of iOS 11.2 beta2 to a IphoneX devise using an older version of iOS 11.1, but iOS and iTunes does not support this.

If you try this, you may see the following error messages:

  • You cannot see or select the most recent backup from the list of available backups in iTunes or iCloud.
  • iTunes displays the alert “The backup …. cannot be restored to this …. because of the software on the …. is too old.”
  • iTunes displays the warning “No backups available.”
  • Apple 備份時出現備份已損毀或不相容

Solution

You can update older devise version to latest ios version, but latest ios beta version is not support Iphone X.

So, you can wait it, or try my solution and following this:

  1. backup on your iTunes now.
  2. Use Decipher Backup Repair tool
  3. Fix it and restore to your newer devise.

notice: 29.99 USD can repair 2 backup.

Now, I restore my newer version of iOS to older version of iOS successful now.

 
about 1 month ago

獲得實時更新的方法(Polling, Comet, Long Polling, WebSocket)

在 HTTP 協議上,只能由 Client 發起請求,等候 Server 端回應,然後獲得資料,來讓當前頁面更新。

但 Server 端是無法主動發起回應的,譬如說,當某討論串下面的留言更新了,要發起通知讓所有人的通知欄顯示一則未讀廣播,Server 端無法主動向所有正在線上的使用者進行廣播。

這時候就有幾種方法可以實作

Polling

早期的作法通常都是用 Javascript 來實作輪詢(Polling)的方式獲得 Server 端的最新資料。

利用 Javascript 中的 setIntervalsetTimeout 在固定的時間內向 Server 端發起 Request 以 JSON 或 AJAX(xhr)的方式取得最新的資料。

備註: setTimeout 是較好的選擇,請參考 [javascript] 深入了解 setTimeout() 與 setInterval() 的不同之處

不過這麼做的好處是容易實現,也沒有其他瀏覽器的延伸問題,最主要的缺點就是造成資源的浪費以及負擔,因為你不知道 Server 端的資料何時會有更新,再沒有更新的情況下你一樣要發起 HTTP 請求,就會有浪費網路資源的狀況,如果是效能較差的裝置例如智慧型手機,可能就會有耗電、使用上卡頓的狀況發生。

Comet

Comet 如果要翻譯成中文是「彗星」的意思,他的作法是把發出的 Request 像彗星的尾巴一樣拉的很長(正常的 Request 很快就結束),這樣一來就可以讓伺服器保持連線的狀態,持續讓 Server 端 Response 資料回來,這樣的作法其實就是把 Polling 做在 Server 端。

但這樣的作法會把傳統的 Web Server 如 Apache 的連線給佔住,所以必須要配合 non-Blocking IO 的 Web Server 才能運作。

Long Polling

長時間輪詢的作法是 Comet 演化過後的方式,也是目前 Facebook、Plurk 實現動態更新的方法。

這樣的作法是發一個長時間等待的 Request,當 Server 端有資料 Response 時立刻斷掉、接著在發一個新的 Request。

與 Polling 不同之處的在於他比較有效率,可以等到 Timeout 或拿到新資料的時候在重新發送,相對之下減少很多網路資源的浪費。

加上通常是 Client 在傳遞資料(每次發 Request 時)以及沒有瀏覽器相容性的問題,算是比較常見的解法。

值得注意的是,如果你的 Server 端不支援 non-blocking IO 的話,他其實還是一般的 Polling

例如

(function polling() {
    $.ajax({
        url: "http://server",
        type: "post",
        dataType: "json",
        timeout: 30000,
        success: function(data) {
            /* Do something */
        },
        complete: function() {
            /* Polling here. */
            polling();
        }
    });
})();

在上面的程式碼中,等待三十秒後重複發送 AJAX 請求到 Server 端,而 Long Polling 的作法是

  • Client side 發送 Request 給 Server side
  • Server 收到後 Response 給 Client ,並斷開連線
  • Client 收到 Response 後,執行 Callback ,再次發送 Request 給 Sevrer

這樣的方式是無窮迴圈,但如果後端收到之後並沒有斷開連線,那麼前端就只會每 30 秒斷線重連,其實也就是一般的 Polling 而已。

非 non-blocking IO 的 Server 不行的原因:

假定送一個 AJAX 給 Server side, 事情做完之後,丟一個 Response 給 Client , 一樣會觸發 complete 條件。

但當你的 Server 沒放開連線,你只能等 Client timeout, 並且再次發送 Request 才能繼續動作,而這時候 Server 的資料到底有沒有完成,根本不知道,所以 non-blocking IO 的 Server 多少能避開這種問題。

但這都只是單向的溝通。

WebSocket

WebSocket 的誕生就是為了解決單向請求的問題,可以在一條連線上提供全雙工、雙向的資料傳輸,在這樣的標準下你可以很容易實作一個兼具可擴充性與即時性的網頁應用程式。

他的特點

  • 支援程度高,參考Can I use?
  • 建立於 TCP 協議之上,服務器端實現較容易
  • 與 HTTP 協議有良好的兼容性,默認端口也是 80 與 443 port,並且在握手階段(handshake)採用 HTTP 協議,因此不容易被屏蔽,能通過各種 HTTP 代理服務器
  • 性能較 Polling 好,通信高效

協議標示符號是 ws(若加密則為 wss),服務器網址就是 URL

ws://example.com:80/some/path

WebSocket 算是借用 HTTP 協議來完成一部分的 handshack

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

詳細更多部分請參考

參考資源