タグ別アーカイブ: Swift

JSONパーサー Unbox をデータクラスを定義せずに使用する方法

Unboxは便利ですね〜
結構便利でswiftでjson扱う時はこればっかり使っています。
WEBで見てもユーザがどんどん増えている気がしますね。

さて、そんな便利なUnboxですが決まったデータクラスが存在しない時の扱いについてサンプルが少ないように感じました。
特にマップを要素に持つjson配列の扱いにちょっと迷いましたのでここにメモっておきます。

ポイントは performCustomUnboxing*** 系のメソッドを使う事と、 UnboxableDictionary を使う事です。



/*  
jsonStringには下記のjsonが入っているとして

{
   "storename":"金沢店",
   "stocks":[
      {"item":"白菜","stock":12},
      {"item":"甘エビ","stock":439}
   ]
}
*/

if let data = jsonString.dataUsingEncoding(NSUTF8StringEncoding) {           
  do {
                
    // performCustomUnboxing*** を使うのがポイント
                
    let result = try Unboxer.performCustomUnboxingWithData(data) { (unboxer) -> String in
                    
      // ルートの"storename"を取得
      let storename = unboxer.unbox("storename") ?? "unknown store"
                    
      // マップを含むjson配列はUnboxableDictionaryの配列として取得する
      let stockDictArray:[UnboxableDictionary] = unboxer.unbox("stocks")
                    
      // 取得したjson配列を performCustomUnboxingWithArray でパース
      let stocks = try Unboxer.performCustomUnboxingWithArray(stockDictArray) { (unboxer) -> (String,Int) in
                      
          let item = unboxer.unbox("item") ?? ""
          let stock = unboxer.unbox("stock") ?? 0
                        
          // 結果をtupleで返却
          return (item,stock)
      }
                    
      // パースした結果を文字列にして返却
      var caption = "storename:\(storename) \n"
      for stock in stocks {
        caption += " item:\(stock.0) stock:\(stock.1) \n"
      }
      return caption
    }
                
    print(result)
                
  } catch let error as NSError {
    print(error)
  }
}  

/*
[output]  
         
storename:金沢店  
item:白菜 stock:12  
item:甘エビ stock:439  
*/

MKMapViewのコンパスを移動させる

MapViewを回転(方角を変更)すると右上にコンパスが表示されます。
こんなの↓
20150727_1

右上に表示されるもんですから画面上部にツールバーをフロートで表示させようとした時などにとっても邪魔!
しかもこれ、iOS9だと消せるらしいのですがiOS8だと消せないんです(たぶん)。
で、移動させちゃえって思ったのですが、そんなプロパティ見つかりませんし、、

ただ、一部のアプリでは標準と違う場所に表示されているので何かしら方法があるのではないかと思って色々調べていたらstackoverflowでこんな情報が見つかりました。

http://stackoverflow.com/questions/18903808/ios7-compass-in-mapview-placing

簡単に説明すると

  • MKMapviewはUILayoutSupportが示す位置を参考にしてコンパスとか著作権情報などの表示位置を決めている。
  • で、このUILayoutSupportってのはViewControllerのtopLayoutGuideとかbottomLayoutGuideで決める事ができる。
  • なので、いい感じの位置情報を返すUILayoutSupportを、topLayoutGuideとかbottomLayoutGuideで返せばOK!

 
 
実際のコードはこんな感じで

最初に UILayoutSupport を実装したクラスを定義します

class MapLayoutGuide: NSObject,UILayoutSupport {
    
    private var _length:CGFloat = 0;
    
    init(length:CGFloat){
        super.init();
        _length = length;
    }
    
    var length: CGFloat {
        get {
            return _length
        }
    }

    @available(iOS 9.0, *)
    var topAnchor: NSLayoutYAxisAnchor {
        return NSLayoutYAxisAnchor()
    }
    
    @available(iOS 9.0, *)
    var bottomAnchor: NSLayoutYAxisAnchor {
        return NSLayoutYAxisAnchor()
    }

    @available(iOS 9.0, *)
    var heightAnchor: NSLayoutDimension {
        return NSLayoutDimension()
    }
}

で、これを ViewController の topLayoutGuide で返すようにオーバーライド

override var topLayoutGuide: UILayoutSupport {
   get {
      return MapLayoutGuide(length: 50);
    }
}

するとコンパスが下図のように移動してくれます。

20150727_2

(2015.09.15) iOS9向けのコードを追加しました

[Swift] Rangeの罠

RangeのendIndexと聞いてどのような意味を思い浮かべるでしょうか?
0から4のRangeはどの範囲を示しているでしょうか?

今回はRangeのendIndexについて説明します。

forループする時に下記のように記述すると思います

for i in 0..<4 {
  // 0〜3までループ
} 

この時 0..<4は Arange<Int>を示しており、下記のrangeと同じ意味です
let range = Range<Int>(start:0,end:4)
※ ちなみに 0…4であれば Range<Int>(start:0,end:5) です。

このrangeのstartIndexとendIndexを調べると下記のようにイニシャライザで指定した値がそのまま入っています。

println("range \(range.startIndex)〜\(range.endIndex) ")
// range 0〜4

もちろんこのように生成したrangeをfor-inでループしてもリテラルで宣言したものと同様に
処理されます。

for i in range {
  // 0〜3までループ
}

で、何が言いたいのかと言うと、、
endIndexが4ならループ変数 i が4まで実行させるべきではないかと。
そのほうが分かりやすいよね。
javaのString.substringのendIndexと同じって思えば良いんだけど、そもそもjavaのそれも気に入らないので。。

 

百歩ゆずってループで使う場合にはこの仕様で良いとシヨウ(仕様と掛かってます/ドヤ)
でも何かしらの範囲を示す場合に混乱しますよね。
例えばこのような関数があったとして

// ティーンエイジの範囲を求める
func teenagerRange() -> Range<Int> {
  // ティーンって13才から19才だよね
  return Range<Int>(start:13,end:19)
}

この関数の戻り値を使ってティーンエイジャーを列挙すると

for age in teenagerRange() {
  print("age \(age)")
}

・・13から18でループされる、ナインティーンどこいった?

 
 

んー、まぁ1万歩ゆずって整数ならまだ良しとしよう。
DoubleとかでもRangeを作れるので

let range = Range<Double>(start:0.0,end:1.0) 

この場合、最終の値はなに?
0.9? 0.999999?

→ ForwardIndexTypeに準拠しないためDoubleのRangeは使えなくなりました。

もちろん (range.startIndex <= X && X < range.endIndex) という条件が正の場合とすれば良いのは分かるし、
この仕様の方がフィットする型があるのもわかります。(日時とかね)
でも 0 〜 0.9(just)までを示す事が出来ないと思いませんか?

Rangeってからには範囲を示すもので汎用的に使いたいんですけど、なんか使いづらい!
というか、メソッドによってRangeの意味が変わってしまう気がして気持ち悪い!
上限値、下限値を範囲に含めるか否かを示す値があれば良いんだけど、これが無いからだよね。
これは要注意だよ!

(2014-07-11) XCode6.0-beta3での仕様変更を反映

[Swift] Sequence実装のサンプル(独自クラスのリスト)

SequenceTypeを実装することによって独自クラスをfor-inなどでループ処理することが可能になります。

 
SequenceTypeプロトコルは
func generate() -> GeneratorType
だけを実装すれば良いのですが、
今回のサンプルでは二分探索(バイナリサーチ)によるジェネリックスなSortedListを実装してみました。
 
 

使い方はこんな感じです

// イニシャライザで比較関数を渡す
// この例ではクロージャを使っています
let sortedList = SortedList<Int>(){
    (left:Int,right:Int)-> CompareResult in
        if (left == right) {
            return .Equal
        } else if (left > right) {
            return CompareResult.LeftLarge
        }
        else {
            return CompareResult.RightLarge
        }
    }

// 要素を追加
sortedList.append(7)
sortedList.append(5)
sortedList.append(3)

// 配列からも追加
sortedList.append([1,2,9])

// for-inループで要素を列挙
// Sequenceプロトコルが使われています
for elm in sortedList {    
	println("elm:\(elm)")
}
// 追加した要素がソートされて出力されます 1,2,3,5,7,9 


// 要素のインデックスを求める
if let idx = sortedList.indexOf(4) {
	println("found index=\(idx)")
} else {
	println("not found.")
}

 
 

以下ソース

// 要素の比較結果を示すEnum
enum CompareResult {
    case Equal,LeftLarge,RightLarge
}

// SortedListの実装
class SortedList<T>:SequenceType {
    
    // データの保存先 (実装されたらprotectedにするよ)
    var data = Array<T>()
    
    // 比較関数 (実装されたらprotectedにするよ)
    var compFunc:(left:T,right:T) -> CompareResult
    
    
    // イニシャライザ
    init(f:(left:T,right:T)-> CompareResult){
        self.compFunc = f
    }
    
    convenience init(_ initialData:Array<T>, f:(left:T,right:T)-> CompareResult){
        self.init(f:f)
        self.append(initialData)
    }
    
    // Sequence.generateの実装
    // これを実装することでfor-inで反復処理できるようになるよ
    func generate() -> IndexingGenerator<T[]> {
    	// Arrayの同機能を使って簡単に実装してますがカスタムについては後述
        return self.data.generate()
    }
    

    // ここからはArrayに実装されている機能を外にインターフェース

    subscript(index: Int) -> T {
        get {
            return data[index]
        }
        set {
            data[index] = newValue
        }
    }
    
    subscript(range: Range<Int>) -> ArraySlice<T> {
        get {
            return data[range]
        }
        set {
            data[range] = newValue
        }
    }
    
    // 要素を追加する
    // 追加されたIndexを返す
    // すでに同じ値が登録されていた場合にはNoneを返す
    func append(newElement:T) -> Int? {
        if (self.data.isEmpty){
            self.data += newElement
            return 0
        } else {
            let (hit,idx) =  _insertPos(newElement)
            if hit {
				// 上書きするのであればここで
                return .None
            } else {
                data.insert(newElement,atIndex:idx)
                return idx;
            }
        }
    }
    
    // 要素の一括登録
    func append(newElements:Array<T>) {
        let rate = self.data.isEmpty ? 1.0 : Double(newElements.count) / Double(self.data.count)
        if rate > 0.5 {
            //総量の5割を超えるレコードを追加する場合は一括でappendしてあとでソートする
            data += newElements
            _sortData();
            
        } else {
            self.data.reserveCapacity(self.data.count + newElements.count)
            for elm in newElements{
                self.append(elm)
            }
        }
    }    
    
    func append(newElements:SortedList<T>){
        self.append(newElements.array)
    }
    
    func removeAtIndex(index: Int) -> T {
        return data.removeAtIndex(index)
    }

    func removeLast() -> T {
        return data.removeLast()
    }
    
    func removeAll(keepCapacity: Bool = false) {
        data.removeAll(keepCapacity: keepCapacity)
    }
    
    // 要素を削除する
    // 戻り値:削除した要素のIndexを返す
    func remove(elm:T) -> Int? {
        let (hit,idx) =  _search(elm)
        if (hit){
            data.removeAtIndex(idx)
            return idx
        } else {
            return Optional.None
        }
    }

    var array:Array<T> {
        get{
            return self.data
        }
    }
    
    
    var count: Int {
    	get {
        	return data.count
    	}
    }
    
    var isEmpty: Bool {
    	get {
        	return data.isEmpty
    	}
    }
    
    var capacity: Int {
    	get {
        	return data.capacity
    	}
    }
    
    func reserveCapacity(minimumCapacity: Int) {
        data.reserveCapacity(minimumCapacity)
    }

    func filter(includeElement: (T) -> Bool) -> SortedList<T> {
        return SortedList(data.filter(includeElement),f:self.compFunc)
    }
    
    // (mapは比較関数も変わってしまうので実装しないことにしよう..)


    func reduce<U>(initial: U, combine: (U, T) -> U) -> U {
        return data.reduce(initial, combine)
    }
    

    // ここからは独自の機能
    
    // 指定された要素を検索してデータのインデックスを求める
    func indexOf(elm:T) -> Int? {
        let (hit,idx) =  _search(elm)
        return hit ? idx : Optional.None
    }
    
    
    // 指定したインデックスの範囲の要素をもったSortedListを返却する
    func sublist(range:Range<Int>) -> SortedList<T> {
        let subdata = data[range]
        return SortedList(Array(subdata),f:self.compFunc)
    }


 	// ここからはprivate or protectedにしたいな(実装されれば)
    
    
    // 要素の位置を検索する
    // 戻り値
    //  .0:存在したか
    //  .1:Hitした場所 or 近かった場所(前後のどちらか)
    func _search(elm:T) -> (Bool,Int) {
    
        var l = 0
        var r = self.data.count-1      
        
        while l < r {
            let m = (l + r) / 2;
            
            switch self.compFunc(left:elm,right:data[m]) {
            case .LeftLarge://右を探索
                l = m + 1
            case .RightLarge://左を探索
                r = m - 1
            case .Equal:
                return (true,m)
            }
        }
        
        if self.compFunc(left:elm,right:data[l]) == .Equal {
            return (true,l)
        } else {
            return (false,l)
        }
    }
    
    // 要素をインサートする場所を見つける
    func _insertPos(elm:T) -> (Bool,Int){
        
        let (hit,idx) =  _search(elm)
        if (hit) {
            return (hit,idx);
        }
        else {
            switch self.compFunc(left:elm,right:self.data[idx]) {
            case .LeftLarge:
                //追加する要素が大きい・・・idxの後にインサート
                return (false,idx+1)
            default:
                return (false,idx)
            }
        }
    }
    
    // 一括でソートする
    func _sortData() {
        sort(data){
            (o1 : T, o2 : T) -> Bool in
            return self.compFunc(left:o1,right:o2) == .RightLarge
        }
    }
}

C#のSortedListのように使用する場合にはジェネリックス型としてタプルを使えばよいかと。
 
 

今回行ったSequenceTypeプロトコルの実装はArrayの同機能を使って簡単に実装したのですが、カスタムしたい場合には下記のようにします。 
 
方法1. GeneratorOf を使う場合

// generateを下記のようにすればループする順序をカスタマイズできます
// ※カスタマイズしたらSortedListでは無くなってしまいますが。
func generate() -> GeneratorOf<T> {
    var index : Int = 0
    return GeneratorOf<T> {
        if (index < self.data.count){
            return self.data[index++]
        } else {
            return .None
        }
    }
}

 

方法2. 独自のGeneratorを実装する場合

// こんな感じでGeneratorを定義して
class SortedListGenerator<T> : Generator {
    let list : SortedList<T>
    var index : Int
    
    init(_ list: SortedList<T>) {
        self.list = list
        index = 0
    }
    
    mutating func next() -> T? {
        if index >= list.count { return .None }
        return list[index++]
    }
}

// 独自クラスのgenerate()でこのインスタンスを返すようにする
func generate() -> SortedListGenerator<T> {
    return SortedListGenerator(self)
}

 
 

今回のサンプルではSequenceTypeプロトコル以外の機能も実装したのでちょっと長いですが、
SequenceTypeプロトコル自体は簡単に実装できます。
独自クラスを反復処理する場合にはぜひ実装してみて下さい。

[Swift] 演算子のオーバーロード

Swiftでは演算子をオーバーロード可能です。

下記のクラスOkozukaiに幾つかの演算子を定義してみます。

// お小遣いクラス
class Okozukai {
    var okane = 0
    init(okane:Int){
        self.okane = okane
    }
}

 
C = A + B のような演算子を定義する時は @infix を付けて宣言します
※演算子のオーバーロードは全てFileスコープ(classとかの外側、グローバル)に宣言する必要があります。

// +(プラス)を宣言
func + (left: Okozukai, right: Okozukai) -> Okozukai {
    return Okozukai(okane: left.okane + right.okane)
}

// == と !=演算子 
func == (left: Okozukai, right: Okozukai) ->  Bool {
    return left.okane == right.okane
}
// != は明示的に宣言しなきゃダメみたい(==から逆判定はしてくれない)
func != (left: Okozukai, right: Okozukai) ->  Bool {
    return left.okane != right.okane
}

このように宣言すると下記のようにOkozukaiクラスが計算できます

let kozukaiA = Okozukai(okane:1000)
let kozukaiB = Okozukai(okane:2000)

var kozukaiC = kozukaiA + kozukaiB

if (kozukaiC == Okozukai(okane:3000)){
   NSLog("kozukaiCは3000円")
}

 

A += B のような演算子を定義する時は @assignment を付けて宣言します
第1引数がinoutなので注意

// "+="を宣言
func += (inout left: Okozukai, right: Okozukai) {
    left = left + right
}
// オーバーロードも出来る
func += (inout left: Okozukai, right: Int) {
    left = left + Okozukai(okane:right)
}

let kozukaiA = Okozukai(okane:1000)
kozukaiA += 1000
kozukaiA += Okozukai(okane:500)
NSLog("小遣い:\(kozukaiA.okane)")
// 小遣い:2500

 

prefixを指定すると -a のような演算子を宣言できます。
※同様に postfixだと a- のような演算子

prefix func - (oz:Okozukai) -> Okozukai{
    return Okozukai(okane:oz.okane * -1)
}

let kozukaiA = Okozukai(okane:1000)
let minusOkozukai = -kozukaiA
NSLog("小遣い:\(minusOkozukai.okane)")
// 小遣い:-1000

 

++a のような自身に対しての演算を行う場合

prefix func ++ (inout okozukai:Okozukai) -> Okozukai{
    okozukai.okane += 1000
    return okozukai
}

let kozukaiA = Okozukai(okane:1000)
++kozukaiA
NSLog("小遣い:\(kozukaiA.okane)")
// 小遣い:2000

(2014-08-26) beta5で色々変わっていたのでサンプルコードを修正

[Swift] クロージャ(Closures)のサンプル

objective-cのblocksに変わるものとしてSwiftではモダンなクロージャが導入されています。

 

簡単な例から


var closure = {(valueMap:Dictionary<String,Int>) -> Int in
    var total = 0
    for p in valueMap.values {
        total += p
    }
    return total
}

解説すると
dictionary を引数として Int を返すクロージャを宣言して 変数”closure”に格納しています。
なんだか構文がややこしいように見えますが”in”の前にパラメータ・戻り値、”in”の後に実装を書き、全体を{・・・}で囲っているだけです。
こんな構造
{ (引数) -> (戻り値) in
//実装
}
blocksに比べると随分と分かりやすい構文だと思います。

この変数に格納されたクロージャを使うときは


var populationDict:Dictionary<String,Int>  = [
    "kanazawa":10,
    "komatsu":100000,
    "nanao":50000
]

let population = closure(populationDict)

このように普通の関数呼び出しと同様に使えます。

 
 

呼び出し時にメソッドの型をコンパイラが分かっていたら
こんな感じでパラメータ、戻り値を省略してクロージャを記述できる


// クロージャを受け取る関数を宣言
func logCalcResult(calcFunc:(Double,Double) -> Double ,v1:Double,v2:Double) {
    // v1,v2をパラメータとして受け取った関数(calcFunc)を実行
    let calcResult = calcFunc(v1,v2)
    NSLog("calcResult \(calcResult)")
}

// パラメータ、戻り値を省略したクロージャをインラインで宣言してます
// ※{・・・}の部分
// 引き渡されるパラメータ $0、$1、・・・ って名前で参照
logCalcResult({ return $0 + $1} ,20 ,30)

 

trailing closure って機能も使えます
関数の最後のパラメータがクロージャだった時、引数ブロックの外側にブロックを宣言してクロージャを渡せる
(説明がわかりにくい!)


// こんなclosureを受け取る関数があったとして
func logText(closure:() -> String){
    NSLog(closure())
}

// 呼び出しの "()"の外側に { ... } を宣言してクロージャとして渡せる
logText(){
    return "trailing クロージャ!!"
}

上手く使うとカッコ良くかけますね。
 
 

クロージャなので”Capturing Values”も行われます。
※要するにクロージャが宣言された”環境”への参照をクロージャが保持しちゃう機能

たとえばこんな感じでクロージャを返す関数を用意して


// 月から日までを列挙するクロージャを返す関数
func weekNameEnumBuilder() -> () -> String {
    var weekday = -1
    let names = ["月","火","水","木","金","土","日"]
    
    return { () -> String in
    	//キャプチャしたweekdayをクロージャ内でインクリメント
        weekday++
        if (weekday == names.count) {
            weekday = 0
        }
        return names[weekday]
    }

}

このように実行するとweekdayがキャプチャされている事がわかりますね。


//キャプチャする値はweekNameEnumBuilderの実行時に個別に取得される。
//下記のように2回呼び出すとそれぞれ別の変数(weekday)をキャプチャしていることが分かる
let weekNamesA = weekNameEnumBuilder()
let weekNamesB = weekNameEnumBuilder()

NSLog("Capturing A-1: %@" , weekNamesA())
NSLog("Capturing A-2: %@" , weekNamesA())
NSLog("Capturing B-1: %@" , weekNamesB())
NSLog("Capturing B-2: %@" , weekNamesB())
NSLog("Capturing A-3: %@" , weekNamesA())
//  Capturing A-1: 月
//  Capturing A-2: 火
//  Capturing B-1: 月
//  Capturing B-2: 火
//  Capturing A-3: 水

 
  

注意しなければならない事が1点。
クロージャー内でselfとかの参照をしてしまうと selfからクロージャーへの参照とクロージャーからselfへの参照が発生し、循環参照となってしまうというobjective-cではおなじみの現象がswiftでも発生します。
この問題への解決方法としてもobjective-cと同じように弱参照を行うことになるのですが、Swiftのクロージャではキャプチャリスト(Capture List)という形でクロージャ宣言の直前にキャプチャする対象の参照方式を指定することができます。

下記サンプルの [unowned self] ってのがそれで、selfをunownedで参照するように指定しています。


class Doraemon {
    
    var poket = Dictionary<String,AnyObject>()
    
    init(){
        poket["タケコプター"] = "Takekoputer 9-inch,Late 2013"
    }

    // 引数にstringを受け取り、stringを返すクロージャを生成・返却する
    func checkPocket() -> (String) -> String {
    
        return {
            // キャプチャリストは  [参照方式 対象,参照方式 対象 ・・・] という形で記述する
            // この例では self を unowned で参照
            [unowned self] (itemname:String) -> String in
            
            if self.poket[itemname] {
                return "\(itemname)は持ってるよ。僕ドラえもん。"
            } else {
                return "\(itemname)は今修理中。"
            }
        }
    }
}

    
let dora = Doraemon()
let checkPocket = dora.checkPocket()
NSLog(checkPocket("どこでもドア"))
NSLog(checkPocket("タケコプター"))
// どこでもドアは今修理中。
// タケコプターは持ってるよ。僕ドラえもん。

 
 
 
あと、クロージャや関数を変数に格納する場合が多々あると思います。
その宣言方法をメモっておきます。

// クロージャ、関数を変数に格納する場合の宣言
var funcA:(String) -> (String)

funcA = {
    (a:String) -> String in
    return "hello \(a)"
}

// 実行
funcA("マサオ")


// オプショナル(Optional)だった場合
// 全体をカッコで囲ってオプショナルの指定を行う
var funcB:((String) -> (String))?

funcB = {
    (b:String) -> String in
    return "hello \(b)"
}

// 実行する時はこんな感じ
funcB!("タテオ")


// 戻り値が無かった場合
var funcC:(String) -> ()

funcC = {
    (c:String) in
    println("hello \(c)")
}

funcC("ヒロシ")

[Swift] ジェネリックス(Generics)のサンプル

objective-cで何で使えないんだよ、、、と常々思っていたGenericsもSwiftではもちろん使えます。
最近の開発言語では当たり前の機能ですね。

 

ジェネリックスな関数のサンプル

// 引数で与えられた要素を持ったSwift配列を返す
func toArray<T>(values:T...) -> Array<T> {
    var result = Array<T>()
    for v in values {
        result += v
    }
    return result
}


// こんなふうに呼び出すことが可能
// うーん、ジェネリックス!
let intArray = toArray(1,2,3,4,5,6,7)
let stringArray = toArray("A","B","C")

 
 

ジェネリックスなクラスのサンプル

// IntをキーとしたDictionaryを生成するBuilderクラス
// appendで要素を追加してbuildで作成
class IndexedDictionaryBuilder<T> {
    
    var array = Array<T>()
    
    func append(elm:T){
        array.append(elm)
    }
    
    func build() -> Dictionary<Int,T> {
        var result = Dictionary<Int,T>()
        for (index,elm) in enumerate(array) {
            result[index] = elm
        }
        return result
    }
}


// 使い方
// コンストラクタで型を指定
let builder = IndexedDictionaryBuilder<String>()
builder.append("アガシジィ")
builder.append("ビタエニアータ")
builder.append("エリザベサエ")

// buildするとコンストラクタで指定した型を値に持ったdictionaryを生成する    
let dict:Dictionary<Int,String> = builder.build()
for (key,value) in dict {
    NSLog("第\(key)位 \(value)")
}
// 第1位 アガシジィ
// 第2位 ビタエニアータ
// 第3位 エリザベサエ

 
構文も素直で使いやすいですね。

[Swift] Tuple(タプル)の使い方

タプルって馴染みの無いかたには何ぞや?というものですが、ScalaやPythonなどで多用する超ベンリな値です。
一言で言うと
 複数の値を持った構造体
なんです。
え?そんだけ?なんですけど、
メソッドの戻り値などで使うと今までシコシコ専用のResultクラスを作ったり、みっともなく配列で返したりしてたのがスッキリ解決!

宣言方法

// Tupleはジェネリック型なので下記の宣言だと <String,String,Int> なタプルとなります。
let tupleValue = ("お前の","カーチャン",67)

 
 
この要素へのアクセス方法いろいろ

// tupleの要素を取得する方法で簡単なのはインデックスを指定することだと思う
NSLog("\(tupleValue.0)\(tupleValue.1)\(tupleValue.2)才!!")
//・・ お前のカーチャン67才!!


// まとめて変数に取り出すこともできる
let (word1,word2,age) = tupleValue
NSLog("\(word1)\(word2)\(age)才ですか?")
//・・ お前のカーチャン67才ですか?


// 同様の書き方で必要な項目だけ取り出す場合
let (_,_,motherage) = tupleValue
NSLog("かーちゃん\(motherage)才")

 

SwiftのTupleは要素に名前を付ける事ができるんです。
わかりやすくなりますね。

// Swiftのtupleには要素に名前をつけることができる!
let comic = (title:"にんにくマン",volume:1)

// その場合、名前で参照することができて便利!
NSLog("漫画(A):%@ vol.%d",comic.title,comic.volume)
//・・ 漫画(A):にんにくマン vol.1


// もちろん普通に取り出すことも可能
NSLog("漫画(B):%@ vol.%d",comic.0,comic.1)

 

応用編としてswitchで条件分岐

//switchでの使い方
let gokuu = (name:"悟空",taillength:97)
let vegita = (name:"ベジータ",taillength:105)
let frieza = (name:"フリーザ",taillength:0)

for fighter in [gokuu,vegita,frieza] {
	// 尻尾の長さで判定
    switch fighter {
    case (let name,0):
        NSLog("\(name)、あなたはサイヤ人ではありませんね。。。")
    case (let name,let taillen) where taillen > 100:
        NSLog("\(name)、あなたはサイヤ人の王族ですね!!")
    case (let name,let taillen):
        NSLog("\(name)、あなたは普通のサイヤ人ですね?")
    }
}

 
簡単な複合値であればわざわざクラスを作る必要が無くなってコーディングスピードUP間違い無しです。

[Swift] Optionalの使い方

C#でのNullable、scalaのOptionのようなnull値(nil値)を許容する変数を示します。

いきなりですが、下記のコードはコンパイルできるでしょうか?

var text:String = nil

javaやobjective-cの考えかただと通りますよね。
でもswiftでは通りません。
Stringにはnilを代入できなのです。

でも無いことを示したいこともありますよね。
そんな時にOptionalを使います

var text:String? = nil

Stringの後に付けた”?”がオプショナルの宣言で、値が入ってるか入ってないか分からない変数を示します。
これはStringをラップしたString?という変数という認識で良いのではないかと思います。

このOptionalな値はそのままでは使えません。
ラップしている”中身”を取り出す必要があります。
その時に使うのが !(エクスクラメーションマーク)です

var textOpt:String? = "今日もいいお天気"
let text = textOpt!

この!によってアンラップするときにもし値が存在していなかったらどうなるか?
Swiftではエラーになります。

そこで値の有無をチェックしてから取り出す事となります。


var pockyDayOpt:Int? =  "1111".toInt()

// 判定は普通にifすればOK
if pockyDayOpt {
    var pockyDay = pockyDayOpt!
    NSLog("ポッキーの日:%d",pockyDay)
} else {
    NSLog("今年はポッキーの日を中止にします。")
}

まぁ分かりやすいですが、ちょっと面倒ですよね.
そこでこんな構文でチェックとアンラップを同時に行えます


if let pockyDay = pockyDayOpt {
    NSLog("ポッキーの日:%d",pockyDay)
}

ん?まだ面倒ですか?
確かにOptionがネストしていた場合ではifがネストしてしまいちょっと面倒臭いですね。
例えばこういった構造体があったとして


struct MainDish {
    var dishName:String?
}

struct LunchBox {
    var mainDish:MainDish?
}


// ハンバーグランチ
let lunchA = LunchBox(mainDish:MainDish(dishName:"ハンバーグ"))
// 見たことも無い不思議なおかずランチ
let lunchB = LunchBox(mainDish:MainDish(dishName:nil))
// 衝撃のおかず無しランチ
let lunchC = LunchBox(mainDish:nil)

// ランチをArrayに格納
let lunches = [lunchA,lunchB,lunchC]

LunchBoxのMainDishの名前を出力する場合を考えてみます。


// 普通に書いた場合
for lunch in lunches {
    if let mainDish = lunch.mainDish {
        if let dishname = mainDish.dishName {
            NSLog("今日のランチは \(dishname) だ! やっほ~い!")
        }
        else {
            NSLog("このおかず、、、何だろ?")
        }
    }
    else {
        NSLog("おかずが無いとか。。。")
    }
}

面倒ですね。。

Swiftにはこんな時に便利なOptional-Chainという機能があります。
例を見てもらった方が早そうです。


for lunch in lunches {
    if let dishname = lunch.mainDish?.dishName {
        NSLog("今日のランチは \(dishname) だ! やっほ~い!")
    } else {
        NSLog("まともなおかず食べたい。。。")
    }
}

maindish?.dishName と記述している部分がそれです。
これはmainDishに値が設定されている場合にのみ次のdishNameが評価されます。

ifで判定せずにこんな書き方もOK


let dishName:String? = lunch.mainDish?.dishName?.lowercaseString

 
 
最後に、関数・クロージャの変数がオプショナルだった場合はこのように宣言しますよ

// 全体をカッコで囲ってオプショナルの指定を行う
var funcOpt:((String) -> (String))?

funcOpt = {
    (text:String) -> String in
    return "hello \(text)"
}

// 実行する時はこんな感じ
funcOpt!("タテオ")

 
このOptional、Optional-Chainを使うことによってヌルポが発生する機会は激減することでしょう。

[Swift] 列挙型(Enum)の使い方

SwiftのEnumは強力です。

普通に宣言した場合


enum CameraMakers {
    case Canon , Nikon , Olympus , Panasonic , Sigma , Sony , Hasselblad , PhaseOne
}

// 使い方
// 素直に値を参照できる
var maker = CameraMakers.Nikon
// コンパイラが型を分かっていたらenum型を省略することも可能
maker = .Canon

 

Int値としての列挙型
割り当てられた値は”Raw Value”って呼ばれます


enum Weeks:Int {
    case Sunday = 1,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday
    
    // enumの中にも関数を書けるよ!
    func japaneseCaption() -> String {
        switch self {
            case .Sunday:return "日"
            case .Monday:return "月"
            case .Tuesday:return "火"
            case .Wednesday:return "水"
            case .Thursday:return "木"
            case .Friday:return "金"
            case .Saturday:return "土"
        }
    }
}

// RawValueの参照
NSLog("Tuesday : \(Weeks.Tuesday.rawValue)")
// ・・・ Tuesday : 3 って出力される

// Raw Valueからenumを求める事もできる
let wed:Weeks? = Weeks(rawValue:4);

enumの中に関数を書けるなんてビックリだよね。
 

Stringでも宣言可能


enum IOSDevices:String {
    case iPhone = "アイフォーン"
    case iPad = "アイパッド"
    case iPodTouch = "アイポッドタッチ"
}


// 型がStringなenumでもちゃんと動く
let iphone = IOSDevices(rawValue:"アイフォーン")
if let device = iphone {
    if device == IOSDevices.iPhone {
        NSLog("device is iphone")
    } else {
        NSLog("device is not iphone")
    }
} else {
        NSLog("device is None")
}

 

enumをswitchで判定する


let iphone = IOSDevices.fromRaw("アイフォーン")
if let device = iphone {
    switch device {
    case .iPhone:
        NSLog("device is iphone")
    case .iPad:
        NSLog("device is iPad")
    default:
        NSLog("device is something else.")
    }
}

 

SwiftのEnumのユニークで強力な機能は要素ごとに全く違う値(と呼んで良いのかもわからないけど)を持てる事です


// これは。。。なんと呼べば良いのか?
// discriminated unions とか tagged unions って言うものらしいけど。
enum Lens {
    case FixedFocalLens(String,Int,Double)
    case ZoomLens(String,Int,Int,Double)
}

//使い方
// FixedFocalLens
let ef50f18 = Lens.FixedFocalLens("EF",50,1.8)
let ef100f28L = Lens.FixedFocalLens("EF",100,2.8)

// ZoomLensの値
let ef70_200F4L = Lens.ZoomLens("EF",70,200,4.0)

// 求めた値をswitchで条件分けして出力してみる
let lenslist = [ef50f18,ef100f28L,ef70_200F4L]
for lens in lenslist {
    switch lens {
      case .FixedFocalLens(let name,let focallength,let aperture):
          NSLog("単焦点 \(name) \(focallength)mm F\(aperture)")
    
      // letやvarはパラメータ毎に書くと面倒なので下記のようにまとめて指定できる
      case let .ZoomLens( name, minfocallength, maxfocallength, aperture):
          NSLog("ズームレンズ \(name) \(minfocallength)-\(maxfocallength)mm F\(aperture)")
    }
}
// こんな感じで出力される
// 単焦点 EF 50mm F1.8
// 単焦点 EF 100mm F2.8
// ズームレンズ EF 70-200mm F4.0

これはenumの範疇を超えてるね