2016年12月20日 星期二

iOS筆記:Firebase Guide with Swift 3

  1. 建立新專案[建立專案名稱]
    enter image description here
  2. 點選將Firebase加入iOS專案
enter image description here
         3. 將專案的ID加進Firebase中, 在專案的”General“的Bundle Identifier.

enter image description here

4.  Firebase會直接下載GoogleService-info.plist, 將此檔案加進Xcode專案中

enter image description here

      5.透過Cocoapods將Firebase加到專案裡.

enter image description here

enter image description here

      6. 在AppDelegate.swift中加入 import FirebaseFIRApp.configure()

enter image description here

其他功能:

大部分的功能可以參考Firebase線上的文件即可完成


Reference:

  1. https://codelabs.developers.google.com/codelabs/firebase-ios-swift/#12
  2. https://firebase.google.com/docs/cloud-messaging/ios/certs

2016年12月18日 星期日

iOS筆記:在Swift中使用Objective-C的專案

在使用Swift開發的時候, 一定會遇到只用3rd party專案的情形發生. 但也不是每個open source的專案都會轉到Swift中進行開發. 所以這邊紀錄一下. 怎麼在Swift中使用3rd party的Objc-C專案.

Objective-C Bridging Header File

不管是[在Swift專案中使用Objective-C檔案]或是[在Objective-C專案中使用Swift檔案]都需要使用Objective-C bridging header檔案。加入Objective-C bridging header檔案的方法有兩種:
  1. 由Xcode 自己產生
    首先在File -> new file中, 點選Objective-C file (在objc-c 中就是新增.swift的檔案), 名稱可以隨便亂取, 我們主要只是為了再新增檔案的時候會跳出下面的視窗.
enter image description here.

按下 Create Bridging Header後, 就可以把新增的專案刪除了.
  1. 自己手動新增
    如果沒跳出上面的視窗或者是點錯的話就可以在File -> New File。然後選[iOS](或是watchOS或是tvOS或是OSX)下的[Source]裡面的[Header File]自己產生一個標頭檔。
    名稱的話我會跟著下面的方式照打上!
    [Your project name]-Bridging-Header
如果都沒有跳出新增的畫面的話. 可以去專案的 [Build Settings] 下, Swift Compiler – Code Generation一欄中,新增Objective-C bridging header檔案的位置。

enter image description here

新增完之後…

好了之後就是要進行import的動作了.

enter image description here



2016年11月26日 星期六

ML初學筆記:Keywords 筆記

這裡主要是針對流程做一個紀錄, 也可以說是一個筆記, 所以會收錄一些從別人那轉載過來一些文字敘述或是圖等. 來源皆在最下面的網址中. 謝謝!

這次主要針對Google的TensorFlow作為機器學習的入門對象, 在學的時候有出現很多單字, 因此在這邊就紀錄下他們在TensorFlow中代表的意義是什麼.

TensorFlow

TensorFlow為Google開發的一個開源的機器學習工具, 官網有很多解釋以及相關的範例帶你上手這一個強大的工具.
架構如下圖所示, 支援Python跟C++. 在 Python 语言中, 返回的 tensor 是 numpy ndarray 对象; 在 C 和 C++ 语言中, 返回的 tensor 是 tensorflow::Tensor 實例.
主要是跑在Linux&Mac系統上. Windows在執行的時候必須先用虛擬機再安裝Linux才行.
enter image description here
執行的流程, 最大主題可以分成兩塊:
一:模型定義(modeling):抽象化,此階段完全是紙上談兵
1. 輸入項、輸出項的定義(tensorflow的術語叫:placehold)
2. 模型建構(隱藏層)
a. 定義 Variable (例:權重、誤差項)
b. 定義網路 (例:y = W*X+b)
3. 初始化 Variable
4. 定義Loss函數
5. 最佳化:讓Loss函數最小化(Tensorflow 內建 Gradient Desecnt 等,可直接叫用)
二:執行(session階段):計算資源投入(CPU/GPU)、實際資料投入
1. 開啟 Session 來run Graph
2. 要跑幾次去達成最佳化(Iterative Update)
3. 訓練資料讀入( feed)

Tensor

Tensor可以看作是一个 n 维的数组或列表. 計算Graph中, 操作間傳遞的數據都是 Tensor.

Operation

Graph 中的節點,也就是對資料的操作/計算

Variable

A variable maintains state in the graph across calls to run(). You add a variable to the graph by constructing an instance of the class Variable.
(Variable is something that can updated as your training. )

Placeholder

A input into neural network.

Data Flow Graphs

Data Flow Graphs使用節點(Nodes)與邊(Edges)所繪出的有向圖(Directed Graph)來描述數學運算[如下圖所示]。Nodes一般是用來實作數學運算,也可以儲存資料、運算結果或持久性變數,Edges描述Nodes之間的I/O關係。
這些數據的Edges進行動態調整多維數據陣列(或Tensors)的大小。TensorFlow的命名由來是Graph**[表示一整個計算任務]**上,Tensors的Flow在Nodes間藉著Edges穿梭傳遞。當系統中Nodes的所有Tensors從輸入端Edges取得,Nodes就被分配到的運算裝備中,Nodes可非同步且平行的方式作業。
enter image description here

Session

在 Session 階段中才會執行 graph.

Loss function

比較預估以及真實結果之間的差異, 在一開始的範例常使用下列的”Cross Entropy”.

Optimizer

根據Loss function的值 導入學習率去修正誤差.

Cross-Entropy

Entropy: 熵的概念最早起源於物理學,用於度量一個熱力學系統的無序程度。在資訊理論裡面,熵是對不確定性的測量。但是在資訊世界,熵越高,則能傳輸越多的資訊,熵越低,則意味著傳輸的資訊越少。
大多數人都會對犯錯感到不愉快。當發生錯誤的時候,可能當下會覺得非常的悲劇,但是如果可以記得這次的教訓下一次再遇到不犯錯其實就可以了. 且當犯的錯越大的時候,一般人對這錯誤的印象會記很久以避免再次的犯錯,相反,如果錯誤很不明顯的話,我們的學習改進的速度可能將會很慢。
對於電腦來說我們也是希望它在學習時,當發生錯誤的時候所做的修正就應該要越大. 而這就是使用Cross Entropy的一個優點,可以參考名稱的超連結有更近一步的介紹.

Gradient Descent

迴歸方程式是一次方程式, 所以是一條線, 而Loss Function是計算最小平方和,是自變項的二次方程式(下圖右),所以是個曲線(如果是多元迴歸就是一個高維度的山谷)
enter image description here
那做法就是將問題從線性方程式轉換成求最小平方和的最佳解.要求最佳解用的方法是,先隨便站在山谷上的任何一點,發現哪邊比較低,就往哪邊走. 透過多次的迭代來找到最低點,化成公式就長得像下面這樣:
enter image description here
當θ落在最小值的右邊,斜率是正的,帶到公式中,就會減少θ值;反之,如果θ比最小值還小,那斜率是負的,帶到迭代公式中就會增加θ值.由於曲線越接近谷底越為平緩,所以當θ靠近最低點時,每次移動的距離也會越來越小,最終將收斂在最低點.
enter image description here
講完微分的部分,接下來是α值(learning rate).α值的用途主要在控制每次θ移動的距離,下圖顯示移動距離太大或太小的狀況:
enter image description here
如果一次移動太短的距離(左圖),那收斂速度會相當的慢.但是如果移動的距離太大(右圖),反而可能造成無法收斂的情形.所以會需要根據不同的狀況來調整這個α的大小.
References:
1. http://mropengate.blogspot.tw/2016/10/ai-ch165-tensorflow.html
2. http://www.slideshare.net/ckmarkohchang/tensorflow-60645128
3. http://tensorflow-tw.blogspot.tw/2015/12/tensorflow.html
4. http://blog.moebigdata.com/2016/01/tensorflow.html
5. https://hit-scir.gitbooks.io/neural-networks-and-deep-learning-zh_cn/content/chap3/c3s1.html

2016年11月18日 星期五

ML初學筆記:安裝Tensorflow

這裡主要是針對流程做一個紀錄, 也可以說是一個筆記, 所以會收錄一些從別人那轉載過來一些文字敘述或是圖等. 來源皆在最下面的網址中. 謝謝!

安裝步驟

參考Tensorflow官網的安裝流程, 按照以下步驟就可以在Mac上安裝當下最新的版本.

$ virtualenv --system-site-packages ~/tensorflow
$ source ~/tensorflow/bin/activate
(tensorflow)$  # Your prompt should change

(tensorflow)$ export TF_BINARY_URL=https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-0.11.0-py2-none-any.whl

(tensorflow)$ pip install --upgrade $TF_BINARY_URL

在這邊我是參考在Youtube上看的Tutorial .

(tensorflow)$ pip install --upgrade jupyter
(tensorflow)$ pip install --upgrade Pillow

接著去下載在Github上的課程文件. 執行完下面的指令後, 存取課程文件即可.

(tensorflow)$ jupyter notebook

enter image description here

Virtualenv

Virtualenv 可以隔離函數庫需求不同的專案,讓它們不會互相影響。在建立並啟動虛擬環境後,透過 pip 安裝的套件會被放在虛擬環境中,專案就可以擁有一個獨立的環境,也降低了不同版本套件間衝突的可能。

簡而言之,Virtualenv 可以幫你做到:
1. 在沒有權限的情況下安裝新套件
2. 不同專案可以使用不同版本的相同套件
3. 套件版本升級時不會影響其他專案

使用方法
I. 建立虛擬環境
請於命令列模式下輸入下列指令:

$ virtualenv [指定虛擬環境的名稱]

例如下列指令會建立名為 “ENV” 的虛擬環境:

$ virtualenv ENV

預設在建立虛擬環境時,會依賴系統環境中的 site packages,如果想完全不依賴系統的 packages,可以加上參數 –no-site-packages 來建立虛擬環境:

$ virtualenv --no-site-packages [指定虛擬環境的名稱]

II. 啟動虛擬環境
請先切換當前目錄至建立的虛擬環境中。前例中,建立名稱為 “ENV”,則:

$ cd ENV

接著,啟動虛擬環境:

$ source bin/activate

III. 退出虛擬環境

$ deactivate

Reference:
1. https://www.tensorflow.org/
2. https://github.com/sherrym/tf-tutorial/blob/master/install-mac-native.md

2016年8月25日 星期四

Git筆記:gitignore

通常在一般的專案中一定會有引用Library的時候, 但通常這些library伊多, 就會造成整個資料夾過肥, 但這些檔案都是可以透過使用者在本地端下載即可, 所以在這時就可以使用.gitignore來將不需要上傳的檔案留在原地.

iOS的部分可以參考以下網址:
1.Objective-C.gitignore
https://github.com/github/gitignore/blob/master/Objective-C.gitignore
2.Stack
http://stackoverflow.com/questions/49478/git-ignore-file-for-xcode-projects
3.Gitignore產生器
https://www.gitignore.io/
4.一些程式語言的參考
https://github.com/github/gitignore

SourceTree

可以手動加入.
enter image description here

手動新增.gitignore

利用VIM新增檔案即可.

PUSH後才新增時

會發現.gitignore沒有作用, 原因是因為.ignore是後來加進來的 local cache裡面記錄的這個文件是不會忽略的, 所以需要進行緩存清除

$ git rm --cached iLedger.xcodeproj/project.xcworkspace/xcuserdata/mac.xcuserdatad/UserInterfaceState.xcuserstate
$ git commit -m "Removed the stupid strange file that shouldn't be tracked"
$ git push

2016年7月31日 星期日

Node.js筆記:Node.js with Socket.io

WebSocket:

為一個從HTML5中開始的一個協定, 瀏覽器和伺服器之間只需要一個handshaking動作就形成了一條快速通道. 兩者之間可以直接互相傳送資料.
這種方式可以避免無謂的浪費伺服器頻寬和資源(舊式的是一段時間後再發一次request.)

Socket.io

一般的http協定不支援長久的連接, 需要不斷地發送request給server更新資料. 而Socket.io可以達到 server 和 client 的雙向溝通, 只要一建立連線 server 可以一直傳訊息給 client.

ChatRoom Example

1.新增Chatroom資料夾並新增package.json
首先新增一個Chatroom的資料夾, 並執行以下指令安裝相關套件.

    {
      "name": "socket-chat-example",
      "version": "0.0.1",
      "description": "my first socket.io app",
      "dependencies": {}
    }

2.安裝express & Socket.io

    $npm install --save express
    $npm install --save socket.io

3.新增index.js
第一行: 建立一個express module的app function.
第二行: 建立 http 物件
第三行(app.get…): 處理 / (根目錄) 的 URL GET 要求

sendFile為傳輸檔案, 在這裡傳的是index.js所在資料夾底下的index.html
__dirname: 返回被執行的.js檔所在資料夾的絕對路徑
最後則是監聽PORT:3000

    var app = require('express')();
    var http = require('http').Server(app);

    app.get('/', function(req, res){
      res.sendFile(__dirname + '/index.html');
    });

    http.listen(3000, function(){
      console.log('listening on *:3000');
    });

4.建立Socket.io
首先導入Socket.io模組, 因 Socket.io 依賴 HTTP 所以這裡採用一併引入的方式.

socket.io 的核心函數:

emit :用來發射一個事件或者說觸發一個事件, 第一個參數為事件名,第二個參數為要發送的數據, 第三個參數為callback function.
on :用來監聽一個emit 發射的事件, 第一個參數為要監聽的事件名,第二個參數為一個callback function用來接收對方發來的數據.

如上述的介紹這裡的io.on就可以解釋為使用 on() 函數將特定的事件連接到指定的匿名函數(用來定義當伺服器接收到請求時,該做什麼事情,以及該如何回應), 藉此定義整個資料傳輸過程要如何運作。

而connection/connect事件則為socket.io默認的事件之一.
在這邊socket.io提供的interface是基於事件的,server端監聽”connectione”事件, 如果接收到client的連結請求, 就會進入callback function,而連上的socket就會開始監聽事件,事件的名稱就是’chat message’,當socket發出’chat message’事件時就會執行所寫的程式內容,以下面為例,當觸發事件後會針對io所有的client socket發出’chat message’事件以及其後發所帶的msg參數。

io.on為監聽跟client是否達成連線, 內圈的socket.on則是監聽client的事作而回覆對應的function, 通常這裡的事件也需要在client端建立同樣的事件名稱才行.

    var io = require('socket.io')(http);

    io.on('connection', function(socket){
        socket.on('chat message', function(msg){
          io.emit('chat message', msg);
        });
    }); 

5.撰寫index.html
在使用 Socket.io 時, 伺服器端與客戶端都必須要有它的 JS 檔案才有辦法運作, 而在上一步已經為server端裝上 Socket.io module了, 接下來就是為client端, 也就是在html中裝上模組.

裝法為在 HTML 頁面中加入以下這段程式碼.

    <script src="https://cdn.socket.io/socket.io.js"></script>

在Client端一樣要先註冊一個socket, 並建立監聽Server的’chat message’事件。
完整如下:

    <!doctype html>
    <html>
      <head>
        <title>Socket.IO chat</title>
      </head>
      <body>
        <ul id="messages"></ul>
        <form action="">
          <input id="m" autocomplete="off" /><button>Send</button>
        </form>
        <script src="/socket.io/socket.io.js"></script>
        <script src="http://code.jquery.com/jquery-1.12.4.js"></script>
        <script>
          var socket = io.connect();
          $('form').submit(function(){
            socket.emit('chat message', $('#m').val());
            $('#m').val('');
            return false;
          });
          socket.on('chat message', function(msg){
            $('#messages').append($('<li>').text(msg));
          });
        </script>
      </body>
    </html>

參考:
1. http://socket.io/get-started/chat/
2. https://blog.allenchou.cc/socket-io-nodejs/
3. http://socket.io/docs/server-api/

2016年7月3日 星期日

Node.js筆記:Blog system(3)

新增資料庫功能

先在專案中新增一個”lib”的資料夾, 並新建一個檔案”db.js”. 在這裡我們要使用mongoose來存取MongoDB.

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var Blog = new Schema({
    Username: String,
    Article: String,
    CreateDate: Date
});

var Comment = new Schema({
    Visitor: String,
    Comment: String,
    MessageID: Schema.Types.ObjectId,
    CreateDate: Date
});

mongoose.model( 'Blog', Blog );
mongoose.model( 'Comment', Comment );
mongoose.connect('mongodb://localhost/blog');

在上面我們新增的兩個model, 一個叫Blog, 一個是Comment. 最後一行則是連線到本機端的MongoDB伺服器.

接著修改apis.js初始化資料庫
主要是利用require把資料庫設定載入, 再把定義好的model也加入.

require('../lib/db');

var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var Blog = mongoose.model('Blog');
var Comment = mongoose.model('Comment');

...

支線任務:
1.brew update error.
問題簡述在此:https://github.com/Homebrew/legacy-homebrew/issues/49879
解決方式就是利用下面兩組command就可以讓brew正常運作.

    $sudo chown -R $(whoami):admin /usr/local
    $cd $(brew --prefix) && git fetch origin && git reset --hard origin/master

2.MongoDB使用.
安裝:

//Step 1 - Installation ( Don't follow this step if you have already installed MongoDB ):
brew update
brew install mongodb

//Step 2 - Run Mongo Daemon:
mkdir -p /data/db
sudo mongod

//Step 3 - Run Mongo Shell Interface:
mongo

新增功能

1.首頁列出所有文章
修改routes/index.js阿

利用find()找出資料庫中所有的資料, 並把資料存在blogs物件中, 最後把blogs物件回傳給頁面.

require('../lib/db');

var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var Blog = mongoose.model('Blog');

router.get('/', function(req, res, next) {
    res.locals.username = req.session.name ;
    res.locals.authenticated = req.session.logined;
    Blog.find(function (err, blogs, count) {
      res.render('index', {
        title : 'Blog System';
        blogs : blogs;
      });
    });
});

module.exports = router;

修改index.jade, 利用forloop把blogs物件的資料都列印在頁面上.

...
div(style='padding:50px;')
        h1= title

        for article in blogs
                div
                    div(style='float:left;margin-top:10px;')= article.Username
                    div(style='float:right;margin-top:10px;')= article.CreateDate
                    br
                    div(style='clear:both')= article.Article
                        a(href='/users/message/'+article.id, style='float:right;margin-top:105px;margin-right:20px;') Leave a message
                    hr(style='margin-top:110px;')

2.新增文章
views/users/add_article.jade

doctype html
html
    head
        title= Login
    body
        div(style='float:left;margin:10px;')
            a(href='/') Home

        div(style='float:right')
            if (username && authenticated)
                span #{username}
                a(href='/users/profile', style='margin:10px;') Profile
                a(href='/users/signout', style='margin:10px;') Sign Out
            else
                a(href='/users/signin', style='margin:10px;') Log In
                a(href='/users/register', style='margin:10px;') Register
                a(href='/users/forget', style='margin:10px;') Forgot password

        div(style='padding:50px;')
            form(name="add", action="/apis/add", method="post")
                h1 Add Article
                div
                    p
                        span Writer:
                        label(style='padding-right:50px') #{username}
                    p
                        textarea(cols="50", rows="25", style='resize : none;', name="Content")
                    div
                        span(style='padding-right:377px')
                        input(type="submit", name="submit", value="Save")

後半段為一個POST方法, 當按下送出按鈕後會執行 /apis/add 的網址.

修改users.js跟apis.js
下面先判斷使用者是否有登入, 如果有登入才能進入新增文章.

#/users/js

router.get('/add_article', function(req, res, next) {
  if((!req.session.name) || (!req.session.logined)) {
    res.redirect('/');
    return;
  }
  res.locals.username = req.session.name;
  res.locals.authenticated = req.session.logined;
  res.render('users/add_article');
});

...
#/apis.js

router.post('/add', function(req, res, next) {
  if (!req.session.name) {
      res.redirect('/');
      return;
  }
  new Blog({
      Username: req.session.name,
      Article: req.body.Content,
      CreateDate: Date.now()
  }).save( function( err ){
      if (err) {
          console.log('Fail to save to DB.');
          return;
      }
      console.log('Save to DB.');
  });
  res.redirect('/');
});

enter image description here

2016年7月1日 星期五

Node.js筆記:Blog system(2)

Express generation 專案架構

當用express generation指令後會產生一個node project的資料夾.
enter image description here

  • app.js: 開機檔案, 或是說入口網站
  • bin: 存放執行檔
  • node_modules: 存放package.json中安裝的模組, 當你在package.json中新增相依的模組並安裝後, 該模組會存放在這個資料夾下.
  • package.json: 儲存著專案的資訊以及模組相依(dependencies), 當在dependencies中新增相依的模組時, 執行npm install, npm會檢查目前的資料下的package.json, 並自動安裝所有指定的模組
  • public: 存放image, css, js等文件
  • routes: 存放路由文件
  • views: 存放視圖檔, 或是說範本檔案

配置Routing

規劃網站各個頁面的路由路徑. 首先再次修改app.js: 新增apis路由, 用來負責POST方法的處理.

    ...
    var routes = require('./routes/index');
    var users = require('./routes/users');
    var apis = require('./routes/apis');

    ...
    app.use('/', routes);
    app.use('/users', users);
    app.use('/apis', apis);

設定user路由

在我們的設定中, user路由負責處理所有來自/user網址的要求, 例如新增文章, 會員註冊等. user路由會視用戶的請求而回應不同的資料.

新增首頁

首先在views資料夾中建立一個users資料夾. 裡面會放置所有user路由會用到的頁面.

首頁

1.更新index.jade檔
中間的if判斷是會根據使用者是否登入而顯示不一定的連結給使用者.

    doctype html
html
    head
        title= 'Blog System'
    body
        div(style='float:left;margin:10px;')
            a(href='/') Home

        div(style='float:right')
            if (username && authenticated)
                a(href='/users/signout', style='margin:10px;') Sign Out
                a(href='/users/add_article', style='margin:10px;') Add Article
                a(href='/users/profile', style='margin:10px;') Profile
                span #{username}
            else
                a(href='/users/signin', style='margin:10px;') Log In
                a(href='/users/register', style='margin:10px;') Register
                a(href='/users/forget', style='margin:10px;') Forgot password

    div(style='padding:50px;')
        h1= title

2.修改routes/index.js
把session所存的變數所存的變數傳回頁面.

    var express = require('express');
    var router = express.Router();

    /* GET home page. */
    router.get('/', function(req, res, next) {
        res.locals.username = req.session.name ;
        res.locals.authenticated = req.session.logined;
        res.render( 'index', {title : 'Blog System'});
    });
    module.exports = router;

使用者註冊頁面

1.新增views/users/register.jade
這邊的重點是寫了一個form的POST表單來處理使用者設定帳密

doctype html
html
    head
        title= Register
    body
        div(style='float:left;margin:10px;')
            a(href='/') Home

        div(style='float:right')
                a(href='/users/signin', style='margin:10px;') Log In
                a(href='/users/forget', style='margin:10px;') Forgot password

        div(style='padding:50px;')
            form(name="login", action="/apis/login", method="post")
                h1 Register
                div
                    p
                        label(style='padding-right:50px') Username or e-mail
                        input(type="text", name="user", placeholder='username@example.com')
                    p
                        label(style='padding-right:110px')  password
                        input(type="password", name="passwd")
                p
                    span(style='padding-right:260px')
                    input(type="submit", name="submit", value="Sing Up")

2.修改routes/user.js
新增一個判斷式, 如果logined是true, 代表使用者已經是登入狀態, 就會直接回到首頁.
如果尚未登入則會顯示register.jade的頁面.

var express = require('express');
var router = express.Router();

router.get('/register', function(req, res, next) {
    if (req.session.logined) {
        res.redirect('/');
        return;
    }
    res.render('users/register');
});

    ....

3.修改routes/apis.js
新增login函數, 處理註冊頁面傳來的POST方法.
先利用if來判斷使用者是否有輸入帳密, 當其中少一個的話則返回註冊畫面.
當輸入正確就把帳密給儲存到session中. 並把login設定為true代表使用者登入了.

ar express = require('express');
var router = express.Router();

router.post('/login', function(req, res, next) {
    if ((!req.body.user) || (!req.body.passwd)) {
        res.redirect('register');
        return;
    }
    req.session.name = req.body.user;
    req.session.passwd = req.body.passwd;
    req.session.logined = true;
    res.redirect('/');
});

...

使用者登入畫面

1.使用者登入頁面跟註冊的register.jade比, 只有差在字改成”Login”.

doctype html
html
    head
        title= Login
    body
        div(style='float:left;margin:10px;')
            a(href='/') Home

        div(style='float:right')
                a(href='/users/register', style='margin:10px;') Register
                a(href='/users/forget', style='margin:10px;') Forgot password

        div(style='padding:50px;')
            form(name="login", action="/apis/login", method="post")
                h1 Login
                div
                    p
                        label(style='padding-right:50px') Username or e-mail
                        input(type="text", name="user", placeholder='username@example.com')
                    p
                        label(style='padding-right:110px')  password
                        input(type="password", name="passwd")
                p
                    span
                        a(href='/users/forget', style='padding-right:175px') Forgot password
                    input(type="submit", name="submit", value="login")

2.修改routes/user.js

...

router.get('/signin', function(req, res, next) {
    if (req.session.logined) {
        res.redirect('/');
        return;
    }
    res.render('users/signin');
});

使用者登出頁面

1.修改routes/user.js
當登出之後會回到首頁.

...
router.get('/signout', function(req, res, next) {
    req.session.logined = false;
    res.redirect('/');
    res.end();
});

忘記密碼頁面

1.views/users/forget/jade

doctype html
html
    head
        title= 'Forget Password'
    body
        div(style='float:left;margin:10px;')
            a(href='/') Home

        div(style='float:right')
                a(href='/users/register', style='margin:10px;') Register
                a(href='/users/signin', style='margin:10px;') Log In

        div(style='padding:50px;')
            form(name="login", action="/apis/login", method="post")
                h1 Forget Password
                div
                    p
                        label(style='padding-right:50px') Username or e-mail
                        input(type="text", name="user", placeholder='username@example.com')
                p
                    span(style='padding-right:210px')
                    input(type="submit", name="submit", value="Forget Password")

2.修改routes/user.js

...

router.get('/forget', function(req, res, next) {
    if (req.session.logined) {
        res.redirect('/');
        return;
    }
    res.render('users/forget');
});

2016年6月30日 星期四

Node.js筆記:Blog system(1)

練習利用Node.js實作一個Blog:

主要可以分成下列幾項下去實作.
1. 使用Express Web Framework建置一個網站
2. 使用cookie-based session建立會員機制
3. 新增Jade模板動態產生各個頁面
4. 新建自行設計的Restful API處理自訂路由
5. 使用mongoose控制MongoDB資料庫

  1. 使用Express Web Framework建置一個網站
    Express 4.0後提供了一個新的工具“express-generator”, 可以讓我們快速建立一個網站專案, 並幫我們初始化一些基礎設定.
    $npm install express
    $sudo npm install -g express-generator

enter image description here

    $express blog-system

執行express指令時, 後方參數為資料夾名稱.

enter image description here

express命令預設會把express和jade套件填入預先產生的package.json檔中, 所以我們能透過npm install指令把模組安裝完畢.

enter image description here

express 跟 Jade安裝好後就能透過Node.js啟動網站.

    $SET DEBUG=blog-system:* & npm start

打開瀏覽器輸入:http://localhost:3000就可以看到網站了.
enter image description here

npm init指令

Express產生的package.json只有基本的資訊, 這時可利用npm init指令更新專案的相關資訊.

enter image description here

當初入完自訂的資訊之後, 最後會把結果印出來(礙於畫面太長我只截取一小段表示), 如果確定資料正確就會存入package.json中.

安裝mongoose套件

在這邊我們要來修改package.json, 把mongoose套件寫到相依模組(dependencies), 這樣才能把文章寫入MongoDB或是從MongoDB中把文章讀取出來.

新增:
cookie-session是為了用來處理會員登入和登出時可以記得當下的status.

"*"表示版本不限!!!

    "cookie-session": "*",
    "mongoose": "*"

enter image description here

接著透過”npm install”來安裝新增的套件.
enter image description here

2016年6月28日 星期二

Node.js筆記:Express

Express Web Framework

官方定義:
Web Applications

Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.

主要幫忙解決許多node.js http server 所需要的基本服務,讓開發http service 變得更為容易,不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式。

使用Express之後, 它包裝了大多數繁複的HTTP低階操作, 使得我們的程式碼更為精簡.

安裝:
要不要-g(global)就看個人需求.

    npm install -g express

enter image description here

Why use Express?

  1. Express helps you respond to request with route support so that you may write responses to specific URLs.
  2. Supports multiple templating engines to simplify generating HTML.

Explanation of Routes

  • A router maps HTTP requests to a callback(針對從 Client 送來的 URL 做相對應的處理)
  • HTTP requests can be sent as GET/POST/PUT/DELETE …
  • URLs describe the location targeted.
  • To a request handler(callback)
    -> app.<HTTP請求類型>(url,handler{});
    app.get('/index', function(req, res){});

request: 連線要求的資訊和方法:
包含了瀏覽器傳來的各種信息,像是query ,body ,headers,都可以通過req對象訪問到。

response: 回應連線的資訊和方法
我們一般不從裡面取信息,而是通過它來定制我們向瀏覽器輸出的信息,比如 header 信息,比如想要向瀏覽器輸出的內容。這裡我們調用了它的 #send 方法,向瀏覽器輸出一個字符串。

app.get 可以帶入兩個參數,第一個是路徑名稱設定,第二個為Call back function),這裡面就如同前一篇介紹的 createServer 方法,裡面包含 request跟response 兩個物件。使用者就可以透過瀏覽器,輸入不同的url 切換到不同的頁面,顯示不同的結果。

Example:

var express = require('express');
var app = express();

app.get('/', function(req, res){
  res.send('This is GET method');
  res.end;
});

app.listen(process.env.PORT || 12345);

Express middleware

Express在處理一個連線要求時, 利用了middleware的設計, 讓開發者隨時可以自訂或擴充處理的流程. 也可以在適當的時機安插執行一些程式.

為了載入middleware要使用.use, 一個基本middleware長的樣子如下:

function logger(req, res, next){
  console.log(new Date(), req.method, req.url);
  next();
}

Middleware 會收到 3 個參數:
1. req 是 Request 物件,存放這此請求的所有資訊
2. res 是 Response 物件,用來回應該請求
3. next 用來控制流程

上面所寫的logger middleware會把每次request的時間, method還有url都列印在終端機上, 然後呼叫next()往下個middleware過去。Express.js的任務, 就是管理你所有middleware chain。

app.use 和 app.get

再來我們寫一個叫作hello的middleware:

function hello(req, res, next){
  res.write('Hello! \n');
  next();
}

將這個middleware加到我們的middlware chain中:

app.use(logger);
app.get('/hello', hello);
`app.use`表示 所有的request都會執行這個middleware
`app.get`表示 只有針對該URL 做 GET request的時候才會執行這個middleware

Reference:
1. http://expressjs.com
2. https://www.youtube.com/watch?v=czmulJ9NBP0
3. https://dca.gitbooks.io/nodejs-tw-wiki-book/content/book/node_express/node_express.html
4. https://stormpath.com/blog/how-to-write-middleware-for-express-apps
5. http://www.jollen.org/blog/2013/11/expressjs-middleware.html

2016年6月25日 星期六

Node.js筆記:Basic HTTP server

一個基本的網站伺服器流程:
1. 建立HTTP伺服器, 監聽指定的PORT
2. 等待連線
3. 取得新的客戶端連線要求
4. 回傳字串

Example:

var http = require('http');

var server = http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World!\n');
});

server.listen(12345);

console.log('Server rinning at http:127.0.0.1:88888/');

enter image description here

上述主要是透過Node內建的http模組, 然後使用http.createServer()去建立伺服器. 最後使用listen()方法去監聽指定的port.

當在cmd中執行上述的程式, 就可以在瀏覽器上輸入127.0.0.1:88888, 就能得到hellow world的回傳結果.

Port

Port:通過端口來區分出同一電腦內不同應用或者進程,從而實現一條物理網線(通過分組交換技術-比如internet)同時鏈接多個程序

端口號是一個16位的uint, 所以其範圍為1 to 65535 (對TCP來說, port 0 被保留,不能被使用. 對於UDP來說, source端的端口號是可選的, 為0時表示無端口).

server.listen(12345),程式在執行時,電腦接收到的12345端口的網路消息就會被發送給我們啟動的這個程式.

2016年6月23日 星期四

Django筆記:自定義過濾器

在這邊只需要記住一點“過濾器本身是一個函數”.

接著要新增一個filter來判斷食物辣或是不辣.

<tr>
    <td> {{ food.name }} </td>
    <td> {{ food.price }} </td>
    <td> {{ food.is_spicy|yes_no:"辣/不辣" }} </td>
    <td> {{ food.comment }} </td>
</tr>

1. 寫一個Filter函式

首先打開myfilters.py

def yes_no(bool_value, show_str):
    if bool_value:
        return show_str.partition('/')[0]
    else:
        return show_str.partition('/')[2]

這邊的函式名稱不需要跟我們定義的過濾器名稱一樣. 但是一樣的話通常會比較好管理.
bool_value是過濾器的第一個參數,他負責接收模板中pipe符號(|)左邊的值;而show_str是第二個參數,會用來接收過濾器中的額外參數, 在這邊指的是”辣/不辣”

2. 註冊該函式

from django import template
...
register = template.Library()
register.filter('yes_no', yes_no)

在這邊我們使用register的filter函式來幫我們註冊, filter函式第一個變數是”過濾器名稱“, 第二個參數是他對應的過濾器函式

3. 載入過濾器

最後就是在模板中載入他

{% load myfilters %}
...

2016年6月22日 星期三

Django筆記:模板繼承

當我們網站模板太多, 而且每一個模板之間共同的部分又很多的時候. 為了一個Don’t repeat yourself的精神. 就可以利用模板繼承的方式把“相似度”高的頁面共同使用同一個模板,方便我們之後的開發.

一個繼承的模板區塊以{% block BLOCKNAME %}為開頭, 以{% endblock %}為結尾. 可以填入內容也可以空白. 接著使用{% extends%}來繼承模板.

原則

  1. 基礎模板中的block越多越好, 並不是要每個區塊都要求繼承. 但是預留更多可變的位置, 會使得模板設計上更靈活
  2. 如果有大量重複的代碼存在各個模板中, 可以考慮抽取出來做成基礎模板.

模板繼承的步驟

  1. 定義基礎模板, 利用{% block BLOCKNAME %}{% endblock %}.
  2. 利用 {% extends%}來繼承模板
  3. 利用模板區塊來覆寫”基礎模板“的內容, 未被覆寫的將用原本模板就設定好的內容.

Example:

#base.html

<!doctype html>
<html>
    <head>
        <title> {% block title %} {% endblock %} </title> 
    </head>
    <body>
        <nav>
            {% if request.user.is_authenticated %}
                <li><a href="#">Hi! {{ request.user }}</a></li>
                <li><a href="/restaurants_list/">餐廳列表</a></li>
                <li><a href="/accounts/logout/">登出</a></li>
            {% else %}
                <li><a href="/accounts/login/">登入</a></li>
                <li><a href="/accounts/register/">註冊</a></li>
             {% endif %}
        </nav>
        <h2>{% block h2 %}{% endblock %}</h2>
        {% block content %}{% endblock %}
    </body>
</html>
#index.html
{% extends "base.html" %}

{% block title %} 首頁 {% endblock %}

{% block content %}
    <h2>歡迎來到Dinbendon</h2>
{% endblock %}

2016年5月26日 星期四

筆記:iTerm2使用Oh-My-Zsh

改成使用Zsh的最大原因是:美觀
雖然網路上也有很多心得分享.

1.先刪除原本的設定檔.
由於之前我有使用其他的設定檔, 先用ls來搜尋目標資料夾, 再利用rm指令把檔案刪除就可以回覆成初始設定.

2.安裝Zsh

$ curl -L https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh | sh

3.選擇主題
安裝完之後,可以對主題/套件做一些調整,Oh-My-Zsh 的主題可以參考

主題/套件主要的設定在~/.zshrc

$ vim ~/.zshrc

修改的內容如下:

ZSH_THEME = "agnoster"
# 將 ZSH_THEME 設為主題的名稱,以 agnoster 主題為例
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
# 中文設定的話要加上這兩行

4.調整字型
當完成主題設定重開iTerm時會顏色區塊中有出現“?”.
所以這裡就必須要調整字體的設定. 先下載背景顏色以及字體 選擇powerline字體

到 iTerm 的設定, 選擇 Profiles -> Colors -> Load Presets,選擇 Solarized Dark

enter image description here

點擊安裝完字體後, iTerm -> Preferences -> Profiles -> Text -> Change Font
兩個字體都選擇Change Font, 選擇下載的[Menlo for Powerline]即可.

enter image description here

如果用其他主題的話字體不必調整,請直接按照該主題的設定用就好了.

5.成果
enter image description here

參考網址:
1. http://v123582.github.io/blog/2015/12/06/MacOS-%E9%96%8B%E7%99%BC%E7%92%B0%E5%A2%83%E8%A8%AD%E5%AE%9A/
2. https://gist.github.com/kevin-smets/8568070
3. http://iphone4.tw/forums/showthread.php?t=206652

2016年4月15日 星期五

iOS筆記:UITableView Programming

這次在練習利用code把UITableView建立出來
1. 在'h'中宣告delegate 跟 datasource.

@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
@property (strong, nonatomic) UITableView *tableview;
@end
    2.

@interface ViewController ()
{
    NSMutableArray *testArray;
}
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    _tableview = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
    [self.view addSubview:_tableview];
    _tableview.delegate = self;
    _tableview.dataSource = self;
    testArray = [NSMutableArray arrayWithObjects:@"Iron Man", @"Spiderman", @"Superman", @"Batman", nil];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [testArray count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
    if (cell == nil) {
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
    }
    cell.textLabel.text = [testArray objectAtIndex:indexPath.row];
    return  cell;
}
@end

Error

Solution:
1. http://stackoverflow.com/questions/25826383/when-to-use-dequeuereusablecellwithidentifier-vs-dequeuereusablecellwithidentifi
2. http://stackoverflow.com/questions/12737860/assertion-failure-in-dequeuereusablecellwithidentifierforindexpath

參考:
1. https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/TableView_iPhone/CreateConfigureTableView/CreateConfigureTableView.html

2016年4月14日 星期四

iOS筆記:iAd Banner

  1. 將iAd framework加到專案中:
    在左側的 Project Navigator 中點選Project
    再點選TARGETS中的專案, 標籤選擇 General
    在 Linked Frameworks and Libraries 點選'+'
    尋找iAd.framework 再按 'add'便成功加進專案

enter image description here

  1. 在 ViewController.h 中 import 'iAd.h'檔案並加上 delegate
#import <iAd/iAd.h>

@interface ViewController : UIViewController <ADBannerViewDelegate>
  1. 拖曳或是建立一個Banner View
    (也要將BannerView的Delegate連到ViewController)
    enter image description here

or

@interface ViewController ()
{
    ADBannerView *bannerTestView;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    bannerTestView = [[ADBannerView alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height - 50, 320, 50)];
    bannerTestView.delegate = self;
    // 預設為隱藏 load成功後再在 delegate 的 method 中顯示
    bannerTestView.alpha = 0.0;
    [self.view addSubview:bannerTestView];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
    //banner 廣告載入
    NSLog(@"Ad Banner did load ad.");
    // Show the ad banner.
    [UIView animateWithDuration:0.5 animations:^{
        bannerTestView.alpha = 1.0;
    }];
}

- (BOOL)bannerViewActionShouldBegin:(ADBannerView *)banner willLeaveApplication:(BOOL)willLeave
{
    //使用者點了banner廣告後開啟廣告內容畫面
    return YES;
}

-(void)bannerViewActionDidFinish:(ADBannerView *)banner
{
    //使用者關掉廣告內容畫面
}

- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
    //廣告載入錯誤時會呼叫此方法
}

2016年4月2日 星期六

C/C++ 筆記:typedef struct

typedef用法

定義一個已知資料型態的別名, 也就是說可以用這個名稱代替設定的資料型態.

    typedef int NewINT; //幫int取一個別名叫NewINT

typedef struct

    typedef struct Node
    {
        int no;
        char Name[20];
    } NewNode;

上述程式可以分成兩個部分來看:
1. 就是定義一個Node的結構.
2. 幫這個Node的結構取一個別名叫NewNode.

結合起來的結論就是“定義一個Node的結構並給他一個等義的別名NewNode”

Example


``````
    typedef struct CSNode
    {
        int no;
        char Name[20];
    } MyNode, CSTree;

意義:宣告一個CSTree變數, 其為一個CSNode的結構, 別名為MyNode.

2016年3月31日 星期四

iOS筆記:判斷裝置種類及取得Size資訊 & Super用法

這邊練習如何得知目前device的方向以及種類.

在ViewDidLoad中透過traitCollection屬性判斷裝置的種類.

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    if (self.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
        NSLog(@"Pad");
    } else if (self.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
        NSLog(@"Phone");
    }
}

實作- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator,這個方法是當width或是height的regular或compact有變化時呼叫.

iPad的長寬都是regular, 因此iPad方向變化時這個method不會被呼叫.

- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
    [super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator];

    if (newCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact) {
        NSLog(@"Compact width");
    }
    if (newCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular) {
        NSLog(@"Regular width");
    }
    if (newCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact) {
        NSLog(@"Compact Length");
    }
    if (newCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
        NSLog(@"Regular Length");
    }
}

實作- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator:當width或是height的解析度改變時呼叫, 可以在這個方法中透過UIDevice來判斷裝置是橫向還是直向.

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
    if (orientation == UIDeviceOrientationLandscapeLeft) {
        NSLog(@"橫向, 頂端在左邊");
    }
    if (orientation == UIDeviceOrientationLandscapeRight) {
        NSLog(@"橫向, 頂端在右邊");
    }
    if (orientation == UIDeviceOrientationPortraitUpsideDown) {
        NSLog(@"直向但上下顛倒");
    }
    if (orientation == UIDeviceOrientationPortrait) {
        NSLog(@"直向");
    }
    if (orientation == UIDeviceOrientationUnknown) {
        NSLog(@"方向未知");
    }
}

Super

Assuming that MyClass is a subclass of BaseClass, the following happens when you call

  1. MyClass *mc = [[MyClass alloc] init];
  2. [MyClass alloc] allocates an instance of MyClass.
    The init message is sent to this instance to complete the initialization process.
    In that method, self (which is a hidden argument to all Objective-C methods) is the allocated instance from step 1.
  3. [super init] calls the superclass implementation of init with the same (hidden) self argument. (This might be the point that you understood wrongly.)
  4. In the init method of BaseClass, self is still the same instance of MyClass. This superclass init method can now either
    • Do the base initialization of self and return self, or
    • Discard self and allocate/initialize and return a different object.
  5. Back in the init method of MyClass: self = [super init] is now either
    • The MyClass object that was allocated in step 1, or
    • Something different. (That’s why one should check and use this return value.)
  6. The initialization is completed (using the self returned by the superclass init).
    So, if I understood your question correctly, the main point is that

[super init]
calls the superclass implementation of init with the self argument, which is a MyClass object, not a BaseClass object.

2016年3月29日 星期二

iOS筆記:NSURLSession

NSURLSession

NSURLSession從iOS9開始取代了Connection.

NSURLSessionTask 為抽象類別,它有三個子類別:NSURLSessionDataTask、NSURLSessionUploadTask、NSURLSessionDownloadTask 負責處理網路任務、取得JSON或XML資料,以及上傳下載文件: 範例

NSURL *url = [NSURL URLWithString:@"http://www.imdb.com/"];
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        //NSLog(@"%@", data);
        NSLog(@"%@", response);
    }];
    [dataTask resume];

HTTPS

當完成session的request並執行後發生以下錯誤:

App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app’s Info.plist file.

A: 原因是因為Applw將原HTTP協議改成了HTTPS協議,使用SSL TLS1.2加密請求數據.
iOS 9新增App Transport Security(ATS)的項目,重點是你在APP中如果有網路的要求時,其網址必定要是加密協定(https),否則連線要求會被禁止。主要是保護您的APP在進行網路通訊時一切資料都是加密,防止被嗅探。

解法:在info.plist中添加

<key>NSAppTransportSecurity</key><dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/></dict>

參考:
1. http://my.oschina.net/u/2340880/blog/618888
2. http://cms.35g.tw/coding/ios-9-xcode7-http-%E9%8C%AF%E8%AA%A4/
3. https://www.raywenderlich.com/67081/cookbook-using-nsurlsession

2016年3月6日 星期日

iOS筆記:Swift練習(5)

CoreData

提供了一種儲存資料到persistent store的方式, 可以將APP中的物件與資料庫中的表格相對映.

建立託管物件

被綁訂在CoreData的物件稱為託管物件. 它用來呈現APP的資料且存於托管物件內容中

託管物件是NSManagedObject的子類別, 代表一個Entity. 在Swift中, 我們在每一個所定義的property前面加上@NSManaged, 讓他對應Restaurant Entity的屬性(attribute).

import CoreData

class Restaurant:NSManagedObject{
    @NSManaged var name:String
    @NSManaged var type:String
    @NSManaged var location:String
    @NSManaged var phoneNumber:String?
    @NSManaged var image:NSData?
    @NSManaged var isVisited:NSNumber?
    @NSManaged var rating:String?

指定Entity給Restaurant類別
enter image description here

透過CoreData 儲存資料

  1. 從AppDelegate取得託管物件內容.
if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext { ...
        }
  1. 呼叫insertNewObjectForEntityForName來針對Restaurant Entity建立託管物件
restaurant = NSEntityDescription.insertNewObjectForEntityForName("Restaurant", inManagedObjectContext: managedObjectContext) as! Restaurant
  1. 呼叫save方法來告訴context要儲存物件
do {
       try managedObjectContext.save()
   } catch {
       print(error)
       return
       }

透過CoreData讀取資料

最基本的方式就是用託管物件內容所提供的方法來達成:

if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext {

       let fetchResultController = NSFetchedRequest(entityName: "Restaurant")
       do {
           try managedObjectContext.executefetchRequest(fetchRequest) as! [Restaurant]
       } catch {
           print(error)
       }
}

NSFetchedResultsController API

特別設計用來處理Core Data讀取後所回傳的結果, 並提供資料給表格視圖. 會監看託管物件內容中的物件變更, 然後將變更結果報告給其代理.

    1.
import CoreData

class RestaurantTableViewController: UITableViewController, NSFetchedResultsControllerDelegate 
  1. 宣告實體變數來讀取結果控制器
    var fetchResultController:NSFetchedResultsController!

  2. NSSortDescriptor可以讓你指定物件間的排序.
    當讀取結果建立完成後, 先初始化NSFetchedResultsController, 呼叫performFetch()來執行讀取結果. 完成後我們存取fetchedObjects來存取Restaurant物件. fetchedObjects回傳的事AnyObject屬性所以這邊要強制轉型!
let fetchRequest = NSFetchRequest(entityName: "Restaurant")
        let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
        fetchRequest.sortDescriptors = [sortDescriptor]

        if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext {

            fetchResultController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
            fetchResultController.delegate = self

            do {
                try fetchResultController.performFetch()
                restaurants = fetchResultController.fetchedObjects as! [Restaurant]
            } catch {
                print(error)
            }
        }

如果有任何內容變更, 以下方法會被呼叫.

// MARK: - NSFetchedResultsControllerDelegate

    func controllerWillChangeContent(controller: NSFetchedResultsController) {
        tableView.beginUpdates()
    }

    func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {

        switch type {
        case .Insert:
            if let _newIndexPath = newIndexPath {
                tableView.insertRowsAtIndexPaths([_newIndexPath], withRowAnimation: .Fade)
            }
        case .Delete:
            if let _indexPath = indexPath {
                tableView.deleteRowsAtIndexPaths([_indexPath], withRowAnimation: .Fade)
            }
        case .Update:
            if let _indexPath = indexPath {
                tableView.reloadRowsAtIndexPaths([_indexPath], withRowAnimation: .Fade)
            }

        default:
            tableView.reloadData()
        }

        restaurants = controller.fetchedObjects as! [Restaurant]
    }

    func controllerDidChangeContent(controller: NSFetchedResultsController) {
        tableView.endUpdates()
    }

第一個方法是當讀取結果控制器準備開始處理內容變更時呼叫, 我們只是告訴表格視圖 “我們要更新表格了 快準備好”.
第二個方法是當託管物件內容有任何變更時會被呼叫.
第三個方始只是用來告訴表格視圖我們已經完成更新.

刪除CoreData資料

呼叫deleteObject的方法來刪除託管物件即可.

let deleteAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Delete",handler: { (action, indexPath) -> Void in

            // Delete the row from the database
            if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext {

                let restaurantToDelete = self.fetchResultController.objectAtIndexPath(indexPath) as! Restaurant
                managedObjectContext.deleteObject(restaurantToDelete)

                do {
                    try managedObjectContext.save()
                } catch {
                    print(error)
                }
            }
        })

UISearchController

簡化了建立搜尋欄位的方法以及搜尋結果的處理. 甚至提供開發者透過自訂的動畫物件, 能更彈性的改變搜尋欄的動畫.

第二行為建立instance, 傳遞nil的值表示搜尋結果會顯示於你正在搜尋的相同視圖中.另外也可以指定其他ViewController來顯示搜尋結果.
第四行為告訴searchResultsUpdater, 哪一個物件會負責更新搜尋結果

var searchController:UISearchController!

searchController = UISearchController(searchResultsController: nil)
tableView.tableHeaderView = searchController.searchBar
searchController.searchResultsUpdater = self        

內容過濾

UISearchController在預設環境下並沒有提供任何過濾環境的功能, 必須自己負責實作內容的過濾規則.

在Swift中有一個內建方法叫filter, 是用來過濾目前的陣列. 只需要在閉包中提供過濾規則即可.
在下面的程式中, 我們使用rangeOfString來檢查是否有相對應的名稱在搜尋文字內, 若是沒有找到就會回傳nil, 最後再檢查是否有找到搜尋文字, 並回傳相對應的Bool value.

func filterContentForSearchText(searchText: String) {
        searchResults = restaurants.filter({ (restaurant:Restaurant) -> Bool in
            let nameMatch = restaurant.name.rangeOfString(searchText, options: NSStringCompareOptions.CaseInsensitiveSearch)
            return nameMatch != nil
        })
    }

更新搜尋結果

接著要實作如何在畫面上更新與顯示搜尋到的結果.

  1. 使用UISearchResultsUpdating Delegate.
  2. updateSearchResultsForSearchController 為定義在協定中的一個方法. 當使用者選取搜尋欄時或者在裡面做了些變更時會被呼叫.
func updateSearchResultsForSearchController(searchController: UISearchController) {
    if let searchText = searchController.searchBar.text {
        filterContentForSearchText(searchText)
        tableView.reloadData()
    }
}
  1. 當完成到這一步的時候, tableView會同時顯示所有的餐廳資訊以及你的搜尋結果, 所以需要透過以下的方式來變免這種狀況發生.
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if searchController.active {
            return searchResults.count
        } else {
            return restaurants.count
        }
    }

自訂SearchBar外觀

// Customize the appearance of the search bar
        searchController.searchBar.placeholder = "Search restaurants..."
        searchController.searchBar.tintColor = UIColor(red: 100.0/255.0, green: 100.0/255.0, blue: 100.0/255.0, alpha: 1.0)
        searchController.searchBar.barTintColor = UIColor(red: 240.0/255.0, green: 240.0/255.0, blue: 240.0/255.0, alpha: 0.6)

Optional Binding - if let & if var

如果無法確定變數是否為nil就貿然使用! 運算子,當該變數真的為nil時,就會發生Run-Time Error。所以當在執行Optional型別的運算的時候,最好是先判斷是否為nil值再進行運算。

var a:String = "Hello "
var b:String? = "bbb"

if b != nil {
    var c = b!
    print(a+c)
}

上面的寫法其實有些麻煩,所以Swift提供了一個語法 - 使用if let或if var。如下例所示,意思是如果b變數不是nil,則c變數等於b變數,並執行後面的statement。如果b變數是nil,則不執行該段statement。if let或if var就是所謂的Optional Binding語法

var a:String = "Hello "
var b:String? = "bbb"

if let c = b {
    print(a+c)
}