酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

Swiftを使って文字列操作する

文字列操作する方法を手癖で書けるほどSwiftに馴染んでいないので、いつでも見返せるようにメモしておきます。

文字列同士の結合

let str = "daru" + "yanagi"
println(str)
var str = "daru"
str += "yana"
str += "gi"
println(str)

区切り文字を使って文字列を分割する

split関数を使って分割します。

let source = "daruyanagi,shibayan,daisuke_nomura"
let words = split(source) { $0 == "," }
println(words)

上記のサンプルコードを実行してみました。

f:id:ch3cooh393:20150530102241p:plain

componentsSeparatedByStringメソッドを使う方法の方が直感的かもしれません。

let source = "daruyanagi,shibayan,daisuke_nomura"
let words = source.componentsSeparatedByString(",")
println(words)

応用例:区切り文字を使ってInt型の数値として分割する

特定の区切り文字を使って複数の数値を持つことがあります。

特にcsvでこのように表現することが多いと思うのですが、例えば「"3:6:8"」のようにひとつのフィールドに複数の数字が含む方法があります。Swiftを使ってInt型の配列として分割してしまいましょう。

let source = "3:6:8"
let words = source.componentsSeparatedByString(":")
let numbers = words.map({ (value) -> Int in return value.toInt()! })
println(numbers)

上記のサンプルコードを実行してみました。

f:id:ch3cooh393:20150530104407p:plain

配列を区切り文字を使って結合する

join関数を使って文字列を結合します。この時使用する区切り文字は,といった1文字に限らず、好きな文字列を使用することができます。以下の例ではxを使用しています。

let source = ["daruyanagi", "shibayan",  "daisuke_nomura" ]
let words = join(" x ", source)
println(words)

上記のサンプルコードを実行してみました。

f:id:ch3cooh393:20150530102107p:plain

小文字を大文字に変換する

let source = "daruyanagi"
println(source.uppercaseString)

上記のサンプルコードを実行してみました。

f:id:ch3cooh393:20150530102948p:plain

逆に「DARUYANAGI」という大文字の文字列を小文字に変換するためには、lowercaseStringメソッドを使うと良い。

let source = "DARUYANAGI"
println(source.lowercaseString)

ユニコードの値(U+ 9312)をString型に変換する

Swiftで数字をUnicodeScalarクラスを使って①や⑩の丸囲み文字に変換する

①や⑩というのを丸囲み文字と呼ぶようです。数値から丸囲み文字に変換する必要があったので調べてみました。

⓪,
①,②,③,④,⑤,⑥,⑦,⑧,⑨,⑩,
⑪,⑫,⑬,⑭,⑮,⑯,⑰,⑱,⑲,⑳,
㉑,㉒,㉓,㉔,㉕,㉖,㉗,㉘,㉙,㉚,
㉛,㉜,㉝,㉞,㉟,㊱,㊲,㊳,㊴,㊵,
㊶,㊷,㊸,㊹,㊺,㊻,㊼,㊽,㊾,㊿,

数字をUnicodeScalarクラスを使って①や⑩の丸囲み文字に変換する

0から50までのInt型の値をUnicodeScalarクラスを使って、⓪から㊿までのString型に変換してみましょう。これらの数値は何故かコード上に並んでいるわけではなくて点在しています。

private func convertEnclosedNumber(num: Int) -> String? {
    if num < 0 || 50 < num {
        return nil
    }
        
    var char: String? = nil
    if 0 == num {
        var ch = 0x24ea
        char = String(UnicodeScalar(ch))
    } else if 0 < num && num <= 20 {
        var ch = 0x2460 + (num - 1)
        char = String(UnicodeScalar(ch))
    } else if 21 <= num && num <= 35 {
        var ch = 0x3251 + (num - 21)
        char = String(UnicodeScalar(ch))
    } else if 36 <= num && num <= 50 {
        var ch = 0x32b1 + (num - 36)
        char = String(UnicodeScalar(ch))
    }
    return char
}

convertEnclosedNumberメソッドを使って最初に書いた出力するためのサンプルコードです。

for n in 0..<51 {
     if let num = convertEnclosedNumber(n) {
         print("\(num),")
     }
}

追記:⓪もあった

1〜50までしかないと思い込んでました。0を指定された場合でも丸囲み文字に変換するように修正しました。

関連記事

Swiftでreduceメソッドを使って配列の中から文字列長の長いオブジェクトを取得する

Swiftで配列の中から文字列長の長いオブジェクトを取得する方法を紹介します。

ここではString型の配列特定のクラスのString型プロパティの配列から最も長い文字列を持つオブジェクトを取得します。判定の仕方を変更すれば文字列以外での判定をおこなうことも可能です。

スワローズの投手から適当にピックアップしてきました。後述する配列の中で一番名前が長いのは「金伏 ウーゴ」選手です。

String型の配列で最も長い文字列を取得する

文字数を取得するためにはcountメソッドを使います。String型の配列stringsのreduceメソッドを使います。reduceメソッドは与えられたシーケンスを1つの値にまとめることができます。シーケンスの合計を求めることに使われるケースがよく紹介されています。

// 人物名を格納した配列
var strings = ["由規", "石山 泰稚", "秋吉 亮",
    "村中 恭兵", "バーネット", "金伏 ウーゴ"]

// 一番長い人名の選手を取得する
let name = strings.reduce(strings[0], combine: {
    if count($0) > count($1) {
        return $0
    } else {
        return $1
    }
})

// 出力
println(name)

上記のサンプルコードのPlaygroundでの出力を見てみましょう。

f:id:ch3cooh393:20150504121825p:plain

特定のクラスのString型のプロパティで最も長い文字列を持つオブジェクトを取得する

応用例です。例えば、以下のようなPersonクラスがあるとします。プロパティにはString型のnameだけを持っています。

class Person {
    let name: String
    
    init(_ name: String) {
        self.name = name
    }
}

前述した比較方法と同じようにreduceメソッドを使って一番長い名前の選手を取得しています。

// 人物名を格納したPerson
var array = [Person]()
array.append(Person("由規"))
array.append(Person("石山 泰稚"))
array.append(Person("秋吉 亮"))
array.append(Person("村中 恭兵"))
array.append(Person("バーネット"))
array.append(Person("金伏 ウーゴ"))

// 一番長い人名の選手を取得する
let maxElement = array.reduce(array[0], combine: {
    if count($0.name) > count($1.name) {
        return $0
    } else {
        return $1
    }
})

// 出力
println(maxElement.name)

上記のサンプルコードのPlaygroundでの出力を見てみましょう。

f:id:ch3cooh393:20150504115241p:plain

Swiftでリフレクションを使ってインスタンスからプロパティの一覧を取得する

Swiftでインスタンスからプロパティの一覧を取得してみましょう。以下のようなPersonクラスがあるとします。

@objc(Person)
class Person : NSObject {
    let age: Int
    let name: String
    
    required init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
}

reflectメソッドを使ってオブジェクトに定義されているプロパティの一覧を取得します。

let object = Person(age: 17, name: "さくさん")
        
var r = reflect(object)
for var i = 0; i < r.count; i++ {
     let name: String = r[i].0
     let type: Any.Type = r[i].1.valueType
            
     println("\(name): \(type)")
}

出力:
super: rockinonTests.Person
age: Swift.Int
name: Swift.String

インスタンスの内容をダンプする

プロパティの一覧を取得するという目的とは若干異なりますが、dumpメソッドを使うことでインスタンスの内容を出力させることができます。

let object = Person(age: 17, name: "さくさん")
dump(object)

出力:
▿ rockinonTests.Person #0
  - super: <Person: 0x7ff57bc59a20>
  - age: 17
  - name: さくさん

Swiftで文字列のクラス名からインスタンスを動的に生成させる

文字列からインスタンスを生成する方法として、Objective-Cでは「文字列のクラス名からインスタンスを動的に生成させる - iOSアプリ開発の逆引き辞典」で紹介しているようなコードを書くことで文字列のクラス名称からインスタンスを動的に生成させることができました。

Swiftとして文字列からインスタンスを動的に生成する方法は見つけることができませんでした。Objective-Cの力を借りる方法しかわかりませんでした。

文字列のクラス名からインスタンスを生成する

以下のようなDogクラスがあるとしましょう。名前は「まろにー」です。

@objc(Dog)
class Dog : NSObject {
    let name: String
    
    override init() {
        self.name = "まろにー"
    }
}

Objective-Cの時と同様にNSClassFromString関数を使用します。

// 文字列から型情報を取得する
let obj = NSClassFromString("Dog") as! NSObject.Type

// Dogインスタンスを生成する
let dog = obj() as! Dog

// 出力
println("私は犬である。名前は\(dog.name)")

文字列のクラス名からコンストラクタ引数付きでインスタンスを生成する

前述した方法では引数付きのコンストラクタを呼び出すことができず、デフォルトコンストラクタが呼ばれることになってしまいます。

今度は以下のようなPersonクラスのインスタンスを生成させてみましょう。

@objc(Person)
class Person : NSObject {
    let age: Int
    let name: String
    
    required init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
}

先ほどのようにNSClassFromString関数を使用して、NSObject.Type型のオブジェクトを取得します。

 // 文字列から型情報を取得する
let obj: NSObject.Type = NSClassFromString("Person") as! NSObject.Type
if !(obj is Person.Type) {
    // 型情報が取得出来なかった場合は処理しない
    return
}
        
// NSObject.TypeをPerson.Typeにキャストする
let type = obj as! Person.Type
        
// インスタンスを生成する
let person = type(age: 17, name: "さくさん")

// 出力
println("\(person.name)\(person.age)歳でしゅ")

otoolを使ってiOSアプリで使っているframeworkを解析する

ipaファイルの自動生成をするようになると、ipaファイルそのものの解析を依頼されることがあります。僕はバイナリアンではないのでipaファイルを投げられてもよくわかりません。

ipaファイルの正体は、zipファイルの拡張子を変えただけであるものというのは周知の通りだと思います。例えば、Example.ipaの拡張子を.zipに変更して解凍します。中身がPayload/Example.appとなっているのに気付くと思います。Example.appを右クリックして、コンテキストメニューから「パッケージの中身を表示」を選択します。

ずらずら〜とアプリで利用している画像リソースやnibファイルが並んでいます。

  • Payload/Example.app
    • _CodeSignature
    • hoge.png, hoge.jpg
    • Example (Unix実行ファイル)

ターミナルでotool -helpと打ち込むとオプションの説明が表示されます。

otool -help

Usage: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool [-arch arch_type] [-fahlLDtdorSTMRIHGvVcXmqQjC] [-mcpu=arg] [--version] <object file> ...
    -f print the fat headers
    -a print the archive header
    -h print the mach header
    -l print the load commands
    -L print shared libraries used
    -D print shared library id name
    -t print the text section (disassemble with -v)
    -p <routine name>  start dissassemble from routine name
    -s <segname> <sectname> print contents of section
    -d print the data section
    -o print the Objective-C segment
    -r print the relocation entries
    -S print the table of contents of a library
    -T print the table of contents of a dynamic shared library
    -M print the module table of a dynamic shared library
    -R print the reference table of a dynamic shared library
    -I print the indirect symbol table
    -H print the two-level hints table
    -G print the data in code table
    -v print verbosely (symbolically) when possible
    -V print disassembled operands symbolically
    -c print argument strings of a core file
    -X print no leading addresses or headers
    -m don't use archive(member) syntax
  -B force Thumb disassembly (ARM objects only)
  -q use llvm's disassembler (the default)
    -Q use otool(1)'s disassembler
  -mcpu=arg use `arg' as the cpu for disassembly
    -j print opcode bytes
    -C print linker optimization hints
    --version print the version of /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool

Payload配下にある実行ファイルであるExampleというパスを指定して、otool -L Payload/Example.app/Exampleを実行すると、使用しているframeworkを知ることができます。

otool -L Payload/Example.app/Example

Example.app/Example:
    /System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration (compatibility version 1.0.0, current version 432.0.0)
    /usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.5)
    /System/Library/Frameworks/MobileCoreServices.framework/MobileCoreServices (compatibility version 1.0.0, current version 34.0.0)
    /System/Library/Frameworks/CFNetwork.framework/CFNetwork (compatibility version 1.0.0, current version 548.0.3)
    /System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 881.0.0)
    /System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 1.0.0, current version 1600.0.0)
    /System/Library/Frameworks/CoreGraphics.framework/CoreGraphics (compatibility version 64.0.0, current version 600.0.0)
    /System/Library/Frameworks/OpenGLES.framework/OpenGLES (compatibility version 1.0.0, current version 1.0.0)
    /System/Library/Frameworks/QuartzCore.framework/QuartzCore (compatibility version 1.2.0, current version 1.7.0)
    /System/Library/Frameworks/MediaPlayer.framework/MediaPlayer (compatibility version 1.0.0, current version 1.0.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 161.1.0)
    /usr/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 6.0.0)
    /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (compatibility version 150.0.0, current version 675.0.0)
    /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)

Xcode 6.3が公開されました

なんでApple Watch発売直前になってiOS 8.3とXcode 6.3を提供を開始したの……という気もするのですが、今朝方iOS 8.3が公開されました。同時に、Swift 1.2が使えるようになるというXcode 6.3も公開されました。

リリースノートは以下のリンクからみることができます。

Xcode 6.2では日本語フォント等の問題を抱えていたiOSシミュレータ上のApple Watchシミュレータ(って言うの?)でしたが、Xcode 6.3で改善されていることを祈って現在インストールをしています。

案の定と言うかリリースノートには、WatchKit及びWatch関連の情報が記されていました。Known Issuesには特にハマりそうなことが書かれていました。

自動でWatchシミュレータが起動しないことがあるのでWatchKitアプリを起動する前に、Watchシミュレータを起動しておいてね*1ということらしいです。

When launching a WatchKit app on the iOS Simulator, the Watch Simulator may not automatically launch. Launch the Watch Simulator before launching the WatchKit app on the iOS Simulator. (19830540)

Xcode 6.3に対応したXamarin Studioはいつ出てくるんだろう。

*1:起動というかExternal Displayで選択しておく