在寫我的週末習作時, 為了抓取bitmap上所有的pixels時, 碰到這問題, 找了整個晚上才找到答案

首先, 要抓取bitmap上所有的pixel必須要使用到CoreGraphics裡的CGBitmapContextGetData, 想當然爾, 這是老舊的API, C API, 那用Swift可以去取用嗎? 參照Interacting with C APIs 這篇裡面有相關的Swift與C之間的對照

回到取pixels這目的, 我希望取到的值是一個整數陣列, 精確來說是[UInt32]的陣列, 但CGBitmapContextGetData回傳的是什麼呢? 查文件可知是UnsafeMutablePointer<Void> ,依照上面的文件來說, 對照到C則是"void *“, 文件裡面有提到:

When a function is declared as taking an UnsafeMutablePointer<Void> argument, it can accept the same operands as UnsafeMutablePointer<Type> for any type Type.

意思就是, 它也可以是整數陣列指標, 但, 怎麼轉回去呢? 這就是讓我整個晚上超崩潰的, 幹, 這文件沒寫!

搞半天, 才終於讓我找到答案:

let data = CGBitmapContextGetData(context)
    
let intData = UnsafeMutablePointer<UInt32>(data)
let intArray = Array(UnsafeBufferPointer(start: intData, count: bitmapByteCount))

原來必須先把它轉型成UnsafeMutablePointer<UInt32>, 然後再用UnsafeBufferPointer轉存到實際的陣列中(因為UnsafeBufferPointer是SequenceType, 才可以轉成Array), 當然也要給它陣列大小

但實際上這樣做對不對, 安不安全…還不知道, 找到答案寫下來先

(通車的路上寫blog最討厭碰到網路出狀況呀!) KVO (Key Value Observing) 是用來監控一個變數是否有改變的技巧, 在做UI的data binding來說是蠻有用的, 舉個例子, 如果有個Label是一直顯示網路蒐集過來的讀數會一直變化, 一般的作法是收到這讀數再直接去操作設定這個label, 缺點是會把這類的資料邏輯跟UI邏輯綁死, 而且如果是有多個以上要跟這資料連動, 較不易擴充 (還沒完全醒, 例子舉得很爛), KVO提供一個比較好的方式利用監控數值變化來處理這件事

網路上找到一篇文章, 或許這篇解釋的比我好: 了解 Objective-C 上的 KVO(Key-Value Observing) 機制

不過這篇主要是針對Objective C的, 觀察者利用實作observerValueForKeyPath來得到狀態, 而被觀察者利用了addObserver加入觀察者

當數值變動時, 必須要透過willChangeValueForKey 和 didChangeValueForKey 來通知觀察者, 這點算比較麻煩, Swift則不需要自己去呼叫, Swift在語言本身來說就已經設計了監看變數變化的特性, 最簡單的方式是透過willSet和didSet, 例如:

  var myVar:Int {
     willSet {   
       ....
     }
     didSet {
       ....
     }     
  }

不過這樣其實還沒達到KVO的效果, 只算是一個hook來處理數值變化時要做的事, 在Swift中也是透過addObserver和observerValueForKeyPath, 唯一不同的是不需要willChangeValueForKey 和 didChangeValueForKey, 基本上只要把變數宣告成dynamic就好

dynamic var myVar: String = "ssss"

這邊很重要的是一定要宣告成dynamic, 不然會沒效果喔!

(剛好到站, 寫完下車)

想說既然打定主意想玩一下iOS上的開發, 就從一些最夯的東西開始, 使用Swift自是不用說(另一個原因是我還是不喜歡看Objective-C), 另外想說最近FRP (Functional Reactive Programming), 搭配Swift也剛剛好, 因此想說就從ReactiveCocoa來上手吧….

似乎在iOS流行著用CocoaPods來做相依性管理(dependency management), 如果用Objective-C, ReactiveCocoa 2.x版就已經夠用了, 用CocoaPads也非常簡單沒啥問題, 只要在Podfile裡加入即可:

pod ‘ReactiveCocoa’, ’~> 2.4.4’

但根據下面這篇

“MVVM, SWIFT AND REACTIVECOCOA - IT’S ALL GOOD!”

但2.x跟swift不搭,如果要在Swift上使用2.x版的ReactiveCocoa,就必須要使用這個分支 但使用這個分支的話, 就沒辦法透過CococaPods來管理了

不過所幸, ReactiveCocoa打算在3.0之後直接支援swift, 那如果, 把pod改成:

pod 'ReactiveCocoa’, ’~> 3.0’

不就應該搞定了嘛?代誌當然不是我這笨蛋想的那麼簡單, 首先, 把這些library含進swift project裡要使用dynamic frameworks而不是static library, 這在iOS是iOS8之後才支援的, 而CocoaPods要到0.36才有支援, 針對這個, 就必須要在Podfile裡加上一行

use_frameworks!

但…這樣還沒結束, CocoaPods的repository裡的ReactiveCocoa 3的版本其實只有到 3.0.0 alpha 1, 那是相當早期的版本, swift版本的ReactiveCocoa正在積極開發中, 因此這版本舊到如果是用最新的XCode是會編譯不過的, 問題會出在LlamaKit, LlamaKit版本在CocoaPods上是0.1.0, 但最新的則是0.6.0, 在最新版已經解掉這問題了

那這樣表示我得放棄方便相依管理系統轉向手動了嗎? 還好還有Carthage, 這個跟ReactiveCocoa都是同一個作者做的(好像是Github的員工), Carthage跟CocoaPods的概念不同, CocoaPods的採取的是傳統的中心化的管理, 但Carthage的套件則沒有中心的repository, 而是分散在各個git(github project)中, 相對來說比較簡單單純(感覺就像做git clone後再用xcodebuild去build成framework而已), 不過我在這反而碰到更多的陷阱

像CocoaPods的Podfile一樣, Carthage也有一個設定檔Cartfile, Cartfile的內容跟Podfile很像, 像是要加入ReactiveCocoa 3的話只要加入一行:

github “ReactiveCocoa/ReactiveCocoa” ~> 3.0

這邊跟CocoaPods不一樣的地方是, 它的版本管理部分完全靠git, 因此這行它會去github抓tag是v3.0的最新版本(以現在來說最新的是v3.0-beta.1), 它也可以接某個git branch, 比如說github “ReactiveCocoa/ReactiveCocoa” “swift-development”, 寫好Cartfile後只要執行:

carthage update

它應該…理論上啦…就會把相關的porject抓回來build成framework, 然後去General settings把這些framework”手動”含進來(它不像CocoaPod那麼自動):

不過, 我為什麼說"理論上", 原因是我又碰到問題了!在build到LlamaKit(又是它)時我發生以下的問題!

“project has no shared schemes” error

所以ReactiveCocoa並沒被build出來(X的)

看來幾個對這issue的討論, 似乎是執行xcodebuild -list過久timeout, 導致抓不到build target, 而根據commit的log, 0.6.5似乎解決了這問題, 我原本是用brew install carthage裝的, 所以版本是0.6.4, 所以有抓了package來裝0.6.5

裝了0.6.5就解決了嗎?我這笨蛋真的想的太簡單啦!一樣有問題啦!…沒解嗎? 開了那個commit來看, 原來它所謂的解不過是延長timeout並且把錯誤訊息改成讓人家知道那是因為timeout而不是沒有shared scheme

那怎辦, 該回歸手動了嗎?不!要追根究柢!,但看半天好像也沒啥招, 死馬當活馬醫? cd進去Carthage/Checkouts/ReactiveCocoa/目錄, 發現也有Cartfile, 那就在這邊也來一次carthage update吧….結果有沒問題?有!狀況出現在底下這兩行(Cartfile.private):

github “Quick/Quick” “master” github “Quick/Nimble” “master”

這次它說沒"master"這個object, 但carthage不是有支援用branch name嗎?不管, 拿掉試試, 把"master"都拿掉後, 就成功在這邊build過ReactiveCocoa, 那回到上一層還會出現xcode -list發生timeout的問題嗎?回到上層目錄再試, 正常了, 一次通過! X…該不會是這問題吧?這是什麼情形….那我把github “ReactiveCocoa/ReactiveCocoa” ~> 3.0 改成github “ReactiveCocoa/ReactiveCocoa” “swift-development"呢?果不期然!!一樣的問題!!不是應該有支援用branch name嗎?

(寫太多很累了, 省略發現原因的過程…底下直接寫原因)


原因是我電腦裡面有兩個git, 一個是git 1.7的版本, 一個是macos 10.10幫我裝的git, 那個則是2.3.2的, 因為路徑設的問題, 一直都抓到1.7的版本, carthage認branch name是靠git revparse, 大概1.7跟carthage不相容, 導致了這問題, 改成2.3.2之後….一路順暢….看來來去回報一下好了

但問題不是到這邊為止, carthage還是一個很新的東西, 還有一些像是Facebook/pop並不支援carthage, 現在還沒辦法全用carthage管理, 還好CocoaPods跟Carthage是可以混用的, 所以接下來應該可以讓我順利開始寫東西了吧(希望如此)

後記補充


經多次實驗, Shared scheme跟no object的確是兩個不同的問題, 前者0.6.5的確解決了這問題(我猜是我mac mini太慢所以timeout延到8s還不夠才又碰到, MBPR則沒這問題), 後者的確是git版本造成的