前回に引き続き、
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の実行環境 (基礎編)
