前回に引き続き、
import(Cocoa | UIKit)
Objective-CではCにおけるlibc
に相当するのが、Cocoa
、UIKit
です。ただしlibc
よりできることははるかに多彩です。たとえば、libc
では簡単には書けませんが、Cocoa
やUIKit
であればわずかこれだけです
import Cocoa // OS X の場合。iOS ならUIKit
let url = "http://example.com/"
var enc = NSUTF8StringEncoding
var err:NSError?
if let content = NSString(
contentsOfURL: NSURL(string:url)!,
usedEncoding:&enc,
error:&err
) {
println(content)
} else {
println(err)
}
なんと、NSString()
という文字列を初期化するAPIに適切なパラメータを渡すだけで、

この場合URLのコンテンツはテキストですが、
import Cocoa
let url = "http://api.dan.co.jp/asin/4534045220.json"
var enc = NSUTF8StringEncoding
var err:NSError?
if let content = NSString(
contentsOfURL: NSURL(string:url)!,
usedEncoding:&enc,
error:&err
) {
if let json = NSJSONSerialization.JSONObjectWithData(
content.dataUsingEncoding(enc)!,
options: nil, error: &err) {
println(json)
} else {
println(err)
}
} else {
println(err)
}
しかしパースしたJSONから特定のアイテムを抜き取りたいとなると、
import Cocoa
let url = "http://api.dan.co.jp/asin/4534045220.json"
var enc = NSUTF8StringEncoding
var err:NSError?
if let content = NSString(contentsOfURL: NSURL(string:url)!, usedEncoding:&enc, error:&err) {
if let json:AnyObject = NSJSONSerialization.JSONObjectWithData(
content.dataUsingEncoding(enc)!,
options: nil, error: &err) {
if let item = json["ItemAttributes"] as? NSDictionary {
if let author = item["Author"] as? NSString {
println(author)
}
}
} else {
println(err)
}
} else {
println(err)
}
JSONをサポートする多くの言語でjson["ItemAttributes"]["Author"]
と一度に書けるところを、let item = json["ItemAttributes"] as? NSDictionary
でitem
を取り出し、let author =item["Author"] as? NSString
とNSString
を取り出しという具合に、json["Item Attributes"]["Author"]
と書く方法はないでしょうか? さらに可能ならJavaScriptのようにjson.
と書けないのでしょうか?
ラッパーのススメ
その試みがSwiftyJSONであり、
let author = JSON(url:"http://api.dan.co.jp/asin/4534045220.json")["ItemAttributes"]["Author"].asString
と1行で済んでしまいます。さらにスキーマをclass
として実装すれば、
class ASIN : JSON {
override init(_ obj:AnyObject){ super.init(obj) }
override init(_ json:JSON) { super.init(json) }
var ItemAttributes: ASIN { return ASIN(self["ItemAttributes"]) }
var Author: String { return self["Author"].asString! }
}
let author = ASIN(url:"http://api.dan.co.jp/asin/4534045220.json").ItemAttributes.Author
SwiftyJSONやSwift-JSONはこれをどのように実現しているのでしょうか? ソースコード全体を読んでいただければ一目瞭然なのですが、
Swift-JSONのインスタンス変数は、
public class JSON {
private let _value:AnyObject
// ....
}
これに対し、subscript
は2種類定義されています
public subscript(idx:Int) -> JSON {
switch _value {
case let err as NSError:
return self
case let ary as NSArray:
if 0 <= idx && idx < ary.count {
return JSON(ary[idx])
}
return JSON(NSError(
domain:"JSONErrorDomain", code:404, userInfo:[
NSLocalizedDescriptionKey:
"[\(idx)] is out of range"
]))
default:
return JSON(NSError(
domain:"JSONErrorDomain", code:500, userInfo:[
NSLocalizedDescriptionKey: "not an array"
]))
}
}
public subscript(key:String)->JSON {
switch _value {
case let err as NSError:
return self
case let dic as NSDictionary:
if let val:AnyObject = dic[key] { return JSON(val) }
return JSON(NSError(
domain:"JSONErrorDomain", code:404, userInfo:[
NSLocalizedDescriptionKey:
"[\"\(key)\"] not found"
]))
default:
return JSON(NSError(
domain:"JSONErrorDomain", code:500, userInfo:[
NSLocalizedDescriptionKey: "not an object"
]))
}
}
つまり、json[0]
のように添え字がInt
であればインスタンス変数をNSArray
とみなし、json["name"]
のように添え字がString
であればNSDictionary
とみなして、JSON
オブジェクトを生成しているわけです。そして要素が存在しない場合は、NSError
からJSON
オブジェクトを生成し、NSError
の場合はそのまま自分自身を返すことで、Either
が一度Nothing
になればずっとNothing
であるように、
このようなラッパーは同等の機能をフルスクラッチでSwiftで書くよりずっと簡単に書けますし、
AnyObjectとAnyの違い
SwiftのAnyObject
は、id
に相当します。id
同様なんでも入りますが、is
で適切な型を判定したり、as
で適切な型に変換したりしなければなりません。
また、Cocoa
やUIKit
など、import
しておく必要もあります
import Cocoa
var ao:AnyObject
ao = "assign"
// ao += " any value" // error
ao = (ao as String) + " any value"
ao = 40
// ao += 2 // error
ao = (ao as Int) + 2

ところがSwiftにはAnyObject
とは別にAny
という型も存在します。前述のAnyObject
をAny
に変えてもそのまま動いてしまいますしimport Cocoa
をコメントアウトしてもそのまま動いてしまいます

なぜ、
sizeof()
で双方の型を見てみると、sizeof(AnyObject)
は8なのに対し、sizeof(Any)
は32。AnyObject
は1ワード、Any
は4ワードです。賢明な読者であれば、AnyObject
は参照、class
であるのに対し、Any
は実値、struct
なのです。
さらにunsafeBitCast
を使ってAny
がどうなっているのかを見てみましょう
import Cocoa
let s = "Swift"
var a:Any
a = s
var aq = unsafeBitCast(a, (UInt,UInt,UInt,UInt).self)
var sq = unsafeBitCast(s, (UInt,UInt,UInt).self)
let i = 42
a = i
aq = unsafeBitCast(a, (UInt,UInt,UInt,UInt).self)
var a1 = unsafeBitCast((42,0,0,aq.3), Any.self)
a as Int == a1 as Int

なんのことはない。4ワードのうち頭から本来の値を詰め込んだうえで、Struct
は、Int
やDouble
が1ワード、String
やArray
やDictionary
が3ワードなので、Any
の中にすべて納まります。
これに対し、AnyObject
の正体は、id *
、Any
のように値そのものの一部ではなくその参照先に格納されています。
それではAnyはどこで使われているかというと、reflect()
という関数がありますが、Any
を活用している関数の1つで、
しかしそうでもない限り、Any
を使うケースはほとんどないでしょう。以前紹介したようにSwiftには総称関数とプロトコルがあるので、Any
の使用は避けるべきです。まとめると次のようになるでしょう。
AnyObject
は、Objective-Cで書かれたフレームワークの連携においてのみ使う Any
は使わない(複数の型を受け付けるコードには、 総称関数とプロトコルを用いる)
続きは次号
今回はSwiftからObjective-Cのフレームワークを用いる例としてSwift-JSONを紹介し、AnyObject
とAny
の違いを垣間見ました。次回はXcodeでCおよびObjective-CのコードとSwiftのコードを同一のプロジェクトで連携する例を見ていくことにします。
本誌最新号をチェック!
Software Design 2022年9月号
2022年8月18日発売
B5判/
定価1,342円
- 第1特集
MySQL アプリ開発者の必修5科目
不意なトラブルに困らないためのRDB基礎知識 - 第2特集
「知りたい」 「使いたい」 「発信したい」 をかなえる
OSSソースコードリーディングのススメ - 特別企画
企業のシステムを支えるOSとエコシステムの全貌
[特別企画] Red Hat Enterprise Linux 9最新ガイド - 短期連載
今さら聞けないSSH
[前編] リモートログインとコマンドの実行 - 短期連載
MySQLで学ぶ文字コード
[最終回] 文字コードのハマりどころTips集 - 短期連載
新生「Ansible」 徹底解説
[4] Playbookの実行環境 (基礎編)