タグ別アーカイブ: Swift

[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の範疇を超えてるね

[Swift] クラス、プロトコルの宣言

クラス宣言はとてもシンプル

class クラス名:基底クラス {
	
	・・・実装

}

やっぱり構文はシンプルなのが一番

 
いろんな要素を詰め込んだクラスのサンプル


class Programmer {
    
    // プロパティの宣言
    // 普通に宣言
    var name = ""
    // ※今のところprivateな宣言は出来ないっぽい
    

    // セッター、ゲッターの指定
    var _company:String?
    var company:String? {
      get {
        return _company
      }
      set(newValue){
        _company = newValue
      }
    }
    //※ セッター、ゲッターの指定は他のプロパティから計算で求められたり、
    //  計算結果を他のプロパティに格納するような場合に使うと良いと思う。
    //  この例は良くないね。

    
    // 遅延評価するプロパティ
    // ※最初に参照された時に初期化される
    @lazy var skills = Array<String>()
    
    
    // ReadOnlyなプロパティ
    var displayName:String {
    	if let cmp = company {
        	return "\(name)@\(cmp)"
    	} else {
        	return "\(name)"
        }
    }

    
    // プロパティオブザーバー
    // プロパティの値が変更する前後に処理を行う
    var experienceYears:Int = 0 {
    	
    	// willSetではプロパティに値が設定される直前に処理を行うことができる
    	willSet(newValue){
        	NSLog("経験年数が\(experienceYears)から\(newValue)に変更されようとしています。")
    	}

    	// didSetでは値が登録された後に処理を行うことができる
    	didSet {
        	//didSetでは変更前の値が oldValue として参照できる
        	NSLog("経験年数が\(oldValue)から\(experienceYears)に変更されました!")
        	if (experienceYears > 100){
            	// 値を再設定することも出来る
            	NSLog("でも何かの間違いなので元に戻します。")
            	experienceYears = oldValue
        	}
    	}
    }
    


    // subscript
    // C#のインデクサ的なもの    
    subscript(index:Int) -> String {
        get{
            return skills[index]
        }
        set(newValue){
            if skills.count <= index {
                skills += Array(count:index - skills.count + 1,repeatedValue:"")
            }
            skills[index] = newValue
        }
    }
    // ※ subscript(row: Int, column: Int) のように複数のパラメータを受けるように宣言することも可能

    
    
    // タイププロパティ(Type - Property)
    // クラス変数、スタティックプロパティのように理解すればよいか?
    // ※ まだ対応されていないようです
    // class var dislike = "I hate Change Request!!"
    
    

    // イニシャライザの定義
    init(name:String){
        self.name = name
    }
    
    // コンビニエンス イニシャライザ
    convenience init(){
        // 自身のinitを呼び出す必要がある
        self.init(name:"(匿名)")
    }
    
    // デイニシャライザ(?)
    // デストラクタ的なやつ
    deinit {
        // ・・・リソースの開放を行う
    }
    


    // メソッドの定義
    // 普通ですね
    func skillCaption() -> String {
        var result = ""
        for skill in skills {
            result += " \(skill)"
        }
        return result
    }
    
    
    func program(){
        NSLog("やってもやっても終わらない!")
    }
    
}

 
このクラスprogrammerを継承


class SwiftProgrammer:Programmer{

    // initではsuperのinitを呼び出さないとエラーとなる
    init(name:String){
        super.init(name:name)

        skills.append("swift")
    }
    

    // オーバーライド
    override func program(){
        NSLog("Swiftだと仕事が早い!不思議!")
    }
    

    // プロパティのオーバーライド
    override var company:String? {
    	get {
        	return "SwiftCompany-" + (_company ? _company! : "")
    	}
    	set(newValue){
        	super.company = newValue
    	}
    }
}

  

 
プロトコル
もうobjective-cとか気にせずにインターフェースって呼べばいいのにね。


// プロトコルの宣言
protocol Agiler {
    
    // プロパティの宣言
    var canRefactoring:Bool { get set }
    
    // メソッドの定義
    func scrum() -> String
    
}

// プロトコルの実装
class AgileProgrammer:Programmer,Agiler{
    
    init(name:String){
        canRefactoring = false
        super.init(name:name)
    }
    
    
    // protocol[Agiler]の実装
    
    var canRefactoring:Bool
    
    func scrum() -> String {
        return "スクラム"
    }
    
}

 

クラスのインスタンスを生成するときはnewとか不要



    let programmerA = Programmer()
    programmerA.company = "りんごコンピュータ"
    programmerA.experienceYears  = 5
    programmerA.experienceYears  = 200
    programmerA[0] = "swift"
    programmerA[1] = "objective-c"
    
    NSLog("A displayName:\(programmerA.displayName) skills:\(programmerA.skillCaption()) career:\(programmerA.experienceYears)")
    
    
    // コンビニエンスイニシャライザを使ってインスタンス化
    let programmerB = Programmer(name:"まさお")
    NSLog("B displayName:\(programmerB.displayName)")
    
    // 継承されたクラスをインスタンス化
    let programmerC = SwiftProgrammer(name:"ひろし")
    programmerC.company = "ゴーグル"
    NSLog("C displayName:\(programmerC.displayName)")

インスタンスのメモリはARCによって管理されるので明示的な廃棄処理は不要です。

[Swift] 関数・メソッドの宣言

関数やメソッドの宣言方法についてまとめてみました。

 

関数の基本形は下記みたいな感じです

引数は 名称:型 で指定
戻り値の型を “->” で指定


func laughText(text:String) -> String {
    return text + "(笑)"
}

// 引数も戻り値も無い時
func logNow() {
    NSLog("現在時刻:%@",NSDate.date())
}

 

パラメータ名の宣言ってのも出来ます


// 外部パラメータ名(下記だとvalueFirst,valueSecond)と
// 内部パラメータ名(下記だとvalue1,value2)をそれぞれ個別に宣言。
func calcPair(valueFirst value1:Double ,valueSecond value2:Double ) -> Double {
    return value1 + value2
}

// これを呼び出すときには
let v = calcPair(valueFirst: 1, valueSecond: 50)

objective-cのメソッド呼び出しのようですね。
 

ちなみに内部と外部で同じ名前とする場合には#(シャープ)を指定すればOK


// この場合は内部・外部ともに同じ名前が使われる
func calcPair2( #valueFirst:Double , #valueSecond:Double ) -> Double {
    return valueFirst + valueSecond
}

 

In-Outパラメータ
引数のポインタ渡し的なやつ


func arrangeTwo(inout largeVal:Int ,inout smallVal:Int){
    if (largeVal < smallVal){
        let t = smallVal
        smallVal = largeVal
        largeVal = t
    }
}

//呼び出すときは&を付けて変数を引き渡します
var valA:Int = 50
var valB:Int = 70
arrangeTwo(&valA,&valB)
NSLog("valA\(valA) valB:\(valB)")

 

可変長パラメータ
型の後ろに “…” と余韻(?)を残すことで可変長引数になる


func totalNumbers(numbers:Double...) -> Double {
    var result:Double = 0
    for v in numbers {
        result += v
    }
    return result
}

//呼び出し方
let totalnum = totalNumbers(1,2,3,4,5,6,7,8,9)
NSLog("total:\(totalnum)")

  

 
Swiftは関数もscala等と同様にfirst-classなので引数に直接渡せちゃいます。


// 引数 calcFunc -- Doubleを2つ引数で受け取りってDoubleを返すメソッド
// 引数 v1,v2  -- 計算対象のDouble値
func logCalcResult(calcFunc:(Double,Double) -> Double   ,v1:Double,v2:Double) {
    // v1,v2を引数として関数(calcFunc)を実行
    let calcResult = calcFunc(v1,v2)
    NSLog("calcResult \(calcResult)")
}


//呼び出し方
logCalcResult(calcPair,12,33)

 

同様に戻り値として関数を返す事もできます


func goodmorning(l:String) -> (String)->(String) {
    
    func sayEnglish(name:String) -> String {
        return "Good morning,\(name) "
    }
    func sayJapanese(name:String) -> String {
        return "\(name) おはよ〜 "
    }
    
    // 戻り値として関数sayEnglish or sayJapanese を返す
    if (l == "english"){
        return sayEnglish
    }
    else {
        return sayJapanese
    }
}

// 使い方
// 戻り値の関数を変数に格納
var speakfunc = goodmorning("english")
// 実行
NSLog(speakfunc("たろう"))
// ・・・ Good morning,たろう

 
関数型言語のように関数が通常の変数同様の扱いとなっているのが今風で良いですね。

[Swift] ループ制御 (繰り返し処理)

Swiftのループ構文をまとめてみました。
 

forループ


// 普通のCスタイルなループ
for var i = 0 ; i < 10 ; i++ {
    // do something
    NSLog("for statement i:\(i)")
}

// Rangeを使った for-in ループ
// "..<"で終端-1までの数値範囲を示すことができる
for i in 0..<10 {
    // 0〜9でループ
    NSLog("for-in statement i:\(i)")
}

// "..." の時は終端の数値も含める
for i in 0...10 {
    // 0〜10でループ
    NSLog("for-in statement2 i:\(i)")
}

 

次はwhileを使ったループ


// whileループ
// ごく一般的な書き方ですね。
var counter = 0
while counter < 3 {
    // do something
    NSLog("while statement counter:\(counter)")
    counter++
}

// do-whileも一般的な文法
counter = 3
do {
    // 最初の1回は無条件で実行される
    NSLog("do-while statement counter:\(counter)")
    counter++
} while counter < 3

 
 

Swiftのループは break、continue にも対応しています。


// nilはスキップ(continue)して999で終了(break)しています
var total = 0
let list:Array<Int?> = [1,2,nil,4,5,6,-1,999]
for v in list {
    if let intVal = v {
        if (intVal == 999) {
            break
        }
        
        total += intVal
    }
    else {
        continue
    }
}
NSLog("total: \(total)") // total: 17

 

ラベル付きのループにも対応しています。
[ラベル名]: for
のようにループの前にコロンで区切ってラベルを定義します。


let programmers = [
    ("まさし",["サボりぐせ","C#","java"]),
    ("ひろし",["Swift","Oracle","VPN"])
    ]
// ラベル付きのループ
programmersLoop: for (name,skills) in programmers {
    for skill in skills {
        if (skill == "サボりぐせ") {
            NSLog("%@さん、あなたは帰ってください。",name)
            // 外側のループに対してcontinue 
            // 便利!
            continue programmersLoop
        }
        NSLog("%@さん、あなた %@も出来るんですね!",name,skill)
    }
}

これは便利ですね。
この例ではforにラベルを付けているけど、whileでももちろんOK

[Swift] Dictionaryの使い方

SwiftのDictionaryを使ってみた。
よくあるHashmap的なやつね。
 

宣言はこんな具合


// 空Dictionaryの宣言
var emptydictionaly = Dictionary<String,Int>()

// Dictionalyの宣言
// ※型は省略可能です
var populationDict:Dictionary<String,Int>  = [
    "kanazawa":10,
    "komatsu":100000,
    "nanao":50000
]

 

保存している値へのアクセス方法


// 値の取得
// Dictionalyから値を求めるとoptionalな値が帰ってくる
let kanazawaOpt = populationDict["kanazawa"]
NSLog("kanazawa population:%d",kanazawaOpt!)


// 値の追加
populationDict["kaga"] = 60000
populationDict.updateValue(38000,forKey:"suzu")


// 値の上書き
// こんな素直な書き方も可能だけど
populationDict["kanazawa"] = 1000000

// updateValueを使うと変更前の値をOptionalで取得できる
if let oldvalue = populationDict.updateValue(460000,forKey:"kanazawa") {
    NSLog("old kanazawa population:%d",oldvalue)
}


// 要素の削除
// nilを入れることで要素が削除される
populationDict["nanao"] = nil

// removeValueForKeyを使うと削除と同時に古い値を取得できる
if let oldvalue = populationDict.removeValueForKey("komatsu") {
    NSLog("old komatsu population:%d",oldvalue)
}


// ちなみにletでDictionalyを宣言すると要素を変更することができない
// ※arrayではletでも上書きできちゃう。。
let immutableDict = ["A":1]
// ↓でエラーとなる
//immutableDict["A"] = 2

 

要素の列挙、ループ処理


var totalPopulataion = 0

// for-each的なループ
// キーと値を一緒に取得
for (city,population) in populationDict {    
    totalPopulataion += population
}
NSLog("合計:\(totalPopulataion)")


// キーのみを求めてループ
for city in populationDict.keys {
    NSLog("city:\(city)")
}

 

Dictionalyは構造体(Structure)なので値型!
なので下記では複写元と複写先では違う値が取得される


// 参照を別の変数に代入して値を書き換える
populationDict["kanazawa"] = 460000
var copiedDict = populationDict
copiedDict["kanazawa"] = 90000

// でも元の値は書き換わっていない
NSLog("populationDict:%d copiedDict:%d",populationDict["kanazawa"]!,copiedDict["kanazawa"]!)
// ・・・ populationDict:460000 
//        copiedDict    :90000

 

[Swift] コレクション(Array)のループ速度比較

下記の3パターンで速度比較を行ってみた
・パターンA
 Cスタイルなループカウンタを使ったforループ
 objective-cでは遅かったが。。。

・パターンB
 enumerateを使ったfor-inループ
 要素のループと同時にindexを求めることが可能
 きっと遅いな。これは。

・パターンC
 for-inループ(objective-cのFast Enumeration)
 直接indexは求めることが出来ないけどきっと速いはず

実行時間を取るためにこんな関数を用意して


func currentTime() -> Double {
    return ((CFAbsoluteTimeGetCurrent() as Double) * 1000.0)
}

下記のコードで検証しました


// 10万要素の配列を作成
// こういったデータもSwiftでは簡単に作成できます。
let testValues = Array(count:100000,repeatedValue:2)

var total = 0,startTime=0.0,interval=0.0

// パターンA
// Cスタイルなforループ
total = 0
startTime = currentTime()
for var i = 0; i < testValues.count; i++ {
    total += testValues[i]
}
interval = currentTime() - startTime
NSLog("patttern-A takes \(interval)ms total:\(total)")

// パターンB
// enumerateを使った for-in ループ
// scalaの zipWithIndex みたいなやつ
total = 0
startTime = currentTime()
for (index,v) in enumerate(testValues){
    total += v
}
interval = currentTime() - startTime
NSLog("patttern-B takes \(interval)ms total:\(total)")


// パターンC
// indexを求めない for-in ループ(foreachだね)
total = 0
startTime = currentTime()
for v in testValues {
    total += v
}
interval = currentTime() - startTime
NSLog("patttern-C takes \(interval)ms total:\(total)")

結果は速い順で a > c > b の結果となった
実際の計測結果はまだいろいろ問題がありそうなので伏せておきますが、
一番速かったAを100%とした場合Bが159%、Cが123%の実行時間でした。
※これは処理順を変えても同様だった

予想通りenumerate を使ったループが一番遅かった。
予想と違ったのはパターンCよりパターンAが速かった事。objective-cでは要素をインデックスによって求めると遅かったけどSwiftでは気にしなくていいみたい。

AとBでは約1.6倍の処理速度差があるので、大量データの処理時には気をつけたほうが良いかもね。

(2014-09-19追記)
XCode6.0.1で再検証すると、パターンAとCではほとんど差が無くなりました。
それでも2〜3%ほどパターンAの方が速いですが、コードの見やすさを考えると積極的にパターンCを使って良いのではないかと。

[Swift] コレクション(Array)の使い方

Swiftの配列コレクションを使ってみた
Arrayって言う位だからList構造ではなくてホントに配列なのかも?

宣言のサンプル

// 空配列の宣言
var citylist = [String]()

// 宣言時に要素を設定
// ※型宣言は省略可能
// String[]  は Array<String>のシンタックスシュガー
let ishikawaCitylist:[String] = ["kanazawa","komatsu","nanao","kaga"]

構文が簡単でいいね!
 

要素の追加とか削除とか

// 要素が設定されているか判定
if citylist.isEmpty {
    NSLog("からっぽ")
}

// 要素を追加
// +=でも追加できるし (array も 要素も直接足せる)
citylist += ishikawaCitylist
citylist += "nonoichi"

// appendでも追加可能
citylist.append("suzu")

// 要素を挿入
citylist.insert("wakura",atIndex:0)


// 要素を削除
// この時取り除かれた要素が返却される
let kanazawa = citylist.removeAtIndex(1)
let suzu = citylist.removeLast()
NSLog("is kanazawa?:%@", kanazawa)
NSLog("is suzu?:%@", suzu)

arrayをループさせるときはこんな感じで


// for-each (for-in) なループ
for city in citylist {
    NSLog("cities A:%@", city)
}
// 同時にindexを求める場合
for (index,city) in enumerate(citylist){
    NSLog("cities B:%@ (%d)", city,index)
}

// よくあるループカウンタを使ったforループ
for var i = 0; i < citylist.count; i++ {
    NSLog("cities C:%@ (%d)", citylist[i],i)
}

 

もちろん多次元配列も作れます

// 2次元配列
let matrix:Int[][] = [[1,2,3,4,5,6,7] ,[11,12,13,14,15,16,17]]
let matrix:[[Int]] = [[1,2,3,4,5,6,7] ,[11,12,13,14,15,16,17]]
// matrix[0] は [1,2,3,4,5,6,7]
// matrix[0][1] は 2
NSLog("matrix[0]:%@", matrix[0])
NSLog("matrix[0][1]:%d", matrix[0][1])

(XCode6.0-beta3でArrayの動作が大きく変更となりました)

でこれは要注意なんだけど、arrayってのは構造体(値型)って事もあり参照のコピーには
いろいろ癖がある。


// arrayの参照をコピーする場合は要注意!
// 例えばこのように単純に代入して複写すると同じものとして扱われる
var copiedList = citylist
copiedList[0] = "yokohama"
NSLog("simple assign  citylist[0]:\(citylist[0])  copiedList[0]:\(copiedList[0])")
// ・・・ citylist[0]:yokohama  copiedList[0]:yokohama

// だけど一方にappendなどで要素の数を変更した場合には別の配列として扱われる!!
// なのでこの例では citylist と copiedList で違う値が取得される
copiedList.append("kumamoto")
copiedList[0] = "nagasaki"
NSLog("after append  citylist[0]:\(citylist[0])  copiedList[0]:\(copiedList[0])")
// ・・・  citylist[0]:yokohama  copiedList[0]:nagasaki
// これはハマり所でバグ臭がプンプンするぜ〜

// 常に別物として使う場合はunshareってのを呼べば良い
var copiedListB = citylist
copiedListB.unshare()
copiedListB[0] = "fukuoka"
NSLog("unshare  citylist[0]:\(citylist[0])  copiedListB[0]:\(copiedList[0])")
// ・・・  citylist[0]:yokohama  copiedListB[0]:nagasaki

// もしくはcopyでシャローコピーする
var copiedListC = citylist.copy()

// letだとappend出来ないのでarrayはletで宣言するのがバグの要因を減らす事になるかな?


// 関数に渡した場合の挙動
var list1 = [1,2,3]

// 関数内で要素の値を変更した場合の挙動
// 関数内からの操作でも呼び出し元のArrayが変更されている
func modifyArrayA(arr:Array<Int>) {
    arr[0] = 999
}
modifyArrayA(list1)
NSLog("modifyArrayA  list1[0]:\(list1[0])")
//・・・ list1[0]:999

// letを付けても同様
func modifyArrayB(let arr:Array<Int>) {
    arr[0] = 999
}
list1[0] = 1
modifyArrayB(list1)
NSLog("modifyArrayB  list1[0]:\(list1[0])")
//・・・ list1[0]:999



// 関数内で要素数を修正した場合
// 同一関数内での操作と同様にappendした時点で別ものとして扱われているようだ
func modifyArrayC(var arr:Array<Int>) {
    arr.append(4)
    arr[0] = 999
}
list1[0] = 1
modifyArrayC(list1)
NSLog("modifyArrayC  list1[0]:\(list1[0]) count:\(list1.count)")
//・・・ list1[0]:1 count:3

// inoutを指定した場合には要素数を変更を含む操作が呼び出し元の変数に反映される
func modifyArrayD(inout arr:Array<Int>) {
    arr.append(4)
    arr[0] = 999
}
list1[0] = 1
modifyArrayD(&list1)
NSLog("modifyArrayD  list1[0]:\(list1[0]) count:\(list1.count)")
// list1[0]:999 count:4


うーん、引数の配列へ影響を与えるかどうか明示的にしたいんだけど、どうすればいいかな?
ちゃんと方針を決めておかないと絶対バグの温床になるよ。これ。

  
(XCode6.0-Beta3での変更)
→ beta3では随分スッキリしました

letで宣言したArrayは変更不可

let immutableArray = ["りんご","オレンジ","キューイ"]
// これはエラーとなる   (beta2まではOK)
// immutableArray[0] = "すいか"
// もちろんこれもエラー 
// immutableArray.append("すいか")

varで宣言すればもちろん変更可能

var mutableArray = ["大根","人参"]
mutableArray[0] = "こぼう"
mutableArray.append("きゃべつ") 

問題(?)だった別変数へのコピーは、値型らしく完全に別ものとして扱われます
※unshareは無くなったようです

var mutableArray = ["ごぼう","人参","ぎゃべつ"]

var mutableArray2 = mutableArray
mutableArray2[0] = "レタス"
for item in mutableArray {
    NSLog("mutableArray:%@", item)
}
//  ごぼう 人参 きゃべつ

for item in mutableArray2 {
    NSLog("mutableArray2:%@", item)
}
//  レタス 人参 きゃべつ

inoutな変数の場合は今まで通り元のArrayを変更してくれます


func modifyArrayInout(inout arr:[String]) {
    arr.append("きゅうり")
    arr[0] = "なすび"
}

var mutableArray = ["ごぼう","人参","ぎゃべつ"]
modifyArrayInout(&mutableArray);
for item in mutableArray {
    NSLog("mutableArray:%@", item)
}
// なすび 人参 きゃべつ きゅうり

 
 
 
 
NSArrayをSwiftの配列にキャストする時は下記のように行う

// NSArrayをString[]にキャスト
let wrestlers: NSArray = ["鶴田", "渕", "田上"]
if let wrestlersArr = wrestlers as? String[] {
    for  wrestlerName in wrestlersArr {
        NSLog("\(wrestlerName)")
    }
}

 

最後にArrayを使うときに便利そうなmapってメソッドのサンプル
arrayの各要素を変換して別のarrayとして返します
scalaのseq.mapとほぼ同じ機能ですね
この例ではArray<Int>からArray<String>に変換しています

var list1 = [1,2,3]
let stringlist:Array<String> = list1.map {
    num -> String in
    return String(num)
}
NSLog("string list %@",stringlist)

 
 
 

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

新言語”Swift”をさわってみた

Appleが満を持して発表したOSX・iOS用の新言語「Swift」
ちょっとさわってみた感じでは
・構文がとてもシンプルで分かりやすい!
・最近のトレンドも取り込まれていてなかなかのパワフルさ!
・実行速度もobjective-cより速い(らしい)
・関数言語っぽい所もある
・メモリ管理もARCでラクチン
・Cocoaの呼び出しも大丈夫そう
・これで大カッコのネストから開放される!!
かなり期待大!
これからiOSでアプリつくろうって人はSwiftで良いのではないだろか?

コードはこんな雰囲気

// varで宣言すると変数
// ※型推論によって多くの場合には型を省略できる。便利!
var scoreMath = 78

// letで宣言すると定数となる
let scoreEnglish = 43
// なのでletでは再代入するとエラーとなる
//scoreEnglish = 100



// 文字列の扱いが簡単になった
let textMath = "数学:" + String(scoreMath)

// 文字列へ変換するときにバックスラッシュで直接変数を書ける
let textTotal = "合計:\(scoreMath + scoreEnglish)です!"
NSLog(textTotal)



// arrayの宣言
var citylist = ["kanazawa","komatsu","nanao"]
let sweetslist = ["cake","candy"]

// varでもletでも下記のように代入可能
// もちろん添字の最大値を超えるとエラーとなる
citylist[0] = "angir"
sweetslist[1] = "キャンディ"

// varで宣言されたarrayには要素を追加できる
citylist.append("wajima")



// Dictionary(マップ)の宣言
// key:valueで初期化する
// もちろんこれも型推論してくれる
var population = [
    "kanazawa":460000,
    "komatsu":100000,
    "nanao":50000
]
let finisher = [
    "キン肉マン":"マッスルスパーク",
    "ロビンマスク":"ベルリンの赤い雨"
]

// varで宣言されたDictionalyには要素を再設定できる
population["kaga"] = 60000
// letで宣言されたDictionalyではエラーになる。。arrayではletであっても変更出来るのにね。
// finisher["ペンタゴン"] = "クロノスチェンジ"


// 関数の宣言はこんな感じ
// 関数の中に関数を書いたりも出来るよ
func pickPopulation(cityname:String) -> Int {
    // ? はOptionalっていうC#のnullable、scalaのOptionみたいなやつ
    // (もっとシンプルな書き方もあるよ)
    let popOpt:Int? = population[cityname]
    if popOpt {
        let pop:Int = popOpt!
        return pop
    } else {
        return 0
    }
}


// ループ
var totalpopulation = 0
for city in citylist {
    totalpopulation += pickPopulation(city)
}
NSLog("人口:\(totalpopulation)")

Cとかjavaとか書ける人なら全然問題無いと思います。

ただ、現状XCodeがベータ版ってこともあってか、
 入力補助がちゃんと効かなかったり。
 エディタが構文解析中にエラーで落ちたり。
 言語仕様的には書けるはずのものが実装されていなかったり。
します。
正式版が待ち遠しい。