JavaScriptプログラマー(JSer)がSwiftデビューして、ただ作りたいアプリを作ってみたシリーズ第2回目です。
第1回目では作ったアプリとこの連載に関するOverviewについて紹介しました。

今回はSwiftの学習方法と基本文法を紹介します。

自分がSwiftを使ってみてJavaScriptと少し似てるなって思った点はいくつかありました。例えば以下のような点。

  • 変数の宣言
  • Eventのハンドラーの定義(delegate)
  • 変数の監視
  • クロージャ(関数をcallbackとして引数に渡すことが簡単にできる)

もちろん明示的に変数の型を宣言する必要な部分は必要であります。そこらへんはJavaと同じです。
自分はこれまでの仕事で扱ってきた言語がJavaとJavaScriptがメインだったので、イメージはJavaとJavaScriptを混ぜた言語という印象でした。

Swift ≒ (Java + JavaScript) / 2

同じような印象を持っている人はいるだろうなと思ってググってみると、誰かさんが記事を書いていました。


Swift for JavaScript developer
http://www.mircozeiss.com/swift-for-javascript-developers/

この方の説明を引用+要約すると、

  • ECMAScript6のclass定義とSwiftのclass定義は似ている
    JS

    class Person {
    
      constructor(name) {
        this.name = name;
      }
    
      speak() {
        return `Hey there, my name is ${this.name}`;
      }
    
    }
    var me = new Person('Mirco');
    me.speak();
    // Hey there, my name is Mirco
    

    Swift

    class Person {
    
      var name: String
    
      init(name: String) {
        self.name = name
      }
    
      func speak() -> String {
        return "Hey there, my name is \(self.name)"
      }
    
    }
    
    var me = Person(name: "Mirco")
    me.speak()
    // Hey there, my name is Mirco
    
  • コールバックの仕組みも似ていて、Promiseと同等のものをSwiftで書くことができる
    Swift

    func log(txt: String, #resolve: () -> (), #reject: () -> ()) {
      var delta: Int64 = 1 * Int64(NSEC_PER_SEC)
      var time = dispatch_time(DISPATCH_TIME_NOW, delta)
    
      dispatch_after(time, dispatch_get_main_queue(), {
        println("closures are " + txt)
        resolve()
      });
    }
    
  • イベントのアクションの定義の仕方が似ている
    Swift

    var txt : UITextField
    
    // add event listener
    txt.addTarget(self, action: Selector("updated"), forControlEvents: UIControlEvents.ValueChanged)
    
    // handle the event
    func updated() {
      println("the new value is \(txt.text)")
    }
    
  • オプショナルの変数をnullでないかどうかをif文でcheckする方法が似ている
    Swift

    var error: NSError?
    
    if let desc = error.description {
        println("oh noes we have an error \(desc)")
    }
    
  • Genericsを使うと同じ型の引数を引数名無しに呼ぶことができる
    Swift

    func add<T: Addable>(a: T, b: T) -> T {
      return a + b
    }
    
    add(2, 3)
    // 5
    add("hello", " world!")
    // hello world!
    

なんかJavaScript書いている人にとってはとっつきやすそうですよね。

このblogのSwiftのコードのsyntaxハイライトプラグインがSwiftに対応していなかったので、実はjavascript用として設定しています。これで割と自然にハイライトされているので、その文法が似ているということがわかります。

以下は、2週間という短期間でどのようにSwiftを学習したのか、またいくつかの概念についてJavaScriptとの対比を交えながら紹介します。


基本文法の学習

一番いいのはAppleの公式ドキュメントを読むことですが、英語だしボリュームが多くてしんどいので、以下の記事を読んでswiftの基本中の基本を一通り理解しました。

作って学ぶSwift/iOSアプリ入門
http://tech.camph.net/how-to-make-ios-app-with-swift/

何もswiftの知識ない状態から読んでいって、チュートリアルを真似て進めていくだけでアプリっぽいものを作れてしまいました。とても良記事だと思います。(現時点の最新xcode 6.2 beta3では少しコンパイルエラーになる部分もありましたが)

参考までに、xcode 6.2 beta3でチュートリアルを動くようにしたサンプルは以下に置きました。
https://github.com/tejitak/swift-sample

ということで、まずはこの記事を読んで基本を知る!


var & letの変数宣言

基本を理解した上で、let, varについて。
Swiftではvarが再代入可能な変数。letが再代入不可能な変数です。利用する側としてそれ以外の区別はなさそう(?)。

Swift

var variable = 1
let constant = "Swift"
 
variable = 0
// これはOK
 
constant = "Objective-C"
// これはコンパイルエラー

型を明示しなくても内部的に型が定義されているので、例えばIntで一度初期化したものにStringを入れるとエラーになります。

var variable = 1
variable = "Swift"
// 型が違うのでコンパイルエラー

配列やmap(JSでいうオブジェクト)の初期化は以下のように簡単にできます。

var array = [1, 2, 3, 4, 5]
var dict = [
  "apple": "りんご",
  "orange": "みかん"
]

Computed Properties

Swiftにはcomputed propertiesが存在していて、カスタムなgetter/setterを定義することができます。

Swift公式ドキュメント – Properties
https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html

例えば、Rect.centerでアクセスした時にはgetterが呼び出されます。

Swift

struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set(newCenter) {
            origin.x = newCenter.x - (size.width / 2)
            origin.y = newCenter.y - (size.height / 2)
        }
    }
}

これってどこかでみたことあるなぁと思ったら、

Ember.js – Computed Properties
http://emberjs.com/guides/object-model/computed-properties/

Vue.js – Computed Properties
http://vuejs.org/guide/computed.html

Ractive.js – Computed Properties
http://docs.ractivejs.org/latest/computed-properties

モダンなJSフレームワークで取り入れられているやつと同じですね!


変更監視

上記でモダンなJSフレームワークの例を出しましたが、最近ではAngularJSを代表にpropertyの値の変更検知の$watchみたいな仕組みはよくありますよね。

それがSwiftにもあります。変更される前に呼び出されるのがwillSet、変更された後に呼び出されるのがdidSetです。

Swift Property Observers
http://www.codingexplorer.com/swift-property-observers/

Swift

var userStatusText: String{
    willSet {
        println("About to set status to:  \(newValue)")
    }
    didSet {
        if userStatusText != oldValue {
            postNewStatusNotification()
        }
    }
}

イベントハンドラー

イベントのアクションをセットする方法も似ています。

例えばUIViewControllerを継承したクラスでbuttonを一つ設置する場合の例。

Swift

override func viewDidLoad() {
    super.viewDidLoad()
    let closeBtn = UIButton(frame: CGRectMake(0, 0, 100, 100))
    closeBtn.setTitle("Close", forState: .Normal)
    closeBtn.addTarget(self, action: "onClickClose", forControlEvents: .TouchUpInside)
    self.view.addSubview(closeBtn)
}

func onClickClose() {
    // 何か処理する
}

JSで書くと以下のような感じ。

function init() {
    var closeBtn =  document.createElement("BUTTON");
    closeBtn.innerHTML = "Close";
    closeBtn.onclick = onClickClose;
    document.body.appendChild(closeBtn);
}

function onClickClose() {
    // 何か処理する
}

似てる!!!


delegate

Swiftのdelegateという概念は自分の理解でざっくり言うと、あるイベントが起こった時に呼び出されるメソッドを明示しておき、それをdelegateしたクラスは明示されたメソッドを実装しなくてはいけないという規制をつけるものです。(Javaでいうinterfaceみたいなもの?)

JavaScriptでコンポーネントが何かクリックされた際にonClickXxxを定義しておいて、それを継承 or 使う側がそのonClickXxxを実装するというケースはよくありますよね。Swiftではそれを明示的に強制できます。

例えば、今回作ったアプリの設定パネルで使っているコードから抜粋です。
SettingViewControllerというクラスの中でUITextFieldを生成し、delegate先を自分に指定します。

そうすると、”Type ‘SettingViewController’ does not conform to protocol ‘UITextFieldDelegate'”というコンパイルエラーになります。そしたら、以下のようにUITextFieldDelegateをそのクラスがextensionで指定してメソッドの実装を行えばOKです。

Swift

class SettingViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let input = UITextField(frame: CGRectMake(0, 0, 100, 100))
        // delegate先を自分にする
        input.delegate = self
    }
}

extension SettingViewController : UITextFieldDelegate {
    func textFieldShouldReturn(textField: UITextField!) -> Bool {
        textField.resignFirstResponder()
        return true;
    }
}

クロージャ

クロージャは単に関数を変数として渡せるというだけではなく、一般的には関数定義時の外側のコンテキストを関数実行時に参照できる仕組みのことを指します。

Swiftではjavascriptとは少し異なりcallbackとして指定するfuncを全般的にクロージャと呼んでいるように思えます。

callbackの渡し方

inを使います。例えばTwitterのsearch APIを叩くときにcallbackを指定する場合。

Swift

        TwitterAPI.search(["q": "hoge"], tweets: {
            twttrs in
              // successの処理
              // callbackで渡されるデータの変数名がtwttrs

            }, error: {
                error in
                // errorの処理
                // callbackで渡されるデータの変数名がerror

        })

callbackを受け取るfuncを持つTwitterAPI側は以下のようになります。

class func search(params: [NSObject : AnyObject]!, tweets: [TWTRTweet]->(), error: (NSError) -> ()) {
   // APIリクエストして、successだったらレスポンスデータをtweets callbackに渡す, API errorの場合はerrorを呼ぶ
}

JSと違うところはやはり引数の型を明記する必要があるので、callbackを受け取る側は[TWTRTweet]->()のように入力->出力の形式で記述します。

self(this)のスコープ

例えば、tableのcellをswipeしてanimationが完了した時にcompletedというcallbackが呼ばれる際にselfを指定する例です。

Swift

        UIView.animateWithDuration(0.1, animations: {
            let size   = self.contentView.frame.size
            let origin = self.contentView.frame.origin
            self.contentView.frame = CGRect(x: origin.x - 100, y:origin.y, width:size.width, height:size.height)
            }) { completed in
                if self.contentView.frame.origin.x == -100 {
                    self.delegate?.readTweet?(self)
                }
            }

JSでcallbackが呼ばれるとthisはwindowオブジェクトになってしまいますが、Swiftでは定義した際のselfが呼ばれます。直感的ですね。


JSONの扱い

swiftはやはりJSと比べると型があるのでJSONデータへの特定プロパティへのアクセスなどはやや面倒です。(これはSwiftのFundation内にNSJSONSerializationを使った場合で、便利ライブラリを使えばもっと容易に扱えるようですが。)

例えば、twitter APIのレスポンスJSONを辿ってnilチェックする例です。

Swift

                var list: AnyObject? =  NSJSONSerialization.JSONObjectWithData(data,
                    options: nil,
                    error: &jsonError)
                if let dic = list as? NSDictionary {
                    list = dic["statuses"]
                }
                if let jsonArray = list as? NSArray {
                    tweets(TWTRTweet.tweetsWithJSONArray(jsonArray) as [TWTRTweet])
                }

その他

その他、細かな点で気になったところ。

  • Swiftには引数に名前を付与する仕組みがある
  • JSと同様にオーバロードはサポートされていない様子
  • オーバーライド内でsuperを呼び出す仕組みは、prototypeによる継承を作るJSよりJavaと同等のものと考えた方が良さそう
  • swiftの継承は多重継承できるっぽい
  • オプショナルについて
    オプショナルはnilを許容するかしないか変数の宣言時に指定する仕組みです。最初は慣れませんでしたが、アプリを一つ作り上げたところでなんとなくわかったような。。とりあえずコンパイルエラーが起きたら、!や?をつけると大丈夫ですw
  • タプルについて
    今回のアプリではタプル(a, b)みたいなやつ、は使っていないです。使うと便利そうですが、使いすぎはデータ構造の把握が大変になるので混乱しそうな予感

以上、自分もまだswift初めて2, 3週間程度の知識なので間違っているところも多数あるかもしれません。(ご指摘いただけると助かります。)

ただ、お伝えしたかったのはこんな感じでJSerがswiftを書くのは結構敷居が低いんですよ、ということです。

もしJavaの知識もあるならば、クラスの扱いは大体同じなので、オプショナル(Java8に導入されていますが)あたりがなんとなくわかれば、スムーズにコーディングが進むと思います。

もちろん、実際にアプリを作っていくためにはフレームワーク(特にUIKit)の知識が必要なので勉強は必要ですが、そこらへんは習うより慣れろ精神で、ぶつかっては理解していく戦法でアプリを作っていきました。

次回はFabricというtwitter社製のフレームワークについて紹介します〜。

TwitStockerのダウンロードはこちらから。
https://itunes.apple.com/en/app/twitstocker/id958798898?l=ja&ls=1&mt=8


Categories: Swift

Related Posts

Swift

Fabric + MoPubでバナー広告を設置する

今回の記事はいつも連載している「JavaScriptプログラマがSwift iOSアプリを2週間で作って公開してみた」のリリース後に行った広告設置について番外編として紹介します。

Swift

JavaScriptプログラマがSwift iOSアプリを2週間で作って公開してみた〜その18 NSDate〜

JavaScriptプログラマー(JSer)がSwiftデビューして、ただ作りたいアプリを作ってみたシリーズ第18回目です。 前回は設定パネルに設置したフォーム部品からデータを取り出しアプリ内で永続的に保存する例を紹介しました。今回は、保存したデータをNSDateを使った日付の比較をして消す処理について紹介します。

Swift

JavaScriptプログラマがSwift iOSアプリを2週間で作って公開してみた〜その8 UITableViewCell Swipe Action〜

JavaScriptプログラマー(JSer)がSwiftデビューして、ただ作りたいアプリを作ってみたシリーズ第8回目です。 前回はFabric(Twitter API)を使用して、ResponseをUITableViewとして表示する部分の実装について紹介しました。今回は表示したCellに左スワイプと右スワイプを可能にして、カスタムなアクションを加えていく実装方法について紹介します。