-- Views
August 20, 16
スライド概要
Swift ランタイムで簡単に JavaScript を実行できる JavaScriptCore を Swift で使うための基礎のお話です。Swift と JavaScript の相互運用が簡単にできて面白いです。この資料は 2016/08/20 に iOSDC 2016 で発表したものになります。
※ Docswell での公開に移行する直前の Slideshare での閲覧数は 10,093 でした。
正統派趣味人プログラマー。プログラミングとは幼馴染です。
4XJGU Ͱ +BWB4DSJQU ࢝Ί·ͤΜ͔ʁ J04%$PO"VHVTU UI &;/&5۽୩༑ IUUQF[OFUKQ 4XJGU%FWFMPQFS1SFWJFX
Έͳ͞Μ +BWB4DSJQU͖Ͱ͔͢ʁ
+BWB4DSJQU ྑ͍Ͱ͢ΑͶʂ
+BWB4DSJQU ίʔυͷงғؾ for (var index in lines) { var line = lines[index]; var itemContent; if (line.match(expression)) { itemContent = RegExp.$2; } var outputItem = core.escapeHtml(itemContent); return outputItems.map(convertToTag).join('\n'); } +BWB4DSJQU
+BWB4DSJQU͕4XJGU͔Β͑Δʂ
8,8FC7JFX
+BWB4DSJQU$PSF GSBNFXPSL
۽୩༑ 5PNPIJSP,VNBHBJ ⾣ 4XJGU͕ޠݴେ͖Ͱ͢ʂ ⾣ ΈΜͳͰָ͠Ήษڧձ͕େ͖Ͱ͢ʂ ⾣ ϓϩάϥϛϯάͷָ͠͞Λ͍͖͍͑ͯͨɻ !FT@LVNBHBJ UPNPIJSPLVNBHBJ $PEF1JFDFGPSNBD04 IUUQF[OFUKQ 9DPEFపఈղઆ
ษڧձΛ։࠵͍ͯ͠·͢ɻ Θ͍Θ͍ɺָ͘͠ɺ ΈΜͳͰޠΒ͑ΔॴΛࢦͯ͠ɻ ԣJ1IPOF։ൃऀษڧձ ΧδϡΞϧ4XJGUษڧձ ZJEFW ˏԣɾഅंಓ DTXJGU !ԣɾ੨༿ ୈճ݄ͷ։࠵ʢඪʣ IUUQTBUOEPSHHSPVQTZJEFW ୈճΛ݄̏̕ʹ։࠵ IUUQTBUOEPSHHSPVQTDTXJGU
͏ͻͱͭɺษڧձΛ࢝Ί·͢ʂ 4XJGUͷૅجΛΈΜͳͱ෮श͍ͨ͠ɻ ͜Ε͔Β࢝ΊΔਓͱ্ͱऀڃҰॹʹɻ ΈΜͳͰ4XJGU ෮शձ NJOOB@EF@TXJGU ˏौ୩ ୈճΛ݄ʹ։࠵ IUUQDTXJGUDPOOQBTTDPN
ωοτϥδΦ Ќ์ૹ։࢝ ϓϩάϥϛϯά͕େ͖ͳ̎ਓͰɺ ϓϩάϥϜޠָ͍ͯͭ͘͠ʹޠݴΒ͏൪ NPPLNPPLSBEJP Ќ൛ ۽୩ͱ៸໘͕ϓϩάϥϛϯάίʔυͷ͔Β ௌ͑ͯ͘͜ΔʹࣖΛָ͚ͯ͠ΉϥδΦ IUUQNPPLNPPLSBEJPDPNB
$PEF1JFDFGPSNBD04 ษڧձΛָ͠ΉΞϓϦ ⾣ ιʔείʔυΛ5XJUUFSͱ(JTUʹಉ࣌ߘ ⾣ ίʔυཝΛۭʹ͢Εɺී௨ʹπΠʔτՄೳ ⾣ ϋογϡλάΛઃఆͰ͖Δʗ͚Εͳ͍ #iOSDC
4XJGUͰ+BWB4DSJQU ࢝Ί·ͤΜ͔ʁ
͡Ίʹ +BWB4DSJQUͱ͍͑ ⾣ खܰʹॻ͚ΔεΫϦϓτޠݴ ⾣ ʹొ ⾣ ొ͔࣌Βࠓ·Ͱ8FCํ໘Ͱ׆༂ ⾣ +BWB4DSJQUΛॻ͚Δਓͬͯଟ͍ͣ
͡Ίʹ +BWB4DSJQU$PSFͱ͍͑ ⾣ +BWB4DSJQUΛ࣮ߦͰ͖Δڥ ⾣ ίʔυϥϯλΠϜͰධՁ ⾣ 4XJGUͱͷ૬ޓӡ༻͕؆୯ʹͰ͖Δ
͡Ίʹ +BWB4DSJQU$PSFͷΠϝʔδ 4XJGU 0CKFDUJWF$3VOUJNF +47JSUVBM.BDIJOF PCKD +4$POUFYU 関数 ؔ ؔ クラス型 ΦϒδΣΫτ ΦϒδΣΫτ 変数 ม ม 文字列 変数 Ç㲋 Ç㲋 +BWB4DSJQUίʔυ ม
͡Ίʹ +BWB4DSJQU$PSFͷ༻ྫ ⾣ ࠶Ϗϧυͤͣʹ࣮Λࠩ͠ସ͑Δ ⾣ ΞϓϦͷಈ࡞ΧελϚΠζͷͨΊͷ ΧελϜεΫϦϓτڥΛఏ͖ͰڙΔ ⾣ +BWB4DSJQUίʔυΛͬͯ ωΠςΟϒΞϓϦΛ੍͖ͰޚΔ ⾣ 8FCܦ༝ͰΞϓϦͷ࣮Λมߋ͢Δ
4XJGUͰ+BWB4DSJQU ͬͯΈͨ͘ͳΓ·͔ͨ͠ʁ
Ͱ·ͬͯ ͪΐͬͱ͍ͳ͗͢ྗڧʁ
ΞϓϦ৹ࠪ௨Δͷʁ
΄Μͱ͢Έ·ͤΜ ˚A ͥΜͥΜߟ͑ͯ·ͤΜͰͨ͠ 2"Ͱͨ͠·͖͍ͩͨͯ͑ڭʂ ͪΌΜͱ৹ࠪ௨ΔΈ͍ͨʁ͋Γ͕ͱ͏͍͟͝·͢ʂ
͡Ίʹ +BWB4DSJQU$PSF ಈ࡞ڥ ⾣ J04Ҏ্ ⾣ NBD04.BWFSJDLTҎ্ ⾣ UW04Ҏ্ ެࣜจॻ "1*3FGFSFODF+BWB4DSJQU$PSF IUUQTEFWFMPQFSBQQMFDPNSFGFSFODFKBWBTDSJQUDPSF
4XJGUͰ+BWB4DSJQU ͬͯΈͨ͘ͳΓ·͔ͨ͠ʁ
+BWB4DSJQU$PSF جຊతͳ͍ํ
+BWB4DSJQU$PSFͷجຊ ཁॴ +BWB4DSJQU$PSFΛΠϯϙʔτ͢Δ +4$POUFYUΛੜ͢Δ +BWB4DSJQUίʔυΛ࣮ߦ͢Δ มͷΛ4XJGUͰऔಘ͢Δ ࣮ߦͱ݁ՌऔಘΛಉ࣌ʹߦ͏
+BWB4DSJQU$PSFͷجຊ +BWB4DSJQU$PSFΛΠϯϙʔτ͢Δ ⾣ +BWB4DSJQU$PSFϞδϡʔϧΛ͏ ⾣ ී௨ʹJNQPSUߏจͰΠϯϙʔτ͢Δ import JavaScriptCore 4XJGUCFUB
+BWB4DSJQU$PSFͷجຊ +4$POUFYUΛੜ͢Δ ⾣ ίϯςΩετͰঢ়ଶΛཧ͢Δ ⾣ ෳͷίϯςΩετΛ࡞Δ͜ͱՄೳ let context = JSContext()! 4XJGUCFUB
+BWB4DSJQU$PSFͷجຊ +BWB4DSJQUίʔυΛ࣮ߦ͢Δ ⾣ ίϯςΩετʹ+BWB4DSJQUίʔυΛૹΔ ⾣ ίʔυ4USJOHܕͷσʔλͰ࡞Δ context.evaluateScript("var v1 = 10") context.evaluateScript("var v2 = 20") context.evaluateScript("var v3 = v1 + v2") 4XJGUCFUB
+BWB4DSJQU$PSFͷجຊ มͷΛ4XJGUͰऔಘ͢Δ ⾣ 4XJGUଆͰ+47BMVFܕͷͱͯ͠औಘͰ͖Δ ⾣ ଘࡏ͠ͳ͍߹VOEFpOFEͱͯ͠ಘΒΕΔ let value = context.objectForKeyedSubscript("v3")! print("Answer = ", value) // Answer = 30 // 指定した名前が存在しない場合は undefined let value = context.objectForKeyedSubscript("vX")! print("Answer = ", value) // Answer = undefined 4XJGUCFUB
+BWB4DSJQU$PSFͷجຊ ࣮ߦͱ݁ՌऔಘΛಉ࣌ʹߦ͏ ⾣ FWBMVBUF4DSJQUϝιου+47BMVFΛฦ͢ ⾣ ධՁ݁ՌΛ4XJGUͰड͚औΕΔ let answer = context.evaluateScript("v1 + v2") 4XJGUCFUB
4XJGU͔Β+BWB4DSJQUΛ εϚʔτʹ࣮ߦͰ͖ͯɺ͍͠خ
+BWB4DSJQU$PSF มͷѻ͍ํ
+BWB4DSJQU$PSFͷมʹ͍ͭͯ +47BMVFͰද͢ݱΔ ⾣ +BWB4DSJQUͷ+47BMVFͰܕѻ͏ ⾣ ͍ΖΜͳछྨͷΛऔΓѻ͑Δ let value: JSValue 4XJGUCFUB
+BWB4DSJQU$PSFͷมʹ͍ͭͯ +47BMVFΛ4XJGUʹܕม͢Δ ⾣ +47BMVF͔Β4XJGUܕͷΛऔಘͰ͖Δ ⾣ UP5ZQFOBNFϝιουΛ͏ • toInt32() -> Int32 • toUInt32() -> UInt32 • toDouble() -> Double • toNumber() -> NSNumber! • toString() -> String! • toBool() -> Bool! • toObject() -> Any! • toDate() -> Date! -> [Any]! • toArray() • toDictionary() -> [AnyHashable : Any]! 4XJGUCFUB
+BWB4DSJQU$PSFͷมʹ͍ͭͯ +47BMVFͷछྨΛఆ͢Δ ⾣ +BWB4DSJQUతʹͲΜͳͰ͔ܕఆ͢Δ ⾣ JT5ZQFOBNFϓϩύςΟΛ͏ • isNumber • isString • isBoolean • isObject • isUndefined • isNull • isArray • isDate 4XJGUCFUB
+BWB4DSJQU$PSFͷมʹ͍ͭͯ 4XJGUͰͷVOEFpOFEͷѻ͍ ⾣ ͍ΘΏΔ;&30ͱͯ͠ѻΘΕΔ͜ͱ͕ଟ͍ • toInt32() → 0 • toUInt32() → 0 • toDouble() → nan • toBool() → false → nil • toObject() → nil • toArray() • toDictionary() → nil → nan • toNumber() • toString() → "undefined" • toDate() → -5877520-03-03 -596:-31:-23 +0000 • toDate().timeIntervalSince1970 → nan • toNumber().intValue → -9223372036854775808 • toNumber().uintValue → 9223372036854775808 4XJGUCFUB • toNumber().doubleValue → nan
+BWB4DSJQU$PSFͷมʹ͍ͭͯ 4XJGUͰͷOVMMͷѻ͍ ⾣ ͍ΘΏΔ;&30ͱͯ͠ѻΘΕΔ • toInt32() → 0 • toUInt32() → 0 • toDouble() → 0.0 • toBool() → false → nil • toObject() → nil • toArray() • toDictionary() → nil → 0 • toNumber() • toString() → "null" • toDate() → 1970-01-01 00:00:00 +0000 • toDate().timeIntervalSince1970 → 0 • toNumber().intValue → 0 • toNumber().uintValue → 0 4XJGUCFUB • toNumber().doubleValue → 0.0
+BWB4DSJQU$PSFͷมʹ͍ͭͯ +47BMVFͰΦϒδΣΫτΛૢ࡞͢Δ ⾣ +BWB4DSJQUΦϒδΣΫτΛ4XJGUͰ͑Δ ⾣ ϓϩύςΟϝιουར༻Մೳ let article: JSValue = context.evaluateScript("article")! // プロパティの参照 let title = article.forProperty("title") // JavaScript メソッドの実行 let note = article.invokeMethod("getDescription", withArguments: []) 4XJGUCFUB
+BWB4DSJQU$PSFͷมʹ͍ͭͯ +47BMVFൺֱͰ͖Δ ⾣ +47BMVFܕԋࢉࢠͰൺֱͰ͖Δ ⾣ ܧঝݩͷ/40CKFDU͕&RVBUBCMFʹ४͍ͯ͠ڌΔ let value1: JSValue = context.objectForKeyedSubscript("v1")! let value2: JSValue = context.objectForKeyedSubscript("v2")! if value1 == value2 { } 4XJGUCFUB
มͳࡏࣗݬ+BWB4DSJQUมΛ ΦϒδΣΫτؚΊͯૢΕΔͷɺྑ͍
+BWB4DSJQU$PSF +BWB4DSJQU"1*
+BWB4DSJQU$PSFͷ+BWB4DSJQU"1* ΈࠐΈͷ"1* ⾣ جຊతͳΈࠐΈػೳ͚͕ͩ͑Δ ⾣ %0.ͳͲͷಛผͳػೳඋΘ͍ͬͯͳ͍ • Object • Array • Date • Math • RegEx • JSON • Error ʜ • parseInt • parseFloat • encodeURI • decodeURI • encodeURIComponent • decodeURIComponent • encodeURI • eval • escape +BWB4DSJQU
γϯϓϧͳʹڥ ࣗͷखͰ"1*Λߏங͢Δ
+BWB4DSJQU"1*ͷߏங ࣮ํݱ๏ ⾣ +4$POUFYUʹ"1*ΛՃ͢Δ ⾣ ࣍ͷΑ͏ͳํ๏ͰػೳΛఏ͖ͰڙΔ " มΛ+BWB4DSJQU͔Βఏ͢ڙΔ # มΛ4XJGU͔Βఏ͢ڙΔ $ ؔΛ+BWB4DSJQU͔Βఏ͢ڙΔ % ؔΛ4XJGU͔ΒऔΓࠐΜͰఏ͢ڙΔ & +BWB4DSJQU͔ΒΦϒδΣΫτΛఏ͢ڙΔ ' 4XJGUͷܕΛ+BWB4DSJQUʹఏ͢ڙΔ
+BWB4DSJQU"1*ͷߏஙํ๏ ม Λ+BWB4DSJQUͰఆٛ͢Δ ⾣ มΛఆٛ͢Δ+BWB4DSJQUΛ࣮ߦ͢Δ ⾣ Ҏ߱ɺίϯςΩετͰ͜ͷม͕͑Δ context.evaluateScript("var timeout = 5.0") 4XJGUCFUB
+BWB4DSJQU"1*ͷߏஙํ๏ ม Λ4XJGU͔Βఆٛ͢Δ ⾣ 4XJGUͷมΛίϯςΩετʹઃఆ͢Δ ⾣ ͳܕΒෳ͞ΕɺࢀরͳܕΒڞ༗͞ΕΔ let account = "@es_kumagai" // Swift 変数 account を、JavaScript に変数 name で登録 context.setObject(account, forKeyedSubscript: "name" as NSString) 4XJGUCFUBΩϟετ͕ඞཁ ͦͷ͏ͪɺඞཁͳ͘ͳΔͣ 4XJGUCFUB
+BWB4DSJQU"1*ͷߏஙํ๏ ؔ Λ+BWB4DSJQUͰఆٛ͢Δ ⾣ ؔΛఆٛ͢Δ+BWB4DSJQUΛ࣮ߦ͢Δ ⾣ Ҏ߱ɺίϯςΩετͰ͜ͷ͕ؔ͑Δ context.evaluateScript( "function getAttribute(name) {" + " return attributes[name];" + "}") 4XJGUCFUB
+BWB4DSJQU"1*ͷߏஙํ๏
ؔ Λ4XJGU͔ΒऔΓࠐΉ
⾣ 4XJGUͷΫϩʔδϟʔΛίϯςΩετʹઃఆ͢Δ
⾣ !DPOWFOUJPO CMPDL Λࢦఆͯ͠"OZ0CKFDUͰѻ͏
let output : @convention(block) (String) -> Void = {
NSLog("\(account) : \($0)")
}
// クロージャーを AnyObject 型にキャストして登録
context.setObject(
unsafeBitCast(output, to: AnyObject.self),
forKeyedSubscript: "output" as NSString)
4XJGUCFUB
+BWB4DSJQU"1*ͷߏஙํ๏ +BWB4DSJQUͰΦϒδΣΫτΛఆٛ͢Δ ⾣ ΦϒδΣΫτΛఆٛ͢Δ+BWB4DSJQUΛ࣮ߦ͢Δ ⾣ Πϯελϯε4XJGUͰ͑Δ context.evaluateScript( "function Article(title, body) {" + " this.title = title;" + " this.body = body;" + "}" + "" + "Article.prototype.getDescription = " + " function() {" + " return this.body.substr(0, 10);" + "};") 4XJGUCFUB
+BWB4DSJQU"1*ͷߏஙํ๏
4XJGUͷ ܕΛ+BWB4DSJQUͰ͏
⾣ +BWB4DSJQUͰ͏"1*ΛϓϩτίϧͰఆٛ͢Δ
⾣ ϓϩτίϧ!PCKDΛࢦఆɺ+4&YQPSUΛܧঝ͢Δ
@objc protocol ImageInterface : JSExport {
var width: Int { get set }
var height: Int { get set }
// インスタンス化をしたい場合は静的メソッドも宣言
static func make(name: String, scale: CGFloat)
-> AnyObject
}
4XJGUCFUB
+BWB4DSJQU"1*ͷߏஙํ๏
4XJGUͷ ܕΛ+BWB4DSJQUͰ͏
⾣ +BWB4DSJQUͰ͏ܕΛఆٛ͢Δ
⾣ +4&YQPSU४ڌͷϓϩτίϧͱ/40CKFDUΛܧঝ͢Δ
class Image : NSObject, ImageInterface {
var width, height: Int
required init(name: String, scale: CGFloat) {…}
class func make(name: String, scale: CGFloat)
-> AnyObject {
return self.init(name: name, scale: scale)
}
4XJGUCFUB
+BWB4DSJQU"1*ͷߏஙํ๏ 4XJGUͷ ܕΛ+BWB4DSJQUͰ͏ ⾣ ΠϯελϯεΛ͏͚ͩͳΒมΛηοτ͢Δ ⾣ ϓϩτίϧͰ໌"ͨ͠ه1*͚͕ͩ͑Δ let image = Image(name: "Profile", scale: 0.8) context.setObject(image, forKeyedSubscript: "icon" as NSString) 4XJGUCFUB
+BWB4DSJQU"1*ͷߏஙํ๏ 4XJGUͷ ܕΛ+BWB4DSJQUͰ͏ ⾣ ΠϯελϯεΛੜ͍ͨ͠ͳΒܕใΛηοτ͢Δ ⾣ ੩తϝιουܦ༝ͰΠϯελϯεΛੜͰ͖Δ context.setObject(Image.self, forKeyedSubscript: "Image" as NSString) // メソッドは、全てのラベルを名前に含めて実行すること context.evaluateScript( "var banner = " + "Image.makeWithNameScale('picture', 0.5)") 4XJGUCFUB
4XJGUͷػೳΛͦͷ·· +BWB4DSJQUʹఏ͖ͰڙΔͷɺૉΒ͍͠
+BWB4DSJQU$PSF ίʔυΛॻ͖͘͢
+BWB4DSJQUΛॻ͖͘͢͢Δ จࣈྻʹຒΊࠐΉͷ໘ ⾣ ͜ͷॻ͖ํͩͱɺͱͯಡΈʹ͍͘ ⾣ ίʔυΛॻ͘ͷ͕ࠔʹͳΓ͕ͪ context.evaluateScript( "function Article(title, body) {" + " this.title = title;" + " this.body = body;" + "}" + "" + "Article.prototype.getDescription = " + " function() {" + " return this.body.substr(0, 10);" + "};") 4XJGUCFUB
+BWB4DSJQUΛ ผϑΝΠϧʹॻ͖ग़͢
+BWB4DSJQUΛॻ͖͘͢͢Δ +4ϑΝΠϧΛొ͢Δ ⾣ +BWB4DSJQUίʔυΛKTϑΝΠϧͰ༻ҙ͢Δ ⾣ ΞϓϦʹόϯυϧͯ͠͏ 9DPEF
+BWB4DSJQUΛॻ͖͘͢͢Δ +4ϑΝΠϧʹίʔυΛॻ͘ ⾣ ιʔείʔυΤσΟλʔͰฤूͰ͖Δ ⾣ ϋΠϥΠτදࣔɺ͋Δఔͷίʔυิ͕ར͘ 9DPEF
+BWB4DSJQUΛॻ͖͘͢͢Δ +4ϑΝΠϧΛಡΈࠐΜͰ͏ ⾣ +4ϑΝΠϧ͔ΒίʔυΛಡΈࠐΉ ⾣ ಡΈࠐΜͩίʔυΛ࣮ߦ͢Δ let path = Bundle.main.url( forResource: "JavaScriptAPI", withExtension: "js")! let source = try! String(contentsOf: path) // 読み込んだ JavaScript をコンテキストで実行 let context = JSContext()! context.evaluateScript(source) 4XJGUCFUB
จࣈྻϦςϥϧͰͳ͘ ૉͷίʔυͰॻ͚ΔͱɺḿΔ
+BWB4DSJQU$PSFGSBNFXPSL ·ͱΊ +BWB4DSJQUΛ4XJGU͔Β ؆୯ɾࣗࡏʹѻ͑Δͷ͕͍͠خ ⾣ +BWB4DSJQU$PSFͱ ⾣ +BWB4DSJQUίʔυΛ࣮ߦ͢Δ ⾣ +47BMVFΛѻ͏ʢൺֱɺมɺఆʣ ⾣ "1*Λఆٛ͢ΔʢؔɺมɺΦϒδΣΫτʣ
4XJGUͰ+BWB4DSJQU ࢝ΊͯΈ·ͤΜ͔ʁ
&OKPZ4XJGU 5IBOLZPV 4XJGUͰ+BWB4DSJQU࢝Ί·ͤΜ͔ʁ &;/&5۽୩༑ IUUQF[OFUKQ ⾣ +BWB4DSJQU$PSFͱ ⾣ +BWB4DSJQUίʔυΛ࣮ߦ͢Δ ⾣ +47BMVFΛѻ͏ʢൺֱɺมɺఆʣ ⾣ "1*Λఆٛ͢ΔʢؔɺมɺΦϒδΣΫτʣ