PTT 好讀版開發經驗談

前言

小弟與 Raincole 合力開發的 PTT 好讀版 上架快滿一個月了,期間得到不少迴響與幫助,特別撰寫此文聊表分享與感謝。

一切從一場黑客松開始

那是個晴朗的午後,剛載完女友去上課的我,腦中浮現一個瘋狂的點子:『應該要有一個 PTT App 乘著 iOS 7 的更新一起推出』。所以就在一次的下課中,我跟夥伴就敲定了要進行這個專案,代號 iPTT,不知天高地厚的設定開發時程為一個星期。然後我們也規劃了一天來做黑客松。

黑客松的開始總是美好的,儘管事前已經做了準備,但千算萬算我們都不會發現這背後的技術難度比我們想像中還來的高。那天我開心的畫著設計,Raincole 開始寫 parser。到了後來才發現事情沒有我們想像中的單純(故事的內幕就我們天真地以為把 TCP 連線接好,東西就會照順序進來...)。

黑客松的尾聲就是兩個人合力鑽研 parser 該怎麼寫但卻一籌莫展的奮鬥到了天亮。

儘管隔天回家後,一些基礎問題被解決了,解析器的開發也慢慢上軌道。我們這也才發現一個星期的時間完全不夠用,我們都過度的自信跟自負。於是一個星期的時程變成一個月,然後再變成變成三個月。直到我們把那個叫做 MVP 的待辦事項都清完後,就送交審核了。

然而所有荒腔走板的事就在我們送審後發生。

蘋果的 reviewer 完全無法使用這個 app,Curpertino 內的 WiFi 似乎被東方神秘力量給擋在 PTT 的伺服器外,我們試了很多解決方法(換 port,改善連線方式等)但卻依舊吃閉門羹。儘管後來 PTT 的伺服器終於可以連得上了,但再加上一些非預期的 bug 總在 reviewer 審核時才出現,三個月就這樣過去了。預定釋出的日期從聖誕節延到農曆新年,再延到開學時才真的成功通過審核,期間我們被總共退了七次。

對我們而言,這是一個磨練心智的開發過程,因為儘管你知道蘋果有嚴厲的審核標準,但你卻不會知道所有的 bug 跟無法控制的因素都會在 reviewer 開始審核 app 時一路冒出來。教訓就是如果你想要投資 iOS 平台的話,請確定你已經準備好了(時間或是品質),否則只是徒耗精力。

批踢踢的行動體驗

談完開發的辛酸血淚(拭淚,另外一個很重要的點是 體驗 ,我們開發過程中不斷去詢問自己:『PTT 的行動體驗究竟是怎麼樣呢?』

過去使用桌上型電腦上 PTT 時,那是一個 80 x 24 大小的黑白畫面,它允許使用者可以用鍵盤上下左右搭配快捷鍵在文字的世界做巡覽。

那在 iPhone 上呢?我們想要做的事,就是去解構 PTT,然後設計一個專屬 PTT 的行動介面跟體驗。

舉一個例子好了,桌面版的 PTT 中,一篇文章在列表中顯示方式如下

其中第一個數字代表文章編號,再來那個記號表示文章狀態,有下面幾種可能,

  • 代表未讀
  • 代表有未讀的評論
  • 代表這篇文章被鎖起來了

然後則是人氣、日期,作者。而標題前的那個記號則表示文章的類型,可能會出現的記號有下面幾種可能,

  • 表示一般文章
  • 的話表示轉錄的文章
  • R: 則表示這篇文章是回覆某一篇文章

這樣仔細看下來你會發現,光是這一列的資訊密度就高到不可思議,不過卻忽略了一件事,很多使用者其實並不知道這些記號代表的意思。就連我們也是到開始開發才真正去仔細了解這些記號的意思。所以對我們來說,怎麼選擇關鍵的資訊然後用 更簡潔的符號 呈現就是一個需要面對的課題了。

我們不斷去想最重要的資訊究竟是什麼?不外乎就是,

  1. 已讀未讀的狀態
  2. 人氣
  3. R 文與否

這幾個因素影響到使用者會不會去看這篇文章,也是使用者在瀏覽掃描文章列表時會去特別注意的資訊。

所以最後我們用了一個藍色圓球表示未讀,空心圓球表示有未讀的評論,這個簡單的隱喻也被用在系統內建的 Mail.app(如下圖)。而為了加強已讀未讀的對比,已讀文章的標題也會被淡出。

最後的結果如下圖,去除多餘的,留下重要的,因為少即是多 (Less is more.)

App 中還有其他微小的細節(文章段落,複雜的看板名稱搜尋演算法等),你可能不會去特別注意到,但是就是這許多的細節串連起來,才讓好讀版使用上能夠如此舒適而自在。

嶄新的技術架構

終於來到技術架構了(擦汗),由於 Raincole 不會寫 Objective-C ,所以在好讀版的開發中他貢獻了他最擅長的 JavaScript。

在一個從溼度計到瀏覽器都可以跑 js 的年代,誰說 iOS app 沒辦法直接執行 js 的!

蘋果在 iOS 7 的釋出中藏了一顆沒有什麼人注意到的珍珠 - JavaScriptCore。JavaScriptCore 簡言之就是一個 js 的 runtime,傳統上要讓原生的 app 跑 JS,方法不外乎用 WebView 然後手刻傳遞 message 的方式跟協定。JavaScriptCore 的好處是讓你不用載入肥大的 WebKit,更棒的是還有原生的 bridging support。

回到好讀版,我們背後的批踢踢解析器完全都由 JS 實作,Objective-C 這邊則透過定義好的 API 跟解析器做請求,然後再將資料呈現出來。

乍聽之下瘋狂的架構,但實務上卻讓我們之間的開發幾乎平行。我可以專注在 UI/UX 上,而我的夥伴則可以專注在 API 的穩定度跟速度。更有趣的是,我的夥伴進行開發時,可以完全用 node.js 來進行,不需要編譯 Objective-C 也不用開模擬器。

結語

我們都知道產品上線後才是真正的開始,不過光是開發或許就是一趟有趣的旅程了。萬分感謝過程中給我們回饋跟幫助的使用者、朋友與前輩們。沒有你們的愛戴跟支持,我們不會有動力繼續前進。

最後,小弟我將在 3/21 號的HackNTU 開發小聚中分享這一次的開發經驗,當天你可以聽到更多的內幕、技術細節以及辛酸血淚,期待在那天看到各位 :)

為什麼你該用 Parse 來打造 MVP

前言

小弟前些日子因為朋友介紹開始使用了 Parse 這個平台服務。最近正在進行的創業計劃 Score Master -- 專為國高中生設計的解題平台 也採用了它來進行 iOS App 以及網頁後台的建置。前前後後大概花了 六個工作天 就做出了第一版的服務,這種速度,是我過去任何開發經驗所不能比擬的。

從前,如果你問我說:『我要儘快做出一個 MVP(Minimum Viable Product),有什麼建議嗎?』我可能會推薦 Rails + Heroku。但是現在,如果你想要趕快將服務上線,得到回饋,我會推薦你使用 Parse。

Read on

ReactiveCocoa 筆記

ReactiveCocoa 是 Github 推出的一個 Objective-C 框架。GitHub 用這個框架推動了 GitHub for Mac 的開發。

響應而非維護狀態

舉例來說,你有一個按鈕,但他會在特定狀況時才可以被按(例如說密碼欄跟使用者名稱欄都不為空白)。一般的做法一種是不斷的去檢查(polling)或者是在每次改變相依的狀態時(例如輸入密碼)都去做這個檢查然後再改變按鈕的狀態。

響應式編程則提供了另一個手段,讓你去『宣告』這個按鈕的狀態該怎麼被決定。

Signal

這是 RAC 的核心概念。你可以使用運算元(mapping, filtering, chaining)來將眾多的訊號組合起來。

下面的例子是 NapCat 最近採用 RAC 解決的一個問題。

[[downloadSignal flattenMap:^RACStream *(NSData* data) {    
        // Save to temparay path
            NSString* tmpFilePath = [code temporaryFilePath];
          [data writeToFile:tmpFilePath atomically:NO];
            // Extract the file
            return [[[ZipfileExtractor alloc] init] extractFileFromPath:tmpFilePath toPath:[[code uniqueDirectoryURL] absoluteString]];
}] subscribeNext:^(NSNumber* progress) {
            [subject sendNext:@([progress doubleValue] / 2.0 + 0.5)];
} error:^(NSError *error) {
            [self showDownloadingError:error];
            [subject sendError:error];
} completed:^{
            [subject sendCompleted];
}];

downloadSignal 背後是真正下載的動作,而要等到下載完成後,我們才能進行解壓縮的動作。傳統的寫法面臨的問題是,這兩個步驟都可能會遇到需要處理錯誤的部分,就會巢層的寫的盤根錯節。但引入 Singal 的概念後,一旦有錯誤就會自動結束這個 Signal ,所以只要寫一遍的 error handling 就好。官網首頁上還有不少有趣的例子應該都可以讓你體會它的威力。

現存整合

這個就是難點所在了,引入這個框架就是引入一整套新的編程習慣。不只要對團隊進行再教育,現有的框架也需要改寫。主要是先前 Networking 太過仰賴 AFNetworking,造成我的卻步,不過有人已經把這個接口補上了,AFNetworking-RACExtensions

除此之外,官方也提供了不少與現存 KVO 或是 delegate based 的方法做整合,例如 RAC 可以直接將物件的屬性綁定到某個 signal,UIActionSheet 等 UIKit Control 都可以拿到有用的 signal

小結

我當然不會吹噓這個框架是什麼神兵利器,不過會持續將這個框架整合到現有專案。當然,這也是小弟初次使用這種編程範疇跟這個框架,主要是拋磚引玉,希望各位中文開發者也可以多多交流相關開發與使用經驗。

相關資源

Hello World

Hi, This a demo post of Logdown.

Logdown use Markdown as main syntax, you can find more example by reading this document on Wikipedia

Logdown also support drag & drop image uploading ( required Beta / Premium membership). The picture syntax is like this:

Bloging with code snippet:

inline code

Plain Code

puts "Hello World!"

Code with Language

puts "Hello World!"

Code with Title

hello_world.rb
puts "Hello World!"

MathJax Example

Mathjax

Inline Mathjax

The answser is .

Table Example

Tables Are Cool
col 1 Hello $1600
col 2 Hello $12
col 3 Hello $1