普段はWeb屋で日常的にPHPやJavascriptを使っていますが、iOS開発用の新言語「Swift」を学習したのでメモを残しておきます。
基本
行末に「;(セミコロン)」のような記号はいらない。
println("hoge")
PHPやJavascriptなどに慣れていると付けないとなんだか不安感がありますね。
変数
変数は「var」を頭につけて「変数名:型」で宣言する。
var msg:String
msg = "hello world"
println(msg)
以下のようにも書けるそうです。
var msg = "hello world"
println(msg)
なんと型宣言が不要らしいです。
後者のパターンだとほとんどJavascriptって感じですよね。
文字列内での変数の展開は以下のように「\()」を使います。
var msg = "Hello World"
println("aaa\(msg)aaa")
ちなみにバックスラッシュはMacだとoption + ¥ で打てます(知らなかった)。
定数
定数の定義には「let」を使います。
let teisuu = "hoge"
println(teisuu)
データ型
/*
文字列型
String "hoge"
数値型
Int 999
浮動小数点型
Float/Double 7.777
Bool型
Bool true false
何もない場合
nil
*/
C言語やJavaを少しでもかじったことがあったり、MySQLなどのデータベースの知識がある人なら何となく理解できるかと。
データが無いことを示すのが「nil」で「null」ではないんですね。
ちなみに上のコード部分を見てわかるように、複数行コメントはCSSと同じ/**/が使えるそうです。
演算
演算には定番の「+ – * / %」が使えます。この辺は言語が変わっても一緒ですね。
var x : Float = 5.0 * 2
もちろんインクリメントとデクリメントも使えます。
var x = 1
x++
var y = 10
y--
文字列の連結
文字列の連結には「+」が使えます。
var x = "ho" + "ge"
これだけ見るとJavascriptのセミコロン無しって感じですね。
論理演算子
これも定番の「&&」「||」「!」が使用可能です。
true && false
true || false
!true
型の変換
型を一致させないと文字列の連結などは行えません。これがWeb周辺ではあまり馴染みがない概念なんですよね。
//ダメな例
var x = "abc"
var y = 777
var z = x + y
//良い例
var x = "abc"
var y = 777
var z = x + String(y)
こんな感じで型を変換して同じにしてあげれば接続できるようになります。
タプル
タプルとはどうやら配列のようなもので()でかこってカンマ区切りで定義し、取り出す時はJavascriptのオブジェクトのようにドットと番号指定でできます。
let error = (404,"not found")
error.0
error.1
<div class="code"><pre><code></code></pre></div>
連想配列やハッシュ的な使い方もできるようです。
<div class="code"><pre><code></code></pre></div>
let error = (code:404,msg:"not found")
error.code
error.msg
こんな感じで複数の値を渡すタプルを定義することができます。
以下のようにして使うこともできるみたいです。
let error = (404,"not found")
let (code,msg) = error
上記のようにするとcodeに404が、msgに「not found」が代入された状態になります。
値の安全な破棄
swiftでは値の破棄の際には「_(アンダーバー)」を使います。
let error = (404,"not found")
let (code,_) = error
前項の例の流用ですが、こうすることでnot foundの部分を安全に破棄するそうです。
「安全に破棄」という意味が実はよく分かってはいないんですが(笑)。
配列
swiftでの配列の定義は配列名に型を宣言し、[]で囲ってカンマ区切りです。
var colors : [String] = ["blue","pink"]
colors[0]
最初に型を宣言するので指定した型以外のデータ型が入れられないことに注意が必要とのことです。
配列操作系の関数は以下のようなものがあります。
//配列の要素数をカウント
colors.count
//配列の内容チェック
colors.isEmpty
//配列の末尾に要素を追加
colors.append("green")
//配列の指定位置に要素を追加
colors.insert("gray", atIndex: 1)
//配列の最後の項目を削除
//colors.removeLast()
//空の配列を生成
var emptyArray = [String]()
この辺はJavascriptに似ていますね。
辞書
辞書とは連想配列やハッシュ的なものですが、値の型だけでなく添字の部分の型も指定しなければならない点に注意が必要かもしれません。
var fruits : [String: Int] = [
"banana" : 500,
"apple" : 100,
"orange" :300
]
fruits["apple"]
配列とほぼ同じなのですが辞書操作系の関数は以下のようなものがあります。
//辞書要素数のカウント
fruits.count
//辞書要素のチェック
fruits.isEmpty
//辞書に追加
fruits["peach"] = 250
//辞書から削除
fruits.removeValueForKey("apple")
//辞書の要素を更新 1
fruits["banana"] = 1000
//辞書の要素を更新 2(更新前の値が取得できるパターン)
fruits.updateValue(1000, forKey: "banana")
//辞書のキーの一覧を取得
var keys = Array(fruits.keys)
//辞書の値の一覧を取得
var values = Array(fruits.values)
//空の辞書を作成
var emptyDictionary = [String: Int]()
PHPのarray関数とは書き方が同じで機能が違うものがあるのでそこに注意が必要ですね。
条件分岐
条件分岐に関しては他の言語とほぼ同じですね。特徴的なのは条件式の前後に()をつけないことでしょうか。
基本的なif文は以下のように書きます。
// > >= < <= == != && || ! が使えます
var num = 10
var msg = ""
if num > 80 {
msg = "Great"
} else if num > 60 {
msg = "Good"
} else {
msg = "Bad"
}
三項演算子で書くことももちろん出来ます。
msg = num > 80 ? "Great" : "Bad" ;
switch文は以下のように書くことができます。
num = 0
switch num {
case 0:
println("zero")
fallthrough //次の処理も行う場合
case 1,2,3: //複数指定の場合
println("small")
case 4...6: // 4,5,6の場合
println("4/5/6")
case 7..<9:// 7,8 9は含まれない
println("7/8")
case var cnt where cnt > 10:
println("hoge")
default:
println("default")//何も無い場合はbreak
}
使える条件式は少々特殊なものがありますが、大きなポイントとしてはPHPのようにbreakを指定しなくても基本的に該当があった場合はその部分だけ実行されること。
逆に続けて処理したい場合には「fallthrough」を自分で指定してあげなくてはなりません。
これはPHPのほうがおかしいとずっと思っていたのでむしろSwiftのこの仕様には納得です。
繰り返し処理
繰り返し処理も例によって条件式の前後に()が不要なこと以外は同じですね。
while文(do while文)は以下のように書きます。
var n = 0
//while文
while n < 10 {
println(n)
n++
}
//do while文
do {
println(n)
n++
}while n < 10;
for文の場合は以下のように書きます。
for var i = 0; i < 10; i++ {
println(i)
}
範囲演算子を使うと以下のように書くこともできます。
for i in 0...9 {
println(i)
}
PHPやJavascriptにはこの0…9みたいな「範囲演算子」が無いので見慣れませんね。
同じWeb系でもRubyやPythonにはあったと思いますが。
ループから抜ける場合はcontinueとbreakが使えます。
for var i = 0; i < 10; i++ {
if i == 5 {
continue//またはbreak
}
println(i)
}
配列を一気に表示したい場合などにはfor inを使います。
var a = [5,10,15]
for i in a {
println(i)
}
辞書を表示したい場合は以下のようにします。
var d = ["taro":100 , "jiro":200]
for (k,v) in d {
println("key:\(k) value:\(v)")
}
上の項でやった「タプル」を何に使うのかわかっていませんでしたが、ここで(k,v)みたいな感じで使うみたいです。
Optional
swiftではnil(空)になる可能性がある場合は型に?をつけてoptionalとしなければならないみたいです。
//これはエラーになります
var s: String
s = nil
//こうすればOK
var s: String?
s = nil
?をつけて定義したoptionalのものを取り出すときは!をつけて「アンラップ」しないとエラーになります。
//これはエラーになります
var name: String? = "Taro"
var msg = "Hello " + name
//こうすればOK
var name: String? = "Taro"
var msg = "Hello " + name!
ちなみにアンラップする項目は事前にnil(空)でないことを確認しなければならず、nilをそのままアンラップしてもエラーになって表示されません。
if name != nil {
var msg = "Hello " + name!
}
//以下のようにも書ける。代入することによりnilでないかを確認
if var s = name {
var msg = "Hello " + s
}
後者の場合は一度sに代入されているので!をつけてアンラップする必要はありません。
暗黙的にアンラップされる項目の場合は以下のように書きます。
var label: String!
label = "score"
println(label)
どうやらアプリの初期化などに必要になる書き方らしいです。
初期化の時に一瞬だけnilになるから、ということのようですが、よくわかりません。
optionalは「安全に扱えるようにする」ということらしいのですが、Web出身の私には理解が難しいところです。
関数
関数に関してはfunctionではなくfuncと書いて宣言することと、引数の型を書かなければならない点に注意が必要ですね。
func kansu(name:String){
println("Hello " + name)
}
kansu("Taro")
また、複数の値を引数で渡す場合はどの引数がどの引数定義に対応するかを以下のように明示しなければなりません。
//hogeはnameと引数宣言で書く
func kansu(hoge name:String){
println("Hello " + name)
}
kansu(hoge: "Taro")
//同じ名前を使う場合は#をつけるだけで良い
func kansu(#name:String){
println("Hello " + name)
}
kansu(name: "Taro")
引数は初期値を設定しておく場合は以下のようにします。
func kansu(name:String = "Taro"){
println("Hello " + name)
}
kansu()
関数の返り値が欲しい場合は -> Int のようにして予め返り値の型について宣言しておかなければなりません。
func sum(a:Int,b:Int) -> Int {
return a + b
}
println(sum(7,8))
複数の返り値をとる場合はタプルを使って以下のように書きます。
func swap(a:Int,b:Int) -> (Int, Int) {
return (b,a)
}
println(swap(7,8))
タプルはこういう時に使うものなんですね。
関数を使って変数の中身を書き換える時はinoutを宣言しなければなりません。
func f(inout a:Int){
a = 20
}
var a = 5
f(&a)
a
inoutをつけずにわたすと、aのコピーがわたされるだけで本体ではないので、inoutを使うことで変数を書き換えることができるようになります。
列挙型
馴染みのない言葉ですが、どうやらJavascriptのオブジェクトのようなもの、と思えば良さそうです。中に関数などを含めることもできます。
列挙型の名前の先頭は必ず大文字でなければなりません。
//列挙型定義
enum Result {
case Success
case Error
}
//変数をResult型で作成
var r:Result
//取り出し方
r = Result.Success
r = .Success //上と同じ意味で短く書ける
関数を使ったり列挙型自体にデータ型を定義する場合は以下のようにします。
//列挙型定義
enum Result: Int {
case Success = 0
case Error = 9
func getMsg() -> String {
switch self {// self にデータが入ってくる
case .Success:
return "OK"
case .Error:
return "NG"
}
}
}
//変数をResult型で作成
var r:Result
//値を取り出す場合はrawValue
Result.Error.rawValue
//関数にアクセス
Result.Error.getMsg()
selfにデータが入ってくるというのを覚えておきたいですね。
クラス
クラスの基本的な書き方はPHPと同じです。
クラスのプロパティは初期化されている(0が指定されているとか)か、Optional型でなければいけません。
//optional型を使う場合
class User {
var name: String?
var score: Int = 0
func upgrade() {
score++
}
}
//init(イニシャライザ)を作って初期化する場合
class User {
var name: String
var score: Int = 0
init(name:String) {
self.name = name
}
func upgrade() {
score++
}
}
var taro = User(name: "Taro")
taro.name
taro.upgrade()
taro.score
なるほど、Optional型はここで使って、エラーを防ぐ役割を果たしてくれるわけですね。
クラスの継承は定義の際に クラス名:継承元クラス名 と書くことで継承できるようになります。
class User {
var name: String
var score: Int = 0
init(name:String) {
self.name = name
}
func upgrade() {
score++
}
final func hoge(){//finalでoverride禁止
println("hoge")
}
}
class AdminUser: User {
func reset(){
score = 0;
}
override func upgrade(){//元のメソッドをoverrideで上書き
super.upgrade()//superで親クラス(User)にアクセス
score + 3
}
}
var jiro = AdminUser(name:"Jiro");
要点のみ書いていくと、overrideでメソッドを上書き、finalでオーバーライド禁止、superで親クラスにアクセスです。
プロトコル(クラス)
プロトコルを使うと継承関係の無いクラスにも同じ機能を持たせたり、その存在を保証することができるようになります。
単純に「ルール」と言ってしまったほうが理解は早いと思います。
protocol Student {
var studentId: String{ get set }//権限設定(読み取り、設定可能)
func study()
}
class User: Student {
var name: String
var score: Int = 0
var studentId: String = "hoge"
func study(){
println("studying")
}
init(name:String) {
self.name = name
}
func upgrade() {
score++
}
}
この例だと、protocolのプロパティであるstudentIdとメソッドであるstudy()をUserクラスで実装しないとエラーになります。
こうすることで必ずクラスにその機能やプロパティが実装されている状態を保証するわけですね。
クラス(動的にプロパティを変更する)
プロパティはget/setを使って動的に設定することが可能です。
class User {
var name: String
var score: Int = 0
//動的にプロパティを設定
var level: Int {
get {
return Int(self.score / 10)
}
set {
score = Int(newValue * 10)
}
}
init(name:String) {
self.name = name
}
func upgrade() {
score++
}
}
var taro = User(name: "Taro")
//get
taro.score = 83
taro.level
//set
taro.level = 35
taro.score
他にプロパティの状態を監視して動的に設定することもできます。その場合はwillSet(変わる前)とdidSet(変わった後)を使います。
class User {
var name: String
//状態を監視
var score: Int = 0 {
//変わる前の処理
willSet {
println("\(score) -> \(newValue)")
}
//変わった後の処理
didSet {
println("\(oldValue) -> \(score)")
}
}
init(name:String) {
self.name = name
}
func upgrade() {
score++
}
}
var taro = User(name: "Taro")
taro.score = 32;
Optional Chaining
オプショナルチェイニングとは、クラスにプロパティやメソッドが存在するか確認するための機能です。
optionalの場合に!でアンラップした箇所を?にしてあげることで無い場合はnilを返してくれるようになります。
class User {
var blog: Blog?
}
class Blog {
var title = "My Blog"
}
var taro = User()
taro.blog = Blog()
taro.blog?.title
if var t = taro.blog?.title {
println(t)
}
クラス(型チェック・クラスの変換)
クラスの型をチェックしたり、揃えたりする?のに使われるがTypeCastingという機能です。
class User {
var name: String
init(name: String){
self.name = name
}
}
class AdminUser: User {}
var taro = User( name: "Taro" )
var jiro = AdminUser( name: "Jiro" )
var users = [taro,jiro]
for user in users {
//基本的な書き方
if user is AdminUser {
var u = user as AdminUser
println(u.name)
}
//短い書き方
if var u = user as? AdminUser {
println(u.name)
}
}
正直なところ、どう使うのかがまったく理解できていない状態です・・・。
ついでにメモしておくと、AnyObjectを使うと配列に型の区別なく何でも入れられるようになるそうです。
//※Saburoは継承関係の無いものとする
var users: [AnyObject] = [Taro,Jiro,Saburo]
構造体
構造体とは継承のできないクラスのようなものらしいです。
structを使って定義し、構造体の中でのメソッドでプロパティにアクセスできるようにするにはmutatingというキーワードを使用する必要があります。
struct UserStruct {
var name: String
var score : Int = 0
init(name: String){
self.name = name
}
mutating func upgrade() {
score++
}
}
class User {
var name: String
var score : Int = 0
init(name: String){
self.name = name
}
func upgrade() {
score++
}
}
構造体は値渡しで、クラスは参照渡し、であることに注意が必要です。
その特性上、クラスはメモリを効率的に使用でき、構造体はメモリを圧迫する傾向にあるそうなので、小さなデータは構造体、大きなデータはクラスにすれば良いらしいです。
・・・というか、それなら全部クラスで良いのではなかろうかと・・・???
extensionで機能拡張
これはJavascriptでprototypeを拡張するのと一緒ですね。
extension String {
var size: Int {
return countElements(self)
}
func dummy() -> String {
return "dummy"
}
}
var s: String = "hoge"
s.size
s.dummy()
ジェネリクス
似たような機能を持つ関数を、データ型毎に用意するのではなく、どんなデータ型でも扱えるようにする便利な機能がジェネリクスです。
まずはありがちな例。
func getIntArray(item: Int, count: Int) -> [Int] {
var result = [Int]()
for i in 0..<count {
result.append(item)
}
return result
}
getIntArray(7, 3)
これでIntの部分をStringやFloatなどと書き換えながらいくつも関数を用意するのは面倒です。
そこで関数名の後ろに
func getArray<T>(item:T, count:Int) -> [T] {
var result = [T]()
for _ in 0..<count {
result.append(item)
}
return result
}
getArray(7, 3)
getArray("hello", 3)
getArray(2.3, 3)
ちなみにTじゃなくても問題はないらしいですが、習慣的にTが用いられているようです。
学習を終えて
ザーッとSwiftの基本仕様について勉強してきましたが、PHPやJavascriptにあるものの理解は簡単なものの、やはり型についてのアレコレがよく分からないのが実状です。
私だけに限らずWebからプログラミングの世界に入った人にとってこの「型」の存在がネックになってきそうな予感はします。
とりあえずこれで基本仕様を一通り終えたので、次からはiOS開発を実践しながら学習していきたいと思います。