hachinoBlog

hachinobuのエンジニアライフ

SwiftのArrayとDictionaryの使い方

背景

基本的な言語仕様はふんわりと学んだので、今後多用するであろうArrayとDictionaryの使い方を調べたのでメモ。

Array サンプルコード

// immutableなString型の配列
let names: String[] = ["name5", "name2", "name1", "name3"]

//各データを取得
let name1 = names[0] //name5
let name2 = names[1] //name2
let name3 = names[2] //name1
let name4 = names[3] //name3

//各件数
names.capacity // 4
names.startIndex // 0
names.endIndex // 4
names.count // 4


// mutableなString型の配列
var programLang = ["Objective-C", "Java"]

// データ追加
programLang.append("Swift") // [Objective-C, Java, Swift]
programLang += "Python" // [Objective-C, Java, Swift, Python]

let addLang = ["Ruby", "PHP"] 
programLang.extend(addLang) // [Objective-C, Java, Swift, Python, Ruby, PHP]
programLang += ["Scala", "JavaScript"] // [Objective-C, Java, Swift, Python, Ruby, PHP, Scala, JavaScript]

programLang.insert("ひまわり", atIndex: 0) // [ひまわり, Objective-C, Java, Swift, Python, Ruby, PHP, Scala, JavaScript]

// 配列の中身を逆順にする
programLang.reverse() // [ひまわり, Objective-C, Java, Swift, Python, Ruby, PHP, Scala, JavaScript]

// 配列内をソート(降順)
sort(programLang) { $0 > $1 } // [ひまわり, Swift, Scala, Ruby, Python, PHP, Objective-C, JavaScript, Java]

// 配列内をソート(昇順)
sort(programLang) { $0 < $1 } // [Java, JavaScript, Objective-C, PHP, Python, Ruby, Scala, Swift, ひまわり]

// programLang配列のインデックスが0,1,2のデータを取得
programLang[0...2] // [Java, JavaScript, Objective-C]

// programLang配列のインデックスが0,1のデータをCとC#にする
programLang[0...1] = ["C", "C#"] // [C, C#, Objective-C, PHP, Python, Ruby, Scala, Swift, ひまわり]

// 先頭にCがつく言語を取得
programLang.filter { $0.hasPrefix("C") } // [C, C#]

// 各言語データの先頭に Language: を付与
programLang.map { "Language: \($0)" } // [Language: C, Language: C#, Language: Objective-C, Language: PHP, Language: Python, Language: Ruby, Language: Scala, Language: Swift, Language: ひまわり]

// 指定インデックスに該当するデータ削除
programLang.removeAtIndex(0) // [C#, Objective-C, PHP, Python, Ruby, Scala, Swift, ひまわり]

// インデックス0,1,2のデータを削除
programLang[0...2] = []  // [Python, Ruby, Scala, Swift, ひまわり]

// 配列の最後に格納されているデータを削除
programLang.removeLast()  // [Python, Ruby, Scala, Swift]

// 全て削除
programLang.removeAll()  // []

// copyとunshare
// mutableな配列
var numbersA: Int[] = [1, 2, 3]
var numbersB: Int[] = numbersA
var numbersC: Int[] = numbersA.copy() // numbersAをcopy

numbersA[0] = 0
println(numbersA)  // [0, 2, 3]
println(numbersB)  // [0, 2, 3]
println(numbersC)  // [1, 2, 3]

// copyしたnumbersBをunshare()
numbersB.unshare()
numbersA[1] = 5
numbersC[0...1] = [6, 7]

println(numbersA)  // [0, 5, 3]
println(numbersB)  // [0, 2, 3] unshareしたので参照データでなくなった
println(numbersC)  // [6, 7, 3]

Objective-CのNSArrayと同じように使えるけど += で配列にデータを追加できたりsortが用意されていたりで柔軟で使いやすい印象。


Dictionaries サンプルコード

// 初期化
var personInfo: Dictionary = ["name": "hachinobu", "age": 28]

// データ追加
personInfo["sex"] = "man"

// データ取得
personInfo["name"] //  hachinobu
personInfo["age"]    //  28
personInfo["sex"]    //  man

// 格納件数
personInfo.count //  3

// keyの配列を取得
let keys: Array = Array(personInfo.keys)  //  [sex, name, age]

// valueの配列を取得
let values: Array = Array(personInfo.values) //  [man, hachinobu, 28]

// Dictionaryにnameというkeyでデータが格納されているかチェック、なければ追加する
if let name = personInfo["name"] {
    println("name is \(name)") //  name is hachinobu
} else {
    personInfo["name"] = "hachinobu"
}

// データ更新
personInfo.updateValue("nishinobu", forKey: "name")  //  [sex: man, name: nishinobu, age: 28]


// 既に存在するkeyならupdateで存在しないkeyであればinsertになる
if let age = personInfo.updateValue(29, forKey: "age") {
    println("update")
} else {
    println("insert") 
}

// 追加
personInfo["height"] = 171 //  [age: 29, sex: man, name: nishinobu, height: 171]

// 指定したkeyのデータを削除
personInfo.removeValueForKey("height") //  [age: 29, sex: man, name: nishinobu]

// personInfoのようにvalueの型がOptionalでない場合はvalueにnilを入れると削除扱い
personInfo["name"] = nil //  [age: 29, sex: man]

// 下記のようにvalueの型がOptionalの場合にvalueにnilを入れるとnilが代入される
var optionalDict: Dictionary<String, NSObject?> = ["key1": "A", "key2": "B"]
optionalDict["key1"] = nil // [key2: B, key1: nil]

valueにnil入れて削除とか面白い

valueの型にOptionalを指定すると挙動が変わるので注意が必要


addSubviewやremoveFromSuperviewをトリガーにする方法

背景

addSubviewやremoveFromSuperviewしたタイミングでViewの処理をしたかったので色々調べてたら出てきたのでメモ。

方法

willMoveToSuperview:didMoveToSuperviewメソッドを使用すれば良い。

willMoveToSuperview:

自身(UIView)が親Viewに追加される直前に呼ばれる。

引数のnewSuperviewには親Viewがはいってくる。

追加でなく削除される直前にもこのメソッドは呼ばれる。

削除の場合は引数のnewSuperviewがnilになる。

didMoveToSuperview

自身(UIView)が親Viewに追加された直後に呼ばれる。

追加でなく削除される直後にも呼ばれる。

サンプルコード

該当のメソッドを使った簡単なサンプルコード

UIViewMoveEventSample

WWDCで新言語 Swiftが発表された日にObjective-Cの日記を書いている…

何だか乗り遅れた感が…

Swift勉強するぞー!

CoreDataで集計関数を使う

背景

前回のNSExpressionを使って今度はCoreDataで集計関数を使用する方法を調べたのでメモ。

サンプル

下記のデータ構造とデータを格納しているSeminarsテーブルがあると仮定する。

Seminars

date seminar charge
2014/05/01 SeminarA 1,000
2014/05/02 SeminarB 1,500
2014/05/01 SeminarC 500
2014/05/03 SeminarD 2,000
2014/05/02 SeminarE 0
2014/05/01 SeminarF 0

日付ごとのセミナー件数を取得してみる。

NSEntityDescription* entity = [NSEntityDescription entityForName:@"Seminars"
                                          inManagedObjectContext:_managedObjectContext];

//集計対象カラムseminar
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:@"seminar"];
//集計関数countを指定
NSExpression *countExpression = [NSExpression expressionForFunction:@"count:"
                                                          arguments:@[keyPathExpression]];

//集計式の対象(NSExpressionDescription)
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName:@"seminarCount"];
[expressionDescription setExpression:countExpression];
[expressionDescription setExpressionResultType:NSInteger32AttributeType];

[searchFetchRequest setPropertiesToFetch:@[@"date", expressionDescription]];
//Group By
[searchFetchRequest setPropertiesToGroupBy:@[@"date"]];

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:YES];
[searchFetchRequest setSortDescriptors:@[sortDescriptor]];
[searchFetchRequest setResultType:NSDictionaryResultType];
NSArray *fetchedObjects = [_managedObjectContext executeFetchRequest:searchFetchRequest error:nil];

NSLog(@"出力結果:%@", fetchedObjects);

出力結果

({
    date = "2014/05/01";
    seminarCount = 3;
},
{
    date = "2014/05/02";
    seminarCount = 2;
},
{
    date = "2014/05/03";
    seminarCount = 1;
})

ごちゃごちゃと面倒な印象。

やはりMagicalRecordを使うべしですかね。

配列内で集計関数(最大値、最小値、平均値、合計値など)を使用する方法

背景

配列内の値の合計や最大値などを取得したい場合に何か良い方法はないかと調べた。

方法

NSExpression を使用することで集計関数の式を定義できる。

//最大値
NSArray *numArray = @[@3, @6, @12, @8];
NSExpression *maxExpression = [NSExpression expressionForFunction:@"max:" arguments:@[[NSExpression expressionForConstantValue:numArray]]];
id maxValue = [maxExpression expressionValueWithObject:nil context:nil];
NSLog(@"最大値:%f", [maxValue floatValue]); //最大値:12.000000
    
//最小値
NSExpression *minExpression = [NSExpression expressionForFunction:@"min:" arguments:@[[NSExpression expressionForConstantValue:numArray]]];
id minValue = [minExpression expressionValueWithObject:nil context:nil];
NSLog(@"最小値:%f", [minValue floatValue]); //最大値:3.000000
    
//平均値
NSExpression *averageExpression = [NSExpression expressionForFunction:@"average:" arguments:@[[NSExpression expressionForConstantValue:numArray]]];
id averageValue = [averageExpression expressionValueWithObject:nil context:nil];
NSLog(@"平均値:%f", [averageValue floatValue]); //平均値:7.250000
    
//合計値
NSExpression *sumExpression = [NSExpression expressionForFunction:@"sum:" arguments:@[[NSExpression expressionForConstantValue:numArray]]];
id sumValue = [sumExpression expressionValueWithObject:nil context:nil];
NSLog(@"合計値:%f", [sumValue floatValue]); //合計値:29.000000
    
//カウント
NSExpression *countExpression = [NSExpression expressionForFunction:@"count:" arguments:@[[NSExpression expressionForConstantValue:numArray]]];
id countValue = [countExpression expressionValueWithObject:nil context:nil];
NSLog(@"カウント:%d", [countValue integerValue]); //カウント:4

expressionForFunction:の引数に関数を指定してあげる。

上記以外にも偏差を求める関数など沢山ある。

NSExpression Class Reference

ちなみにNSDictionaryや自作オブジェクトの配列でも同じことができる。

NSDictionary *person1 = @{@"name": @"A", @"age": @20};
NSDictionary *person2 = @{@"name": @"B", @"age": @30};
NSDictionary *person3 = @{@"name": @"C", @"age": @35};
NSDictionary *person4 = @{@"name": @"D", @"age": @40};
NSArray *personArray = @[person1, person2, person3, person4];

//最大値
NSExpression *maxExpression = [NSExpression expressionForFunction:@"max:" arguments:@[[NSExpression expressionForKeyPath:@"age"]]];
id maxValue = [maxExpression expressionValueWithObject:personArray context:nil];
NSLog(@"最大値:%f", [maxValue floatValue]); //最大値:40.000000

//最小値
NSExpression *minExpression = [NSExpression expressionForFunction:@"min:" arguments:@[[NSExpression expressionForKeyPath:@"age"]]];
id minValue = [minExpression expressionValueWithObject:personArray context:nil];
NSLog(@"最小値:%f", [minValue floatValue]); //最大値:20.000000

//平均値
NSExpression *averageExpression = [NSExpression expressionForFunction:@"average:" arguments:@[[NSExpression expressionForKeyPath:@"age"]]];
id averageValue = [averageExpression expressionValueWithObject:personArray context:nil];
NSLog(@"平均値:%f", [averageValue floatValue]); //平均値:31.250000

//合計値
NSExpression *sumExpression = [NSExpression expressionForFunction:@"sum:" arguments:@[[NSExpression expressionForKeyPath:@"age"]]];
id sumValue = [sumExpression expressionValueWithObject:personArray context:nil];
NSLog(@"合計値:%f", [sumValue floatValue]); //合計値:125.000000

//カウント
NSExpression *countExpression = [NSExpression expressionForFunction:@"count:" arguments:@[[NSExpression expressionForKeyPath:@"age"]]];
id countValue = [countExpression expressionValueWithObject:personArray context:nil];
NSLog(@"カウント:%d", [countValue integerValue]); //カウント:4

また、expressionForFunction:に指定する関数のmax:やmin:はNSPredicateのpredicateWithFormat:でも下記のように使用可能。

NSArray *numArray = @[@3, @6, @12, @8];

//最大値
NSPredicate *maxPredicate = [NSPredicate predicateWithFormat:@"SELF == max:(%@)", numArray];
id maxValue = [[numArray filteredArrayUsingPredicate:maxPredicate] firstObject];
NSLog(@"最大値:%f", [maxValue floatValue]); //最大値:12.000000

//最小値
NSPredicate *minPredicate = [NSPredicate predicateWithFormat:@"SELF == min:(%@)", numArray];
id minValue = [[numArray filteredArrayUsingPredicate:minPredicate] firstObject];
NSLog(@"最小値:%f", [minValue floatValue]); //最大値:3.000000

集計関数でお気づきのようにNSExpressionを使用すればCoreDataで集計関数を使用してデータをフェッチすることが可能。

CoreDataでの使い方は次回。

※2014/7/7 追記 こんな便利な書き方ができた

NSDictionary *person1 = @{@"age": @20, @"firstName": @"Takahiro"};
NSDictionary *person2 = @{@"age": @20, @"firstName": @"Ryutaro"};
NSDictionary *person3 = @{@"age": @23, @"firstName": @"Yuuichi"};
NSDictionary *person4 = @{@"age": @50, @"firstName": @"Hiroaki"};
NSDictionary *person5 = @{@"age": @30, @"firstName": @"Kosuke"};
NSArray *persons = @[person1, person2, person3, person4, person5];
 
//最年少の人材を取得
NSPredicate *minAgePredicate = [NSPredicate predicateWithFormat:@"age == %@.@min.age", persons];
NSArray *results = [persons filteredArrayUsingPredicate:minAgePredicate];

NSLog(@"results:%@", results);

出力結果

results:( { age = 20; firstName = Takahiro; }, { age = 20; firstName = Ryutaro; } )

NSURLConnectionでオレオレ証明書(自己署名証明書)を許可する方法

背景

オレオレ証明書を使用している開発機にNSURLConnectionでhttps接続しようとしたら下記エラーが出力された。

NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)

解決方法

NSURLConnectionのデリゲートの - (void)connection:(NSURLConnection )connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge )challenge を実装してやる。

#pragma mark - NSURLConnection Delegate
//開発環境のオレオレ証明書(自己署名証明書)を許可するため
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    //SSL認証だった場合の処理(NSURLAuthenticationMethodHTTPBasicやNSURLAuthenticationMethodHTTPDigestもある)
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        // "hachinobu-test.elasticbeanstalk.com"か確認
        if ([challenge.protectionSpace.host isEqualToString:@"hachinobu-test.elasticbeanstalk.com"]) {
            [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
        }
    }
    [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}

こうすることでオレオレ証明書を使用している開発機にもアクセスできる。

CocoaPodsで[overrides the `HEADER_SEARCH_PATHS` build setting defined in `Pods/Pods.xcconfig'.]の対処方法

背景

CocoaPodsでライブラリをインストールしようとしてpod installコマンドを叩いた際に下記が出力されたので調べた。

[!] The target `MyProject [Debug]` overrides the `HEADER_SEARCH_PATHS` build setting defined in `Pods/Pods.xcconfig'.
    - Use the `$(inherited)` flag, or
    - Remove the build settings from the target.

[!] The target `MyProject [Debug - Release]` overrides the `HEADER_SEARCH_PATHS` build setting defined in `Pods/Pods.xcconfig'.

対処方法

対処方法は出力されている通りで自分のprojectの[TARGETS]-[Build Settings]-[Header Search Paths]に$(inherited)を追加してやれば良い。

f:id:hachinobu:20140423141524p:plain

ここで注意すべきは上記画像のように[Header Search Paths]の一番上に$(inherited)を追加すること。

そうしないとビルドしても継承が読み込まれずエラーとなる。

ちなみに今回はoverrides the HEADER_SEARCH_PATHS だったがOTHER_LDFLAGSの場合がある。

この場合でも今回と同じように[Other Linker Flags]に$(inherited)を適宜追加してやることで対処できる模様。

iOS7でステータスバーの領域が黒く塗りつぶされてしまう場合の対処方法

背景

iOS7でNavigationControllerのNavigationBarを使用しているにも関わらず下記のようにステータスバーが黒く塗りつぶされてしまい対応するのに時間を要してしまったのでメモ。

f:id:hachinobu:20140417134442p:plain

原因

この現象になる原因としてはiOS6以前でUINavigaitonBarに画像を下記のように設定していた。

[self.navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:@"header-44.png"] forBarMetrics:UIBarMetricsDefault];

header-44.pngは名前の通り高さが44ptなので、iOS7からはステータスバー領域も含めてsetBackgroundImage:に指定した画像が適用されるためステータスバー領域が黒く塗りつぶされていた。

setBackgroundImage: forBarPosition: barMetrics:の検証

iOS7用に setBackgroundImage: forBarPosition: barMetrics: メソッドが新しく使えるようになりforBarPosition:に指定するUIBarPositionを適切に設定することで背景がステータスバー領域の上まで延びて表示されるようになるとドキュメントに記載されていたので試してみた。

しかしどれも変わらずステータスバー領域が黒く塗りつぶされたまま。。

UIBarPositionBottomを指定した場合には画像を認識してくれなかった。

対応方法

結局のところ素直にUINavigaitonBarに設定する画像の高さをステータスバー領域も含めた64ptの画像を作成してOSのバージョンによってsetBackgroundImage: forBarMetrics:メソッドで指定する画像を切り替えて対応した。

NSString *headerImageName = @"header-64";
//iOS6以下である場合は高さ44ptの画像を設定
if ([[[UIDevice currentDevice] systemVersion] floatValue] < 7.0f) {
    headerImageName = @"header-44";
}
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:headerImageName] forBarMetrics:UIBarMetricsDefault];

f:id:hachinobu:20140417140538p:plain