Dropbox 從支持到放棄使用 C++ 跨 iOS、Android 平台開發的始末

寧願寫兩遍代碼,也不用C++跨iOS、Android平台開發:
https://kknews.cc/zh-tw/tech/y3g4knj.html

該文章發布於 2019年中

Dropbox 最近宣布將放棄用 C++ 編寫跨 iOS、Android 平台代碼,轉而使用各平台的原生框架(Swift / Kotlin),理由是代碼共享相關的隱藏成本太高。有趣的是,2014 年,Dropbox 在給 Facebook 的開發人員做分享時,卻曾直言寫兩套代碼會帶來種種弊端,推薦使用 C++ 進行跨平台開發。為什麼幾年前的經驗之談卻成了幾年後的技術債?C++ 因何躺槍?跨平台開發前景如何?本文試圖一一為你講解。


事件綜述

編寫一次,隨處運行。這句話不知道被多少工具和框架寫在開篇介紹中,用以吸引更多公司和開發者。然而,作為這一策略忠實的實踐者,Dropbox 近日宣布放棄這一想法,寧願為不同的平台寫兩遍代碼,也不用 C++ 跨 iOS、Android 兩大平台共享代碼的方式進行開發。

早在 2013 年,Dropbox 就採用上述策略進行移動開發,這背後的想法很簡單:用 C++ 編寫一次代碼,而不是用 Java 和 Objective-C 編寫兩次。那時,整個移動工程團隊相對還比較小,但需要支持快速增長的移動路線圖。因此,公司希望找到一種方法,使這個小團隊可以快速交付大量 Android 和 iOS 代碼。

如今,Dropbox 完全放棄了這個策略,轉而使用各個平台的原生語言(主要是 Swift 和 Kotlin,這兩種語言在剛開始制定移動策略時還不存在)。這個決定的主要原因是代碼共享相關的隱藏成本太高,這源於同一個基本問題:

以非標準的方式編寫代碼,使 Dropbox 承擔了一些開銷,而如果採用廣泛使用的平台默認選項,就不必擔心這些開銷,這種開銷最終比只編寫兩次代碼要昂貴得多。


C++ 的問題?

隨後,Dropbox 總結了成本太過高昂的主要原因,比如自定義框架和庫的開銷、自定義開發環境的開銷、處理不同平台差異的開銷以及培訓、招聘和留住開發人員的開銷,這與 C++ 本身存在的問題密切相關。但是,C/C++ 是唯一一種編譯器同時受到谷歌和蘋果支持的語言,如果使用其他語言將會產生一系列更麻煩的問題。

1、自定義框架和庫的開銷

關於使用 C++,最容易出現的開銷就是構建框架和庫,這大致可以分為兩類:


  • 框架,讓開發者與主機環境交互,構建一個功能齊全的移動應用程式。例如 Djinni 、用於在後台和主線程中運行任務的框架等。
  • 一些取代本可以在平台原生語言中使用的默認語言或開源標準的庫。例如:用於 JSON(反)序列化的 json11 ;C++ 非可空指針 nn 。

如果使用平台原生語言,那麼這些代碼都是不必要的。當然,並不排除個別開發者可以很好地利用開源 C++ 庫,但是 C++ 開發社區中的開源文化並不像移動開發社區那麼強大(特別是幾乎不存在的 C++ 移動社區)。這些成本在 C++ 中特別高(與 Python 或 C# 等其他可能的非原生語言相比),因為它缺少一個功能齊全的標準庫。

雖然 C++ 標準委員會為了這門語言做了大量工作,甚至為了 C++20 的新特性舉辦了一場歷史上規模最大的會議(180 人參會),試圖通過會議確定哪些特性可以加入新版本,比如 Concepts、Ranges、Modules、Coroutines 等,但大部分開發人員並不認可此次調整,並將部分新特性歸結為「語法糖」,在社交平台進行吐槽。

國外的一位遊戲開發人員接連在社交平台發表看法,甚至發表了一篇長文聲明自己不看好 C++20 的新特性,並認為新版本沒有解決最關鍵的問題,這讓其未出世就被群嘲。可見,整個 C++ 社區是存在一定問題的,很難讓其達到移動開發社區中的活躍度,很多問題也是懸而未決。

2、自定義開發環境的開銷

移動生態系統有許多工具可用來提高開發效率,但 C++ 社區就不太行。開發者必須投入時間構建支持 C++ 代碼共享的工具。甚至,Dropbox 親自做了定製構建系統,創建了包含 C++ 代碼以及 Java 和 Objective-C 封裝器的庫,並且生成 Xcodebuild 和 Gradle 都能識別的目標文件。這個系統是一個很大的成本支出和拖累,因為需要不斷更新以支持兩個構建系統中的更改。

3、處理不同平台差異的開銷

儘管 iOS 和 Android 應用程式都是「移動應用程式」,通常具有相同的特性和功能,但平台本身存在一些影響實現的差異,甚至不能真正地編寫一次代碼就在不同的平台上運行,必須花費大量時間將代碼集成到不同的平台中,並編寫特定於平台的代碼(有時這些代碼就位於 C++ 層)。

4、培訓、招聘和留住開發人員的開銷

如今,市面上越來越難招聘到具有相關 C++ 經驗、對移動開發感興趣的高級工程師。當然,如果願意提高招聘成本,給出更有吸引力的薪水,這個問題不難解決。但是,怎麼把這些開發人員留住也是一個大問題。事實上,移動開發社區有大量新技術和新模式層出不窮,移動開發者可能根本不想從事 C++ 項目開發。

如上種種,通過 C++ 共享移動代碼的方式讓開發成本變得非常高昂,這也讓曾經的支持者 Dropbox 不得不選擇放棄。


跨平台開發的 Yes or No

"Write once, run anywhere" 一直以來就是開發者的夢想。但從 Dropbox 的經驗來看,實現之路堪稱曲折甚至反覆。

我們發現了一個特別有意思的新聞:2014 年,Dropbox 的開發人員給 Facebook 的開發人員做了個講座,分享了他們使用 C++ 做跨平台開發的經驗之談。他們特別提到,iOS 和 Android 平台代碼庫的不一致會帶來一系列問題:

  • 開發和維護成本成倍增加。
  • 開發團隊需要多次修復同樣的缺陷。
  • 針對某個平台報告的缺陷可能會被另一個平台忽視掉。
  • 不同平台的 App 行為可能會有預料之外的細微差異。
  • 性能優化成本高昂並且與平台相關。

Dropbox 賴以克服上述這些問題的策略基礎就是為所有 UI 無關的代碼建立一個共享的跨平台 C++ 庫,例如數據和網絡部分的邏輯。UI 部分還是使用原生代碼編寫,以便儘可能地利用平台對動畫特效和設備傳感器等的支持,並且確保充分的響應速度。

5 年過去,當年的經驗之談似乎變成了 Dropbox 的技術債,他們最終放棄了用 C++ 做跨平台開發,轉而用各平台的原生語言做開發工作。


跨平台開發,是時興潮流還是明日黃花?

2018 年,Airbnb 放棄使用 React Native,回歸使用原生技術,究其原因主要是:項目龐大後,維護困難;第三方庫良莠不齊;兼容上需要耗費更多精力……

騰訊客戶端工程師方秋枋接受 InfoQ 採訪時曾表示:

這些都是跨平台框架的通病。跨平台意味著需要花費很多時間來解決平台差異性問題,同時要面臨第三方庫不夠原生平台豐富健壯的現狀。跨平台其實是犧牲部分功能和體驗,換取開發速度和一致性的權衡,並不是業務開發的銀彈。

微信團隊在跨平台開發方面的解決方案是:

目前微信團隊並沒有統一使用一套跨平台開發框架。我們也在積極探索各種可能性。

微信小程序,是跨平台開發的第一、二階段的融合做法,利用 Webview 作為渲染容器,通過規定 Web 技術棧的子集(WXML、WXSS),讓客戶端獲得更多的渲染性能優化空間,同時通過規範化組件的使用,逐步引入原生控制項代替 Webview 渲染。

除此之外,我們還探索了基於 C++ 進行跨平台開發。實質上還是第二階段的做法,只不過是使用 C++ 作為中間語言,去編寫業務代碼和調用原生控制項。為什麼要採用 C++ 呢? 因為 C++ 安全性會更高一點,同時性能也會更佳。可以應用在一些對安全性要求比較高的場景。


Flutter 會是跨平台開發的終極之選嗎?

這個問題很有意義。能實現跨平台開發的框架也五花八門,讓人眼花繚亂。最流行的跨平台框架有 Xamarin、PhoneGap、Ionic、Titanium、Monaca、Sencha、jQuery Mobile、React Native、Flutter 等等。但這些工具的表現也是高低有別,各有千秋。熱度不減的只有 React Native 和 Flutter,原因在於這二者背後有 Facebook 和 Google 背書,開發者更加信任。

Flutter 的優勢在於:它完全免費,徹底開源;可以用來更快地創建應用;具有出色的用戶介面(UI);節省代碼量;可接入平台原生功能;最適合 MVP 開發(最小化可行產品);較老的設備也使用相同 UI 運行應用;減少測試工作量;更豐富的社區支持;較低的維護難度;內置來自 Dart 的包管理器。

其中不少特性如節省代碼量、接入平台原生功能、支持較老設備、較低維護難度等等,都擊中了跨平台開發的痛點。業界認為,目前跨平台開發沒有一個完美的解決方案,相對來說 Flutter 在兼容上做得好一點,通過自底向上自研框架來儘可能減少平台差異,但是可靠性仍需進一步檢驗,引入 Flutter 需要大家對自己公司業務有非常清晰的認知,並權衡好利弊。

隨著 Google 對 Flutter 的持續加碼,也許在不久的未來,Flutter 將成為跨平台開發的最佳解決方案。


一個 App 在 iPhone 中的 life cycle

Stanford大學CS193P第三堂課的其中一張投影片:

一個 App 在 iPhone 中的 life cycle


Objective-C 的記憶體管理:
https://kaochenlong.com/2010/11/22/memory-management-in-objective-c/

Objective-C 記憶體管理之 Autorelease:
https://kaochenlong.com/2010/11/25/autorelease-in-objective-c/



macOS 快捷鍵

多重桌面
Control + 向上鍵
Control + 向左鍵
Control + 向右鍵

資料夾或文件
command+delete (丟到垃圾桶)
command+C (複製)
command+V (貼上)

處在中文輸入法的情況時
shift+, (逗號等等標點符號)
shift+. (句號等等標點符號)
其餘標點符號依此類推

擷取畫面
command+shift+3 (擷取全螢幕)
command+shift+4
(拖移十字線 以選取要擷取的螢幕範圍。 若要移動所選範圍,請在拖移時按住空白鍵。 若要取消拍攝截圖,請先按下Esc(Escape)鍵。)

另外還有『擷取軟體視窗,有陰影』、『擷取軟體視窗,無陰影』、『固定擷取大小,移動擷取區域』、『固定擷取長度或寬度,移動另一邊大小』,詳閱
https://www.bnext.com.tw/article/41237/mac-screen-capture-keyboard


Xcode 快捷鍵

文檔和參考: command + shift + 0 (Zero)

程式碼
增
command+C (複製)
command+V (貼上)
刪
control+K (删除整行)
control+D (删一個字元)
改
command+[ (程式碼在同行往左縮)
command+] (程式碼在同行往右縮)
command+option+[ (整行程式碼往上移動一行)
command+option+[ (整行程式碼往下移動一行)
command+/ (註解)
command+Z (回復上一步)
command+B (編譯)
command + \ (開啟中斷點狀態設中斷點)
command+左/右键 (程式碼頭/尾)
command+shift+左/右键 (選取光標以左/右的程式碼)
command+L (直接跳到輸入行數的程式碼)
command+A (全選)
查
command+F (查此文件內的關鍵字)
command+Shift+F (查此專案內的關鍵字)

文件
增
command+N (增文件)
command+option+N (New Group)
刪
command+W (關閉視窗)
改
command+O (打開專案)
command+S (儲存)
查
command+shift+O (查專案內的文件或某個方法定義)
command+control+左/右鍵 (剛瀏覽過頁面的順序)

編輯區
command+0 (隱藏左區Navigator)
command+option+0 (隱藏右區 Utilities)
command+shift+Y (隱藏下區 Debug Area)
command+control+F (視窗最大化)
command+H (視窗最小化)
command+tab (把最小化後的 Xcode 顯示回來)

輔助編輯器 Assistant Editor
command+option+enter (開)
command+enter (關)
option+左鍵點擊操作 (在輔助編輯器 Assistant Editor 中打開文件: 在項目導航器中選中文件執行)
control+6 (文件跳轉:鍵入方法/變量名+Enter 跳轉)

storyboard
control-shift-click (顯示UI階層清單)
control + enter (Label 內文字換行)

模擬器
command+R (跑模擬器)
command+Q (關閉模擬器)
command+左/右键 (切換橫豎屏)
option+shift (在模擬器中雙指拖動效果)
option (在模擬器中放大縮小效果)
command+shift+H (模擬器的 Home 鍵)
command+K (當 keyboard does not show up in simulator)

iOS 開發究竟該不該使用 Storyboard?

Google 關鍵字
「不使用 Storyboard」、「iOS 純程式碼」、「nib xcode」、「segue swift」


OK,讓我們坐下來好好談談 Storyboard:
https://medium.com/@royhsu510/aec2656cf16e


你為什麼不該在你的 iOS 軟體專案中使用 Storyboard
https://zonble.net/archives/2014_01/1540.php

=> 樓主的看法是不該使用 Storyboard,但是在討論區中也有讀者提出完全相反的看法
因為一個 App 應該要做好減法管理,避免過度膨脹,
在一個頁面有限的 App 中,使用 multi-storyboard,關鍵在於怎麼切分。


不使用 Storyboard 的好處
https://medium.com/@Chien_Cheng/813053e3692d
https://blog.bobthedeveloper.io/why-i-dont-use-storyboard-fe14a1a99f58


純 Swift 專案:Xib | StoryBoard 多人協作技巧
https://juejin.im/post/5c468f676fb9a049ed313183

如何將單個 VC (View Controller) 中的功能分解到幾個子 VC 中,並實現各個 VC 的交互,從而讓 VC 的體積不再無限增長:
https://www.jianshu.com/p/091c08c51f55


論壇的討論
https://kaif.io/z/programming/debates/b10ryBtyUL


捨棄 Storyboard 使用純程式碼 的方式撰寫 ViewController
https://ithelp.ithome.com.tw/articles/10160712


View Controller 流(純程式碼版)




在 2019 WWDC 大會上,Apple 發表了一令人振奮的消息,也就是 SwiftUI 的降臨,而 SwiftUI 和 UIKit 其實是對等的,都是用來處理 UI 的 framework,,網路上的學習文章也愈來愈多且豐富。不過呢,Apple 仍持續在調整 SwiftUI 的語法,網路上比較早期的部份教學文章或解法甚至都已經無法使用了。

UIKit 是從 Xcode1 就一直存在的 Framework;而 SwiftUI 則是 2019/6 WWDC 所發表的全新用來繪製 UI 的 Framework。因此,SwiftUI 必須搭配 iOS13+ 和 MacOS10.15+。

UIKit 底層仍為 Objecitve-C;而 SwiftUI 則是完完全全用 Swift 打造的 Framework。

使用 SwiftUI 所開發的專案,可以同時支援 iOS、macOS、watchOS、tvOS 等系統。

開發 iOS 的兩個派系,純 Code 、非純 Code 派,此兩派經常討論程式碼的一致性、直覺性,純 Code 總拿一致性抨擊對方,而另一方則是拿直覺性來回應之。不過,當 SwiftUI 降臨後的那一刻起,或許可以在這兩個派別間稍稍緩頰了。





Refactor to Storyboard: 使用多個 Storyboard 來開發專案

https://medium.com/%E5%BD%BC%E5%BE%97%E6%BD%98%E7%9A%84-swift-ios-app-%E9%96%8B%E7%99%BC%E5%95%8F%E9%A1%8C%E8%A7%A3%E7%AD%94%E9%9B%86/e0d513fb5bcb

https://ithelp.ithome.com.tw/articles/10206572

在我們開發過程中,我們會經常在 Storyboard 上拖拉許多 ViewController,而這些 ViewController 其中又包含我們許多 View 的元件,所以在我們專案開發到一定程度的時候,
你會發現這樣做在 Storyboard 上會有許多缺點:

讀取速度隨著開發時間,越來越慢;
畫面間的 Segue 連線變得複雜;
很難找到其中一個 ViewController 的畫面;
當多人合作開發 App 時,如果大家都編輯同一個 storyboard,git 在合併時沒那麼聰明,時常會發生衝突...

不過現在這些問題都不再是問題了,因為 Xcode 可以讓我們在不同的 storyboard 設計畫面,然後再將這些畫面串接起來:

利用 Refactor to Storyboard 將多個 controller 拆到另一個 storyboard。

APP 上架前需要思考的四個問題

https://tw.alphacamp.co/blog/2015-07-29-app-competitive-analysis

短短數年光陰,因智慧型手機的普及和 4G 頻寬的擴大,讓 APP 的數量如雨後春筍般的蓬勃發展,也因此在這個紅海中,若我們想要在此先搶得一席之地,應該做怎樣的競品評估和分析呢?
扣除 ASO(App Store optimization)優化外,是否可以從某些細節中了解箇中差異?
以下是我操盤 Apps 的經驗分享給大家,希望對想進軍 APP 市場中的您和團隊有所幫助。

在操作及輔導那麼多 App 團隊當中,通常我會區分三種身份(跨國企業、公司行號、獨立開發工作室),本文將針對沒有資源且以技術為基底的獨立開發工作室用最白話的方式當做範例做為解說。

一般來說獨立開發工作室,和其他兩種身分最大的差異在於,當一款 App 開發完成後,扣除熱情、夢想和時間外,其實最常碰到下列幾個問題。

我的消費市場在哪邊?
我的競品是誰?平台(Google Play & IOS)中類別的影響是?
沒有行銷預算怎推廣?
第三方平台對我的幫助是?



一、我的消費市場在哪邊?

一般來說,獨立開發工作室通常會碰到這個問題,好不容易找齊了團隊,產品也順利開發完成,但產品推出後卻發現消費市場和自己想的有點出入,而造成產品和推展停滯。

其實別想太多,在 App 的市場當中若只想做好台灣市場,我其實是不太建議。

在行動的領域,其實應該勇於將產品推向世界,在台灣的營銷則可以當作市場磨練水溫,要知道當你的 App 上架到(Google Play & IOS),除非你自訂國家/區域發佈,否則就該把自家的產品拓展到全世界,目前耳熟能詳的 App 當中多數都是跨國型的 App,行動的市場本無疆域性質,它不太像網際網路,當你在台灣搜尋 “電子商務” 這組關鍵字,出現的將會是台灣相關的網站,但在(Google Play & IOS)當中,可能會出現的是北美或者其他國家/地區的 APP 名稱。這也是為啥當你打你想要的關鍵字,卻可能出現非繁體字的 APP 的原因。

二、我的競品是誰?平台(Google Play & IOS)中類別的影響是?

通常這是我上課常常被問的問題,若說甚麼是應用程式類型 App 的競品,我都會說是遊戲產業,若是遊戲產業我則會說那些有陸資色彩或跨國企業在台公司。以上這些非玩笑話,只因為一款 App 上市在這個紅海中的紅海最先決的條件就是被找到,沒有被找到的 App 就沒有其價值,所以這些行銷投放預算較大的 App ,因為知名度足夠,才能夠有後續的營運模式發展。

另外台灣的市場非常適合當作產品水溫測試,畢竟要在一個地區能有那麼多智慧型裝置族群,又有那麼多不同屬性商圈環境的國家/地區著實不多,這也是為何台灣充斥那麼多非本土型 App 的原因。那獨立開發工作室沒有預算就不能在這個市場上競爭了嗎?錯! 獨立開發工作室有的是速度和靈活性,根據最新一期資策會數據台灣智慧型裝置族群高達 77.3%(約1,604萬人),這意味者你所開發的 App 不是沒人使用而是沒人知道它的存在,所以在此建議當一款產品上架後,設定你認為 App 產業你覺得最好相同類型 App 進行參考(文案、關鍵字)。

以燦坤福利品 App 為例,因市面上 3C 知名度較高的為 PCHOME ,而他的 APP 行動名稱為 24H 行動購物,所以我在操作上藉由 24H 購物的高曝光,讓使用者搜索增加驅動力下載,我在前面文字上說過 App 要能被找尋到才有其價值,因會搜索 24H 購物的使用者,推測是 3C 或電商使用者,故文案(ASO)設定 24H 購物,可以找尋到更正確的用戶,當然燦坤福利品的留存率也要維持在 30%,借力使力才可以讓 App 存活的更好。

另一個可以參考競品評估則是在平台(Google Play & IOS)中類別,為何這樣說呢?以鬧鐘這個關鍵字為例,當好不容易製作了一款鬧鐘,我們所想的可能是放在生活類別,但詳細觀察當你將鬧鐘放在生活品味這個類別後,將發現什麼房屋出租類型 App、Qrcode 二維碼類型 App 或行動類型 App 都在此類別,而這些都是你所謂的競爭對手,在已知的行銷法則當中,我們會把 SWOT 當作競品規劃中一個選項,但法則中不太會告知行動行銷碰觸到的原來不是近似的競爭品項,而是只要放在同個類別,能佔據消費者智慧型手機時間的 App 都將是你的競品,這也是很多行銷高手在行動行銷領域當中可能會滑鐵盧的原因,因為行動行銷的操作要更加整合、交叉和虛實三者並行。

三、沒有行銷預算怎推廣?

剛剛前面兩段寫了消費市場和競品,現在要寫非常實際的內容,若沒有錢我該怎樣行銷推廣呢?

其實在寫之前想請大家思考一下,在記憶當中的 App 產品當中,扣除遊戲業者有多少是花大筆預算而被下載的?而有多少你不知道的 App 卻被下載超過百萬次以上的?我想身為行銷操盤者的我會說,行銷就是要投放預算,且這預算可能隨者產品的上線可能花費會越來越多,因為產品收益回饋也越來越大,這是一種成長。但獨立開發工作室的 App 可能沒有預算那怎麼推廣呢?以下的口訣分享給大家,善用社群、靈活調配、借力使力、勇於國際和捨與得別害怕。

前陣子看了一篇文章,標題是寫社群是窮人家的原子彈,這話其實很貼切,但前提要提醒大家,真實要玩社群是非常花精力和金錢的,會這樣說社群是窮人家的原子彈是因為人與人的互動是具備散播性,而當社群切中議題,後續發酵將超乎預期,可以從最早的「開心農場、憤怒鳥和一次次社會議題」可以看到這個道理,所以當獨立開發工作室的產品要上線,先問自己所有的論壇、免費媒體發佈工具是否都準備齊了!

靈活調配:獨立開發工作室和公司行號有個最大的差異就是,能夠生存就是一切,所謂的規範都是產品成功後才配提出,所以當產品上線首要就是當市場回饋的所有客訴及問題,都將要在最快的時間修正完成,沒有所謂的1-10的排程規劃,只要市場喜歡哪一個,就該拋開一切原則,迎接市場才是。

借力使力:借有行銷資源的力,借成功範例的力,借別人有自己沒有資源的力,不要什麼都一條龍的模式操盤,這將會累死自己。

勇於國際:想要有大下載量,想要被創投發現,想要有高廣告收入,大膽的將產品設計成英文語系版本吧! 若有一點小預算,就進行海外投放,效益應該將會超乎侷限的國家/地區的。

捨與得別害怕:其實這塊很兩難,但在行動領域當中往往我們可能開發多時的產品因為市場的不領情而膠著不前,但有可能自己認為的小作卻可能在市場大放光明,如:2048、知識王、aa 或連續劇型 app。還是老話一句,當你的產品在這個紅海中的紅海被找尋到後,下一步將要思考這 App 對於消費者的日常生活而言,到底將有怎樣的幫助,並不是產品優化或者產品格局夠大市場就會接受的。要知道人類演化也好歹幾百萬年,並不要因為智慧型裝置的普及消費者的使用習性和腦袋就升等阿,只能說開始不排斥和接受而已,所以該捨該得就放寬心吧。

四、第三方平台對我的幫助是?

其實我們常常為犯了一個錯誤,想要占有所謂的 80% 客戶反而失去了那 20% 創造產值的消費者,而這 20% 的消費者有可能間接的影響 80% 的客戶群,在所謂的 App 操作當中,我會建議獨立開發工作者,扣除以上所說的操作外,大量的前往第三方平台試試看自身產品的水平吧,要知道許多第三方平台會使用者有可能是對於 App 高度使用的狂熱份子阿! 還有第三方平台其實也會有所謂的經營行銷團隊,若你的 App 是一款不錯的 App 而本身卻也沒有多餘的行銷預算,為何不先搭者第三方平台的行銷曝光呢? 對我來說第三方平台是一個進可攻、退可守的工具,我知道 Google Play & IOS 使用者很多,但也別忘了當你認知的也可能也是大家認知的,有時候遠路未必不是近路,分享給大家。

結論:

最後我們再複習上架前你需要做的 APP 競品評估和分析幾個重點

‍

行動行銷強調的重點有三個:整合、交叉與虛實

所謂的競爭對手分兩塊:類別和其他主要佔據消費者時間的 App

社群和第三方平台可能是獨立開發者進入 App 市場的原子彈

獨立開發工作室和企業的最大的優勢:速度和靈活性

上架到 Google Play

http://kirafugames.blogspot.com/2013/12/0026-amazon-kindle-android.html


上架到 Amazon App Store

http://kirafugames.blogspot.com/2013/12/0027-amazon-kindle.html#more

https://www.youtube.com/results?search_query=amazon+app+store+publish

商標

https://www.uspto.gov/

https://twtmsearch.tipo.gov.tw/OS0/OS0102.jsp

https://www.ippass.com.tw/trademark/?gclid=EAIaIQobChMI6pWdgNaQ6gIVRXZgCh0oNwkfEAAYAyAAEgJr5vD_BwE

https://www.proguidescreen.com/?p=7745

https://ezstartup.cc/apply-trademark-fee.html


容器化的微服務開發

https://columns.chicken-house.net/2017/05/28/aspnet-msa-labs1/


  1. 微服務架構(概念說明)
  2. 實做基礎技術: API & SDK Design
  3. 架構師觀點 - 轉移到微服務架構的經驗分享
    • Part #1 改變架構的動機; 2017/05/09
    • Part #2 實際改變的架構案例; 2017/05/20
    • Part #3 實際部署的考量: 微服務基礎建設; 2017/07/11
  4. 基礎建設 - 建立微服務的執行環境
    • Part #1 微服務基礎建設 - Service Discovery; 2017/12/31
    • Part #2 微服務基礎建設 - 服務負載的控制; 2018/06/10
    • Part #3 微服務基礎建設 - 排隊機制設計; 2018/12/12
    • Part #4 可靠的微服務通訊 - Message Queue Based RPC; 2019/01/01
    • Part #5 非同步任務的處理機制 - Process Pool; 2020/02/15
    • (計畫) 微服務基礎建設 - Service Mesh;
    • (計畫) 微服務基礎建設 - Circuit Breaker;
    • (計畫) 微服務基礎建設 - Log Tracking & Auth;
    • (計畫) 微服務基礎建設 - 版控, CI/CD, 容器化部署;
  5. 案例實作 - IP 查詢服務的開發與設計
  6. 建構微服務開發團隊


架構師觀點: 你需要什麼樣的 CI / CD ?

https://columns.chicken-house.net/2017/08/05/what-cicd-do-you-need/



文章目录



[參考資料] 服務條款、用戶條款 & 隱私政策

https://medium.com/%E5%BD%BC%E5%BE%97%E6%BD%98%E7%9A%84-swift-ios-app-%E9%96%8B%E7%99%BC%E5%95%8F%E9%A1%8C%E8%A7%A3%E7%AD%94%E9%9B%86/7bc4746cf75d

從 2018/10/3 開始,Apple 要求我們上架的 App 都要包含隱私權政策,包含新上架的 App 和 App 的更新。

提醒您,自2018年10月3日起,App Store Connect 要求所有新 App 和 App 更新項目都必須提供隱私權政策,才能提交以發佈至 App Store。《App Store 審查規範》已在 6 月更新,新規定要求所有新 App 和 App 更新項目皆須提供隱私權政策,作為 App 審查流程的一部分。此外,只有在提交 App 的新版本時,才能編輯 App 隱私權政策的連結或文字。

如果寫不出隱私權政策,可以考慮找 privacypolicies.com 幫忙,它能幫我們自動生成隱私權政策。
https://privacypolicies.com

https://kknews.cc/zh-tw/news/k2begob.html

用戶協議通常分為隱私權政策和服務條款,
文中以各 關鍵詞 切入分析,

也提到了一個專門收集和評價各大廠商用戶協議的網站:ToSDR,這個網站主要收集了國外服務的用戶協議,並且允許用戶對這些協議開展評價,從 A-E 的評分等級去判斷協議的好壞。比如點開查看 GitHub 用戶條款的 評價,值得點讚的條款有:用戶無需向 GitHub 授予任何版權許可,被踩的條款則有:GitHub 保留隨時更新和更改服務條款的權利,甚至有可能是不通知用戶的前提下。

http://sbsys.starwing.com.tw/policy/209

上架到 Apple App Store

Apple 官方的 App Store Review Guidelines (審查指南)
https://developer.apple.com/app-store/review/guidelines/

註冊成為 Apple Developer + 付費
https://ephrain.net/ios-%E8%A8%BB%E5%86%8A%E6%88%90%E7%82%BA-apple-developer-%E4%BA%86%EF%BC%81/

Apple 開發者帳號續約
http://kirafugames.blogspot.com/2014/03/0029-ios.html

App ID 建立流程
https://medium.com/@apppeterpan/93b283662e0c

App 上架和更新流程

iOS 開發:APP 如何串接 Web API

關鍵詞:『NSURLSession』、『AFNetworking』、『Objective-C 非同步下載』、『Objective-C 多執行緒』




苹果官方提供的原生网络请求类包括:NSURLConnection 和 NSURLSession。
其中,NSURLConnection 从 iOS9 开始不被官方推荐使用了,而推荐使用从 iOS7 开始出现的NSURLSession。

現在大家都在用 AFNetWorking 作為首選網路請求框架,之前的 ASIHTTPRequest 是基於NSURLConnection 類實現的,早期的 AFNetWorking 也是基於 NSURLConnection 實現,後來iOS9 之後已經放棄了 NSURLConnection,開始使用 iOS 7 之後推出的 NSURLSession,本著追根溯源的原則,首先學習一下 NSURLSession 的實現網路請求,然後再去學習 AFNetWorking。

下面兩個超連結,一篇是簡體字,另一篇是繁體字,
相同的程式碼,但是兩篇的程式碼各有漏掉一些東西... 交互參照吧




GCD 多執行緒的說明與應用:





摘要如下,其餘詳閱參考資料:

GCD (Grand Central Dispatch) 是 Apple 將複雜且不易使用的 Thread 操作方式簡化過的 API,雖然使用起來比起傳統的 thread 會有些限制,但整體來說使用起來輕鬆許多也較安全。 總而言之,GCD為多執行緒的運用,其作用在於可將單一任務拆成多個小任務同時執行,亦或者同時執行多個任務,目的在於縮短執行任務的時間,而最重要的是,要確保不要佔用主執行緒而影響到使用者的體驗。



POST 和 GET:

相同点:都能给服务器传送数据

不同点:

1.给服务器的传输数据的方式
GET:通过网址字符串
POST:通过data

2.传输的数据大小:
GET:网址字符串最多255个
POST:使用 NSData,容量超过 1G

3.安全性
GET:所有传输的数据都显示在网址栏中,相当于明文,直接可见
POST:数据被转成 NSDate (二进制数据)



定時清理 Xcode 運行所產生的資料

https://www.twblogs.net/a/5d40141abd9eee51fbf969c3


移除 Xcode 運行安裝 APP 產生的緩存文件(DerivedData)

只要運行 Xcode 就一定會生成,而且會隨着運行程序的增多,佔用空間會越來越大。清理後再重新運行程序一開始可能會稍微慢一點。

路徑:
~/Library/Developer/Xcode/DerivedData

釋放空間:0~xx GB


移除 APP 打包的 ipa 歷史版本 (Archives)

刪除後不可恢復,文件夾是按照日期排列的,所以如果你不想全部刪除,就只保留最新的幾個版本就好了。

路徑:
~/Library/Developer/Xcode/Archives

釋放空間:0~xx GB


移除 APP 打包的 app icon 歷史版本 (Archives)

刪除後不可恢復,文件夾是 Bundle Idenifier 排列的,然後再按照 archive 的版本號排列的,如果你看每個版本內的內容,其實就是你的 app icon。

路徑:
~/Library/Developer/Xcode/Products/

釋放空間:30M


移除模擬器的緩存數據 (Devices)

模擬器的相關數據。每個版本的模擬器佔用的內存空間大約爲 10MB 左右。每個文件夾裏包含的就是一個特定系統版本的設備的數據。每個文件夾對應哪個設備可以在其下 device.plist 中查看。刪除之後,如果立即運行程序會報錯,先關閉 Xcode,再重新打開程序,運行即可。運行該路徑下會立即生成模擬器對應版本的文件。

路徑:
~/Library/Developer/CoreSimulator/Devices/

釋放空間 ≈ 12GB


移除對舊設備的支持 (iOS DeviceSupport)

一般是佔用內存空間最大的文件夾,即使全部刪,再連接設備調試時,會重新自動生成。一般 iOS 只需要向下兼容幾個版本就可以了。

路徑:
~/Library/Developer/Xcode/iOS DeviceSupport

釋放空間 ≈ 3GB/版本


移除 Xcode 中無效的插件 (Plug-ins)

當過去安裝了一些 Xcode 的插件,比如 HighlightSelectedString、VVDocumenter-Xcode 等非常方便好用的第三方插件,在 Xcode 升級到 version 8.0 以後,就失效了,Xcode 在內部已經集成了類似的方法,所以之前安裝的也都沒有用了,但還在原來的位置佔用着內存空間,建議刪除。

路徑:
~/Library/Application Support/Developer/Shared/Xcode/Plug-ins

如果你沒有安裝過插件,那麼可能不存在此路徑。


移除舊版本的模擬器支持

如果你不小心安裝了很多個版本的模擬器,那麼你可以刪除一些舊版本的。但是當你需要舊版本的模擬器時,就需要重新下載了。建議留下 1~2 個版本就好了,其餘的都刪了吧。

路徑:
~/Library/Developer/CoreSimulator/Profiles/Runtimes/

釋放空間 ≈ 2.5GB/版本


移除 playground 的項目緩存 (XCPGDevices)

刪除後可重新生成,可以全部刪除。再次運行程序會緩存。

路徑:
~/Library/Developer/XCPGDevices/

使用 Xcode 幾年沒清理後,此文件夾也就佔用約 300MB 內存空間,可依據個人喜好操作。


移除舊的文檔 (Docsets)

刪除後不可恢復,該目錄下存儲的爲開發文檔,一般有三個文件com.apple.adc.documentation.iOS.docset (1.68GB)、
com.apple.adc.documentation.OSX.docset (2.62GB)、
com.apple.adc.documentation.Xcode.docset(256.4M),
如果你只做 iOS 開發,其實你可以把 OSX.docset 刪除掉的,因爲它佔用了 2.62GB 的內存。

路徑:
~/Library/Developer/Shared/Documentation/DocSets

整體所佔空間約 4.56 GB


移除模擬器中的 SDK 版本 (iPhoneSimulator.sdk)

不可恢復,操作請慎重。
我個人的此路徑下只有最新版本的 sdk,除非當你有多個版本的 sdk 再斟酌刪除。

路徑:
~/application/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/

佔用空間約 4GB,刪除時請慎重





 如何徹底刪除 Xcode 相關的東西

sudo /Developer/Library/uninstall-devtools –mode=all

然後再刪除Developer文件夾

sudo rm -rf /Applications/Xcode.app

sudo rm -rf /Library/Preferences/com.apple.dt.Xcode.plist

然後刪除自己目錄下的:

rm -rf ~/Library/Preferences/com.apple.dt.Xcode.plist

rm -rf ~/Library/Caches/com.apple.dt.Xcode

rm -rf ~/Library/Application\ Support/Xcode

以及和開發者相關的:

rm -rf ~/Library/Developer/Xcode

rm -rf ~/Library/Developer/CoreSimulator

rm -rf ~/Library/Developer/XCPGDevices


每個 xocde 都會自帶一個模擬器,且都是隨安裝包一起打包的,比如 xcode8.0 的就自帶 iOS10 的模擬器,這個是沒辦法刪除。

但是對於想要用iOS10以前的模擬器,可以通過這裏進行下載:



最終下載的模擬器被放置在這個位置:
/Library/Developer/CoreSimulator/Profiles/Runtimes/


如果版本太舊可以通過以上位置進行刪除即可。


macOS: 如何完全移除 Xcode

移除時需要在終端機使用 sudo 指令,所以管理者帳號需要有設定密碼的。


打開「終端機」:
在 Finder  中,打開「/應用程式/工具程式」檔案夾,然後按兩下「終端機」。


因為東西是在 / 下面,並不是在自己的資料目錄中,所以最前面要加上 sudo 。

sudo rm -rf /Applications/Xcode.app
sudo rm -rf /Library/Preferences/com.apple.dt.Xcode.plist
接著就都是刪除自己目錄下有關 Xcode 的東西
rm -rf ~/Library/Preferences/com.apple.dt.Xcode.plist
rm -rf ~/Library/Caches/com.apple.dt.Xcode
rm -rf ~/Library/Application\ Support/Xcode
和開發者相關工具
rm -rf ~/Library/Developer/Xcode
rm -rf ~/Library/Developer/CoreSimulator
rm -rf ~/Library/Developer/XCPGDevices
這樣子就可以完整乾淨的解除安裝了



結束終端機:
在 Mac 上的「終端機」App  中,選擇「終端機」>「結束終端機」。




iOS 開發:點選 segment control 可切換顯示不同的頁面


我們時常在 iOS App 上見到如下圖 App Store 的畫面設計,點選 segment control 可切換顯示不同的頁面:
有很多方法可實現這樣的畫面


利用多個 container view 切換頁面:
https://medium.com/%E5%BD%BC%E5%BE%97%E6%BD%98%E7%9A%84-swift-ios-app-%E9%96%8B%E7%99%BC%E5%95%8F%E9%A1%8C%E8%A7%A3%E7%AD%94%E9%9B%86/6d00fe848572


iOS App開發 – UIViewController 的七種切換頁面技巧:
http://www.iotec.tw/?p=54

iOS 教學 – 日期時間處理 ( NSDate 等物件使用)

http://www.iotec.tw/?paged=3&cat=12

取得日期聽來容易,不過實際上使用時很快就發現:哇!怎麼沒有想像的簡單?
實際上依個人經驗,無論什麼程式語言,甚至 MySQL ,日期與時間的處理從來都沒有簡單過,雖然不致於到十分困難,不過若是小看它隨便寫的話,最終一定會遇到問題。
… 話說回來,程式語言的發展也歷經半世紀以上的時光,為何到現在還無法統一日期時間的處理方式呢?新的語言雖然帶來了新的方法,必然有其優點,不過無法完全覆蓋舊的作法,結果也是導致又多了一個時間日期的處理方式罷了。

 Github 目錄

本文中的程式碼皆可在範例 project 中找到:
https://github.com/orangedream/DateExampleiOS

NSDate

NSDate 物件單純只是表示一個特定的時間點。進一步的說,NSDate裡記錄的是自 2001/1/1 以來的時間,時區是 GMT+0。
要取得目前的時間十分簡單:
     NSDate *currentDate=[NSDate date];
要印出來也還算簡單…:
    NSLog(@"currentDate=%@",currentDate);
執行結果(模擬器):
     currentDate=2016-02-07 04:18:50 +0000
執行結果(iPhone):
     currentDate=2016-02-08 00:17:55 +0000
!?這時間看來不太對,仔細看可以看到最後面有個 +0000,這就是表示這個是以 GMT+0 的時區來列印的。簡單來說,這是美國時間,因為台灣時間是 GMT+8。所以說無法直接使用,要先作轉換。

印出當地時間 Local Time

直接用 NSLog() 列印 NSDate 的結果,會得到美國時間,若要取得當地時間,則要借助 NSDateFormatter 的幫忙作以下的轉換:
    NSDate *currentDate=[NSDate date];
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateStyle:NSDateFormatterShortStyle];
    [formatter setTimeStyle:NSDateFormatterMediumStyle];
    NSString *currentDateString = [formatter stringFromDate:currentDate];
    NSLog(@"currentDate=%@", currentDateString);
執行結果(模擬器):
     currentDate=2/8/16, 8:24:35 AM
執行結果(iPhone):
    currentDate=2016/2/8 上午8:24:06
!?為何模擬器與 iPhone 的執行結果會不同?這跟地區( NSDateFormatter.locale )屬性初始值以及時區( NSDateFormatter.timeZone )屬性初始值有關。
用這段 code 來找出 NSDateFormatter.locale 與 NSDateFormatter.timeZone 為何:
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
NSLog(@"NSDateFormatter.locale=%@",formatter.locale.localeIdentifier);
NSLog(@"NSDateFormatter.timeZone=%@",formatter.timeZone);
執行結果(模擬器):
 NSDateFormatter.locale=en_US
 NSDateFormatter.timeZone=Asia/Taipei (GMT+8) offset 28800
執行結果(iPhone):
NSDateFormatter.locale=zh_TW
NSDateFormatter.timeZone=Asia/Taipei (GMT+8) offset 28800

印出台灣時間 Local Time

所以,若不管手機的地點為何,要列印出台灣時間的話,就可以這麼作:
    NSDate *currentDate=[NSDate date];
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateStyle:NSDateFormatterShortStyle];
    [formatter setTimeStyle:NSDateFormatterMediumStyle];
    [formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"zh_Hant_TW"]];
    [formatter setTimeZone:[NSTimeZone timeZoneWithName:@"Asia/Taipei"]];
    NSString *currentDateString = [formatter stringFromDate:currentDate];
    NSLog(@"%@", currentDateString);
執行結果(模擬器):
 Taiwan time=2016/2/8 上午8:52:14
執行結果(iPhone):
 Taiwan time=2016/2/8 上午8:53:12

註:時區列表可以用 [NSTimeZone knownTimeZoneNames] 來取得,而地區列表可以用 [NSLocale availableLocaleIdentifiers] 來得到。

格式化日期輸出

前面的日期輸出使用了內建的格式:NSDateFormatterShortStyle與NSDateFormatterMediumStyle,如果我們要輸出像「2 月 8 日 (星期一)」這樣的格式,可以這麼作:
// setup date format
 NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
 [formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"zh_Hant_TW"]];
 [formatter setTimeZone:[NSTimeZone timeZoneWithName:@"Asia/Taipei"]];
 [formatter setDateFormat:@"M 月 d 日 (eeee)"];
 // Date to string
 NSDate *now = [NSDate date];
 NSString *currentDateString = [formatter stringFromDate:now];
 NSLog(@"currentDate=%@", currentDateString);

台灣常見的幾個輸出格式範例:
2016年 2月 9日
 YYYY年 M月 d日
2016年 2月 9日 23:09:00
 YYYY年 M月 d日 HH:mm:ss
2016/2/9
YYYY/M/d
2016/02/09
YYYY/MM/dd
2/9 21:00
 M/d HH:mm
2/9 9:00 下午
M/d h:mm a
註:格式詳列:Date Format Patterns

民國日期

如果我們想列出民國的年曆,像「民國105年 2月 9日」,就會需要轉換日曆(Calendar)物件才能輸出民國年份:
 // setup date format
 NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
 [formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"zh_Hant_TW"]];
 [formatter setTimeZone:[NSTimeZone timeZoneWithName:@"Asia/Taipei"]];
 [formatter setDateFormat:@"民國yyy年 M月 d日 (eeee)"];
 [formatter setCalendar:[[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierRepublicOfChina]];
 // Date to string
 NSDate *now = [NSDate date];
 NSString *currentDateString = [formatter stringFromDate:now];
 NSLog(@"currentDate=%@", currentDateString);

農曆

比照前例,我們可以將日期轉換為農曆
 // setup date format
 NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
 [formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"zh_Hant_TW"]];
 [formatter setTimeZone:[NSTimeZone timeZoneWithName:@"Asia/Taipei"]];
 [formatter setDateFormat:@"UUU年 M月 d日"];
 [formatter setCalendar:[[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierChinese]];
 // Date to string
 NSDate *now = [NSDate date];
 NSString *currentDateString = [formatter stringFromDate:now];
 NSLog(@"currentDate=%@", currentDateString);
輸出(模擬器):
丙申年 1月 2日

由字串轉換為 NSDate

要將串轉換為 NSDate 需要再度借助 NSDateFormatter 的幫助:
    NSString *dateString = @"2016/2/9";
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    // Make sure it's matched format!
    [dateFormatter setDateFormat:@"yyyy/M/d"];
    NSDate *date = [[NSDate alloc] init];
    date = [dateFormatter dateFromString:dateString];
    NSLog(@"date=%@", date);
輸出(模擬器):
date=2016-02-08 16:00:00 +0000
為何輸出不是 2016-02-09 00:00:00 呢?請參考前面,因為 Timezone 的不同,在 GMT+8 的地方的「2016-02-09 00:00:00」會等於 GMT+0的[2016-02-08 16:00:00]

2013/1/30~2016/2/9之間有幾天

要計算二個 NSDate 之間有幾天,就要借助NSDateComponents 與 NSCalendar 來計算。(注意:使用NSDate.timeIntervalSinceDate/86400 秒的作法在大多數情況下可行,然而遇到日光節約時間就會出錯!)
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"yyyy/M/d"];
    NSDate *date1 = [dateFormatter dateFromString:@"2013/1/30"];
    NSDate *date2 = [dateFormatter dateFromString:@"2016/2/9"];
    NSDateComponents *components;
    NSInteger numberOfDays;
    components = [[NSCalendar currentCalendar] components: NSCalendarUnitDay fromDate: date1 toDate: date2 options: 0];
    numberOfDays = [components day];
    NSLog(@"Total days=%ld", numberOfDays);
輸出:
 Total days=1105

“07:09” ~ “23:12” 之間有幾分鐘

同樣的,我們也可以延伸上例來計算二個時間的間隔:
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"HH:mm"];
    NSDate *date1 = [dateFormatter dateFromString:@"07:09"];
    NSDate *date2 = [dateFormatter dateFromString:@"23:12"];
    NSDateComponents *components;
    NSInteger numberOfMinutes;
    components = [[NSCalendar currentCalendar] components: NSCalendarUnitMinute fromDate: date1 toDate: date2 options: 0];
    numberOfMinutes = [components minute];
    NSLog(@"Total minutes=%ld", numberOfMinutes);

取得今天一開始的時間

借由 NSCalendar.startOfDayForDate 可以取得一天一開始的時間
    NSDate *now=[NSDate date];
    NSCalendar *cal=[NSCalendar currentCalendar];
    NSDate *beginOfToday= [cal startOfDayForDate:now];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"yyyy/M/d HH:mm:ss"];
    NSLog(@"Begin of today=%@",[dateFormatter stringFromDate:beginOfToday]);

取得一個月一開始的時間

若要取得本月一開始的時間,就要借由 NSDateComponents 來處理,自現在時間只取 年/月/日 三個屬性,然後把日期設為1, 即可達成任務。
    NSDate *now=[NSDate date];
    NSCalendar *cal=[NSCalendar currentCalendar];
    NSDateComponents *comp = [cal components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:now];
    [comp setDay:1];
    NSDate *firstDayOfMonthDate = [cal dateFromComponents:comp];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"yyyy/M/d HH:mm:ss"];
    NSLog(@"Begin of this month=%@",[dateFormatter stringFromDate:firstDayOfMonthDate]);

換算自某個日期之後30天後的日期

同樣,要對 NSDate 作計算,NSDateComponents是十分重要的工具,透過NSCalendar.dateByAddingComponents 即可輕易完成任務。
    NSDate *now=[NSDate date];
    NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
    [dateComponents setDay:30];
    NSDate *thirtyDaysLatter = [[NSCalendar currentCalendar] dateByAddingComponents:dateComponents toDate:now options:0];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"yyyy/M/d HH:mm:ss"];
    NSLog(@"Begin of this month=%@",[dateFormatter stringFromDate:thirtyDaysLatter]);

要注意的是,這裡並沒有重設幾時幾分,所以會變成30天前的同一時間。使用上依需求可能要作些調整。參考前面的例子應該可以組合出你所要的結果。

與 Unix(PHP) timestamp 互換

由於 iOS App 常與後台 API 作搭配,而無論是自行建立的後台伺服器或是使用現成的雲端平台,總免不了要作 timestamp 的處理。而 PHP(Unix)或是 MySQL 的 timestamp
不意外的,與 iOS 的 timestamp 並不相同。幸好,轉換的方式並不困難,因為一方是 1970 開始的 timestamp,而另一方則為 2001 年開始的 timestamp,所以只要這樣轉換即可:
    NSDate *now=[NSDate date];
    NSTimeInterval timeStamp = [now timeIntervalSince1970];
    // NSTimeInterval is defined as double
    NSNumber *timeStampObj = [NSNumber numberWithDouble: timeStamp];
    NSLog(@"Unix timestamp=%ld", [timeStampObj longValue]);

其它有關 NSDate 處理教學

有關 NSDate 的處理線上文章不少,不過多半過於零碎,以下這篇我覺得寫得不錯,值得推薦:
Date Programming




iOS 教學: Universal Link (通用鏈接) & URL Scheme

http://www.iotec.tw/?cat=12

如果希望在 App A 按一個按鈕,立即切換至 App B,
或是用手機瀏覽網頁時,按一個連結,自動打開 App,
這個時候,需要使用的技術有二個,一個是 URL Scheme,另一個是 Universal Link。


這二種技術略有不同,說明如下:

iOS URL Scheme (Objective C)
[application openURL:[NSURL URLWithString:@"iotecapp1://page/12"] options:@{}
completionHandler:^(BOOL success) {}];
Universal Link (HTML)
<a href="https://www.iotec.tw/page/12">Open IOTEC App </a>

設定方式

iOS URL Scheme

Xcode > TARGETS > your_app > Info > URL Types

增加 URL Scheme 為 iotecapp1

Universal Link

Xcode > TARGETS >your_app > Capabilities > Associated Domains

增加 applinks:www.iotec.tw

www.iotec.tw 必須支援 HTTPS

編寫 apple-app-site-association (詳見官方文件)

文件類型需為 json且無副檔名

將 apple-app-site-association 放至 https://www.iotec.tw/ 底下
或是
https://www.iotec.tw/.well-known/ 底下

到 Apple Developer webpage 將 App 的 Associated Domain 功能打開


可否接收完整 URL(也就是說,可否傳遞參數?)

iOS URL Scheme &Universal Link

二者皆可。可以取得完整的 URL,把後面的參數部份取出即可。

例: iotecapp1://page/12 後面的 page 與 12 等等即可傳遞參數


觸發方法 (iOS Objective C, AppDelegate.m)

URL Scheme
- (BOOL)application:(UIApplication *) application  openURL:(NSURL *)url
      options: (NSDictionary <UIApplicationOpenURLOptionsKey, id>*)options;
Universal Link
-(BOOL) application:(UIApplication *)application
          continueUserActivity:(NSUserActivity *)userActivity
          restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler;

從網頁觸發時,如果沒有裝 App, 可否導向指定網頁?

URL Scheme

Partially YES
可以用 Timeout 方式處理,但不是 100% 成功

Universal Link
可以無縫接軌

可否從 APP 觸發?

URL Scheme
沒有問題,還可以判斷另一個 APP 是否有安裝

    [application openURL:URL options:@{} completionHandler:^(BOOL success) {
        if (success) {
            NSLog(@"Opened URL Scheme %@",scheme);
        }else{
            NSLog(@"Opened URL Scheme %@ FAILED",scheme);
        }
    }];
Universal Link
有些情況下可能有問題

用 UIWebview 或是用 SFSafariViewController 開啟 Universal Link 在官方文件上似乎是不建議的作法,
有文章提到 UIWebView 裡必需要在切換 domain 時才會觸發 Universal Link。實際上會發生什麼事還需要有用過的人交流一下。

安全性?

URL Scheme

因為任何 APP 都可以註冊自己處理 iotecapp1的 prefix 的 URL Scheme,這會有潛在性的安全問題。
Ex: 如果您的官網透過 URL Scheme 來開啟您的 APP,使用的 URL Scheme prefix 為
“yellow://…”, 而很不巧的剛好有個限制級 APP 也使用 yellow 作為 URL Scheme prefix,
那麼就會有機率開錯了 App…
雖然手機上為何會裝該 APP 使用者本身也要負一點責任…. 不過這邊的行為與設計原意就不一致了

Universal Link
相對安全很多

參考官方文件
Support Universal Links:
https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/UniversalLinks.html

Communicating with Other Apps Using Custom URLs:
https://developer.apple.com/documentation/uikit/core_app/allowing_apps_and_websites_to_link_to_your_content/communicating_with_other_apps_using_custom_urls


GitHub 基礎

https://www.ithome.com.tw/news/95283

使用 Git 總搞不清楚分支、合併背後邏輯?為什麼專案版本線圖總是亂七八糟?如果 Git 讓你頭痛,不妨從頭開始,好好釐清 Git 的基本觀念

對開發新手而言,想要善用 GitHub 平臺提供的 Git 版本控管服務,上手不難,但隨著開發專案開始衍生分支版本後,不少新手往往很容易發生越管版本越混亂的窘況。開設過多場 Git 訓練課程,人稱保哥的多奇數位創意技術總監黃保翕觀察,新手最大的迷思是「直接將集中式版本控制軟體的觀念套用在 Git 上。」他建議,新手要先了解分散式版本控制系統的運作原理,否則「即便有再好的工具,如果觀念不清楚也沒有用。」

比方說,主幹與分支合併時發生衝突,如果開發者不明就裡地硬下指令解決,就會造成版本控制的錯亂。黃保翕表示:「因為工具都是其次,觀念才是根本。」


在 Git 工作流程中,使用者先從儲存庫下載某一版本到工作目錄,檔案修改完後,把檔案快照新增到暫存區域,執行提交後檔案就永久存到儲存庫內。

第一步  了解集中式和分散式版本控管的差異

傳統常見的集中式版本控制系統如 CVS、Subversion 及 Perforce 等,採主從式架構,開發專案所有版本的檔案集中儲存在單一臺伺服器上。而在開發者本機端的儲存庫上,則只會儲存最新版本的歷史紀錄,開發者如果想要提交(commit)新版本、查詢各版本差異或修改歷史紀錄,都要透過網路連到伺服器才能進行。當專案隨時間變得越來越龐大、版本越來越多,每一個人的每一個動作都要連上伺服器,就會影響了集中式版本控制系統的運作效率。

分散式版本控制系統則不同,黃保翕表示:「Git 是分散式版本控制,每個人都有一份完整的本機儲存庫。」也就是說,除了在遠端儲存庫上如 GitHub 上,擁有專案各版本的完整程式碼之外,在每一個開發者本機端也還設計了一個本地端儲存庫(Repository),也儲存了所有變更過的檔案,以及專案各版本的歷史紀錄。遠端儲存庫並非是唯一一份,而是一個供多人同步專案資料用的共享版本。

透過本機端儲存庫上的這份完整專案,開發者不須透過網路便能提交新版本的程式碼到本機端儲存庫。等到需要將本機端儲存庫上資料同步到遠端儲存庫時,才需要使用網路,在 Git 上,這個指令就是推(Push)。所以,開發者進行版本控制的彈性變大,對網路的依賴也減低。

集中式版本控制系統的優點是,可以在遠端進行集中式的權限控管,限制使用者開啟某些目錄的權限。但在分散式的 Git 上要做權限控管就稍微麻煩。因為人人都能取得完整程式碼,要限制存取權限,只能將程式碼依可開放權限,分散在多個遠端儲存庫,限制開發者只能存取符合權限的特定儲存庫。

不過,集中式架構的缺點是,操作版本管理系統高度依賴網路,離線時能使用的版本控制功能有限,例如無法查詢每一個歷史版本,也只能跟本機端最新版本進行版本差異比較(diff)。而分散式版本控制系統因為在本機端存有一份完整的儲存庫,不會因為沒有網路連線而沒辦法進行查詢、版本差異比較等功能。

黃保翕解釋,版本控制的目的在記錄檔案在某一段時間的變更,方便開發者追蹤原始碼,了解系統軟體的歷史變化。如果開發者修正了 3 個程式臭蟲,就得有 3 個版本被寫入儲存庫中。使用集中式版本控制系統若沒辦法連到網路,就必須同時修畢 3 個臭蟲後才能提交,而無法追蹤這 3 次修改的變化處理。

儘管儲存了整套專案,為了減少佔用的磁碟空間,Git 處理資料的方式也與其他版本控制系統的作法不一樣,像 CVS 或 Subversion 會記錄檔案隨著時間變更的內容,而 Git 則只是為當時的專案資料建立快照(Snapshot),如果專案內沒有變更的檔案就不會多儲存一份來佔用磁碟空間,而只是增加了一筆這個檔案的對應連結,開發者開啟新版本存取這個檔案時,還是開啟先前的舊版檔案,而不是開啟內容相同的新副本。

第二步  熟悉 Git 工作流程

第一步先了解分散式版本控管的特性後,第二步要熟悉 Git 工作流程。當開發者要建立一個開發專案時,要為這個專案建立一個目錄,以及使用 git init 指令建立一個這個專案的儲存庫(Repository )。這個目錄可稱為是這個專案的工作目錄(Working Directory),開發專案所有的檔案都儲存在這個目錄下,而執行與專案相關的 Git 指令,也都是在工作目錄下執行。而下達建立儲存庫的指令後,則是會在這個工作目錄下建立一個 .git 資料目錄,來儲存所有版本變更需要的資訊。

另外,Git 還會在 .git 目錄下建立一個名稱為 index 的索引檔案,作為記錄專案所有檔案的處理狀態,例如是儲存在本機儲存庫中的已提交(committed)檔案,或是已變更但尚未提交到本機儲存庫的已修改(modified)檔案,或也有一種狀態是在已修改檔案中標記出要作為提交到下一版本用的已暫存(staged)檔案。這個索引檔是用來管理檔案修改以及是否要提交到儲存庫的記錄。

在一個常見的 Git 工作流程中,首先要建立開發專案(包括建立工作目錄和建立儲存庫),接著在專案目錄下新增或修改檔案等開發工作,工作完成告一階段,就會提交一個專案新版本到 Git 本地端儲存庫裡。因為提交動作會依據索引檔來進行,所以,開發者得先建立快照來更新索引資料,將需要提交的檔案標記為 staged 檔案。然後,才執行提交指令(commit),將 staged 檔案儲存到儲存庫中。若要使用 GitHub 來和其他人共享專案,開發者則需進一步使用 Push 指令,將本地端儲存庫的特定版本專案,推到遠端儲存庫上整併。下一次要展開開發工作時,則可先從遠端儲存庫將新版程式碼取回本地端儲存庫(此動作稱為 Pull),再從本地端儲存庫放入工作目錄中(此動作稱為 Checkout),就可繼續展開下一輪開發。

必懂開發分版觀念:Master、Branch 和 Merge

主幹(Master)與分支(Branch)是稱呼專案的主要版本和分支版本。在 GitHub 上,通常第一個建立的專案版本會稱為 Master 版本。但對 Git 而言,開發專案的版本並無主幹(Master)與分支(Branch)的分別,每個分支都是等價關係,而開發者自行調整版本命名來區分。一般習慣將穩定版本稱主幹,其餘的變動、開發中版本則都稱作分支。Branch 也是一個建立分支的 Git 指令,可將某個歷史版本複製一份,獨立成為另一個新的分支版本,而合併(Merge)指令剛好相反,則是把兩個不同的分支版本,合併到其中一個分支上。

使用 Git 版本控制時,通常在主幹上開發主要功能,並在主幹分出去的分支上開發副功能。一個開發專案在實際運作時,多半希望主幹的程式碼是穩定、乾淨的版本。為了避免影響專案穩定性,通常專案開發、更動會在分支上進行。當某分支的功能已經開發完整時,要把分支上的功能套回主幹上,此時就會執行合併,把分支的版本合併回主幹上。

把分支與主幹放到版本線圖上檢視,分支是從主幹分出去的另外一條路。想要增進版本控制的效率,開發人員必須理解目前自己開發的工作目錄是位於主幹還是分支,才不致把版本線圖弄得混亂。比方分支上的功能已經開發完整,但是卻把主幹合併到分支上,導致穩定版本上沒有分支上的新功能,分支上反而有了穩定版本的主功能。

黃保翕解釋,集中式版本控制系統通常沒有引入分支的概念,開發者多半在主幹上開發,但每一個版本都有不同目的、功能,如果只在一條龍的主幹上建立版本,團隊仍舊不易追蹤軟體開發的過程。

Git 則提供了一個視覺化的版本線圖(commit graph),來呈現出主幹與各分支連結形成的樹狀架構。不過隨著專案越來越大,版本線圖會變得越來越複雜。黃保翕提醒,當新手不清楚目前自己正在開發哪個分支版本時,有時會誤將主幹版本合併到分支版本,反而打亂了版本間的關係線。他建議,可以善用合併指令整併不必要的分支,將版本線圖整理清楚。

必懂專案分版手法:Clone 和 Fork

複製(Clone)指令是把專案在遠端儲存庫上的所有內容複製到本地,建立起本機儲存庫及工作目錄,而叉(Fork)則是把別人專案的遠端儲存庫內容複製一份到自己的遠端儲存庫,黃保翕生動的形容:「就像是在餐桌上用叉子把盤子上的一塊肉叉到自己的盤子上。」。

如果在開發者在 GitHub 上看到有興趣的專案,可以執行 Fork 指令,把別人專案的遠端儲存庫複製到自己的遠端儲存庫,再執行 Clone 指令,把自己遠端儲存庫的整個專案的所有內容(包括各版本)複製到本機端儲存庫。

必懂資料同步指令:Push、Pull、Pull Request

執行推(Push)指令,可以把自己目前本機端儲存庫的相關檔案,上傳到遠端儲存庫,而拉(Pull)指令則把遠端儲存庫的最新版本下載至自己的本機端,並將遠端分支合併到本地分支,不過,Pull 並不像 Clone 指令會下載完整專案各版本內容。

開發者可以執行 Pull 取得其他人開放授權的遠端儲存庫上的程式碼,也可以將自己修改的程式碼 Push 到可存取的第三者遠端儲存庫上。透過推(Push)跟拉(Pull)兩個指令,開發者就能互相分享原始碼。

而 Pull Request 則是更主動地要求第三方開發者納入自己開發的程式,將本地端儲存庫上的程式碼,整併到對方的儲存庫上。例如 A 成員可透過 Pull Request 要求 B 成員,將 A 成員修改後的程式碼併入 B 成員的開發專案中。

從 Hello-World 認識 GitHub 版本控管

「Hello-World」程式範例在電腦學習上有悠久的歷史,引領許多人走入程式語言,幫助我們熟悉由 0 跟 1 構成的世界。只要是 IT 人,對 Hello-World 程式一定不陌生。開始上手 GitHub 的第一步就從 Hello- World 專案開始吧。

在 GitHub 上開啟專案並不難。首先申請帳號並開啟一個專案儲存庫,並且在本機端安裝 SourceTree、GitHub for Windows 等 Git GUI 工具,同步本機端儲存庫與遠端儲存庫,就可以開始進行版本控制。GitHub 是以 Git 為核心的線上協作平臺,使用議題(Issue)、新開分支(Branch)、維基(Wiki)、拉取要求(Pull Request)等功能,開發者能更有效率的進行版本控制,並且在線上合作、討論專案及透過 GitHub 分享程式碼。


步驟 1 建立 Hello-World 專案儲存庫

建立儲存庫是使用 GitHub 的第一步。儲存庫是 GitHub 的基本元件,通常一個儲存庫對應到一個專案。儲存庫裏頭可以放置文件、資料夾與其它專案所需要的檔案。建議在每個儲存庫中附上描述這個專案的「讀我檔案」,方便自己或者其他開發者想要檢閱這個專案的時候,能快速建立對這個專案的認識。


步驟 2 用議題來管理開發任務的進度

在 GitHub 通常會用議題(Issue)功能來管理開發需求,使用者提出一項開發需求或有程式臭蟲要修改,就會發起一個議題來追蹤這個工作的進度。議題就像是一個工作筆記,可以紀錄某個程式臭蟲,或者說明開發中的功能等等。在 GitHub 上,可以對議題執行搜尋、標籤,或把議題指派給某個共同開發者處理,並在議題中開啟對話,讓共同協作的過程更順利。


步驟 3 建立分支管理版本

在 GitHub 專案中新增了一個儲存庫後,第一個分支的名稱預設為主幹(Master)。通常主幹是穩定版本,這個版本主要用於存放測試穩定後的專案程式碼,或是最主要的一份程式碼。

如果專案要開發新功能,或者使用者要自行發展自己的專案版本,則會使用 Fork 指令來增加新的分支版本。

在分支版本上開發程式,等到分支版本上的功能完成開發後,才會將程式碼合併回主幹版本,避免未經測試還不穩定的功能,影響了穩定版本的內容。


步驟 4 執行提交建立新版本

開發者要將修改檔案後的專案內容,增加為一個新的專案版本,這個動作就稱為提交(Commit)。開發者可以每次開發完一項功能,就先將新版程式碼,先提交到本機端儲存庫上,再等到多次提交後,才一次將本機端儲存庫上的專案程式碼,一次性發布(稱為 Push)到 GitHub 網站上的雲端儲存庫。通常執行提交時會輸入提交訊息(commit message),說明這個版本做了些什麼修正。有了這些訊息記錄,專案中的開發者便可以追蹤每個版本間的更動。


步驟 5 用拉取要求提醒他人整併程式碼

在拉取要求(Pull Request)是 GitHub 協作開發的重要功能。透過拉取要求,主動要求他人把自己的程式碼納入專案中。比方 A 開發者看到 B 開發者的專案,覺得也想參與開發,便執行 Fork 指令複製一份到自己的遠端儲存庫。而 A 開發者完成某個新功能後,向 B 開發者提出拉取要求,希望能把變更整併到 B 的專案中。透過拉取要求,開發者也能做到基本的權限控管,而不是無條件的讓別人的變更合併到自己的專案中。比方在收到拉取要求後,原專案發起者能在 GitHub 上比對兩個版本間差異,覺得變更有益於原專案後再合併到自己的專案。許多人使用拉取要求,在 GitHub 上面開始跟其他開發者展開專案的討論。


步驟 6 用 Wiki 整理專案參考文件

GitHub 也提供了 Wiki 功能,可供開發者建立專案文件,來記錄專案開發需要的參考資訊。如開發時,特定需求的議題往往會經過多次修改和討論後,功能變動才會定案,就可以將此議題內的開發者討論過程,整理出精華版本後放入 Wiki 文件,供未來其他開發者參考,也不用再打開該議題回溯冗長的討論過程才能得知結果。Wiki 也是多奇數位創意技術總監黃保翕推薦的好用功能,黃保翕表示,透過 Wiki 功能可以好好歸納專案文檔,方便開發團隊成員檢閱。


 Git 參考資料

Git 官方網站:http://git-scm.com/

Try Git:https://try.github.io/levels/1/challenges/1

Git-it:http://jlord.us/git-it/index-zhtw.html

GitHub Guides:https://guides.github.com

保哥 30 天精通 Git 版本控管:http://ithelp.ithome.com.tw/ironman6/player/doggy/dev/1



程式語言編年史

程式語言編年史原文 下面這張圖片描繪了整個程式語言的歷史。包括各種程式語言的發明人、程式語言的特點和適用領域、被什麼網站或公司使用等 (檢視 完整高清圖 )。 之所以會有那麼多不同的程式語言是因為設計程式語言的初衷不同、對語言學習曲線的追求不同、不同程式之間的執行成本差異...