【Swift x Ruby】iOS端末とRuby on RailsサーバでHTTP通信

前回（[【Swift x PHP】iOS端末とPHPサーバでHTTP通信](http://qiita.com/AkkeyLab/items/ae9b43d3c49b006d5569)）のRuby対応版になります。
サーバ側の処理言語としても人気なRuby on Railsに対応させていきましょう！
なんだか[今日](https://ja.wikipedia.org/wiki/バレンタインデー)はハッピーです！Let's try！！

# 対象
Ruby on Railsの動作環境構築とMVC構成を理解していると良いでしょう。
また、iOS開発に関しても経験があるといいですが、今回作成するアプリを[GitHub](https://github.com/AkkeyLab/SendMessage)に上げているので未経験者でも始められると思います。

# 手順
1. お好みのサーバにRuby on Rails環境を構築
2. Install xcode
3. Clone project file from GitHub
4. Execute

# 1. Rails server
タイトルにRubyとありますが、フレームワークであるRuby on Railsを使っていきます。
Railsの雛形を作ったら、Controllerに`params`を使って、URLに含まれるパラメータを受け取る処理を記述します。出力には`render`を使います。

> **render**
> 指定したRHTMLを返す
> :text	任意のテキストを指定して表示

[Railsドキュメント](http://railsdoc.com/references/render)

> **params**
> URLから送られてきた値やフォームで入力した値をparams[:パラメータ名]で取得する

[Railsドキュメント](http://railsdoc.com/references/params)

```ruby:app/controllers/main_controller.rb
class MainController < ApplicationController
  def index
    render :text => "#{params[:title]} : #{params[:note]}"
  end
end
```

次に、上記のControllerが呼び出されるようにルーティングの設定を行います。
Rubyは小文字のファイル名、大文字使えるクラス名といった感じのお約束があります。それに従って`Controller名#メソッド名`という書式でrootに設定しましょう。

```ruby:config/routes.rb
Rails.application.routes.draw do
  root 'main_controller#index'
end
```

ここまで出来たら、Rails serverを起動させて以下のようにアクセスしてみましょう。titleパラメータに渡した文字列が表示されたら成功です。
`http://192.168.22.2:3000/?title=ありがとう！&note=これからもよろしくね！`
※IPアドレスは臨機応変に変更してください。

# 2. Install Xcode
Swift3とSwift2のコードを準備しているので準備するXcodeもお好きな方をご準備ください。
Androidほど開発環境構築に時間がかかることはないと思います。

# 3. iOS App Development
※Serverへの接続先が`192.168.2.22`であるものとして記述しているので、臨機応変に変えてください（[GitHub](https://github.com/AkkeyLab/SendMessage)のコードも同様です）

まず、iOS9以降でセキュリティ的に問題のあるHTTP通信が禁止されているので例外で許可させておきましょう。HTTPSで通信できるようにサーバを構築した場合はこの作業は必要ありません。

```xml:Info.plist
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
    <key>NSExceptionDomains</key>
    <dict>
        <key>192.168.2.22</key>
        <dict>
            <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
            <false/>
        </dict>
    </dict>
</dict>
```
---

次に、アプリの処理を実際に書いていきます。
`clickButton`メソッド内に通信処理を書いています。
ユーザにTextFieldへ送信内容を入力してもらい、その内容を含めたURLでアクセスし、結果を取得・表示するといった流れを記述しています。
UIはお好みで！`@IBOutlet`となっている部分がStoryboardとリンクさせてる部分です。

**Swift2 version**

```swift:ViewController.swift
import UIKit

class ViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var resultLabel: UILabel!
    @IBOutlet weak var inputText1: UITextField!
    @IBOutlet weak var inputText2: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        inputText1.delegate = self
        inputText2.delegate = self
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    @IBAction func clickButton(sender: AnyObject) {
        if inputText1.text != "" && inputText2.text != "" {
            let title = inputText1.text!
            let note  = inputText2.text!

            // 接続先のURLにパラメータを追加
            let stringUrl = "http://192.168.2.22:3000/?title=\(title)&note=\(note)"
            // 文字列のURLからNSURLに変換
            let URL = NSURL(string: stringUrl.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!)!
            // NSURLを引数にRequestの生成
            let req = NSURLRequest(URL: URL)
            
            // ここからが実際に通信をする所（非同期通信）
            let task = NSURLSession.sharedSession().dataTaskWithRequest(req, completionHandler: {
                (data, res, err) in
                // サーバから出力はありますか？
                if data != nil {
                    // あるみたいだから文字列に変換しておく
                    let text = NSString(data: data!, encoding: NSUTF8StringEncoding)
                    // 非同期通信内でUIに変更を加えるときのおまじないと思って！
                    dispatch_async(dispatch_get_main_queue(), {
                        self.resultLabel.text = text as String?
                    })
                }else{
                    // サーバから返事がないみたいだからエラー表示
                    dispatch_async(dispatch_get_main_queue(), {
                        self.resultLabel.text = "ERROR"
                    })
                }
            })
            task.resume()
        }
    }

    func textFieldShouldReturn(textField: UITextField) -> Bool{
        //Close keyboard.
        textField.resignFirstResponder()
        
        return true
    }
}
```

**Swift3 version**
Swift3に対応するとともにアラートも追加してみました。
コードの解説はSwift2と変わらないところは省略しています。

```swift:ViewController.swift
import UIKit

class ViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var resultLabel: UILabel!
    @IBOutlet weak var inputText1: UITextField!
    @IBOutlet weak var inputText2: UITextField!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        inputText1.delegate = self
        inputText2.delegate = self
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    @IBAction func clickButton(_ sender: AnyObject) {
        if inputText1.text != "" && inputText2.text != "" {
            let title = inputText1.text!
            let note  = inputText2.text!

            let stringUrl = "http://192.168.2.22:3000/?title=\(title)&note=\(note)"
            // Swift3からはNSがなくなってURLになりました
            let url = URL(string: stringUrl.addingPercentEscapes(using: String.Encoding.utf8)!)!
            // これもNSがなくなりました
            let req = URLRequest(url: url)
            
            // ここもNSがなくなりました
            let task = URLSession.shared.dataTask(with: req, completionHandler: {
                (data, res, err) in
                if data != nil {
                    let text = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
                    DispatchQueue.main.async(execute: {
                        self.resultLabel.text = text as String?
                    })
                }else{
                    DispatchQueue.main.async(execute: {
                        self.resultLabel.text = "ERROR"
                    })
                }
            })
            task.resume()
        }else{
            // 未入力なのにサーバにアクセスしようとしたら怒るようにした
            alert("error", messageString: "It is not entered.", buttonString: "OK")
        }
    }
    
    // 標準のアラートを表示させる
    func alert(_ titleString: String, messageString: String, buttonString: String){
        //Create UIAlertController
        let alert: UIAlertController = UIAlertController(title: titleString, message: messageString, preferredStyle: .alert)
        //Create action
        let action = UIAlertAction(title: buttonString, style: .default) { action in
            NSLog("\(titleString):Push button!")
        }
        //Add action
        alert.addAction(action)
        //Start
        present(alert, animated: true, completion: nil)
    }

    func textFieldShouldReturn(_ textField: UITextField) -> Bool{
        //Close keyboard.
        textField.resignFirstResponder()
        
        return true
    }
}
```

# 4. Execute
上部に送信した内容がフォーマットされて返ってきているのがわかると思います。
サンプルはPHPバージョンにも対応しています。
[【Swift x PHP】iOS端末とPHPサーバでHTTP通信(Qiita)](http://qiita.com/AkkeyLab/items/ae9b43d3c49b006d5569)

<img src="https://qiita-image-store.s3.amazonaws.com/0/128344/846b39f4-93b0-f9f6-fb00-4be610874768.png" alt="結果" title="結果" with="300" height="500" />

# 最後に
Rubyっていいですよね
プログラミング言語としても、宝石としても

サーバ構築とアプリ開発を別の人が担当して共同開発するのもいいですね。きっと面白くなります！！


以下がiOS側のサンプルです。
[GitHub | AkkeyLab/SendMessage](https://github.com/AkkeyLab/SendMessage)