REST API呼び出しに欠かせない部品をアップデートしました。
–> GET method REST API v4.3
–> GET method REST API v4.1
–> GET method REST API v4
従来、NSURLConnectionを使って同期処理を行っていましたが、これがDepreceted扱いになりました。すぐになくなることはありませんが、いつかなくなる可能性があります。
このため、NSURLConnectionから動作原理の異なるNSURLSessionを使うよう移行する必要があります(curlコマンドを利用するというルートもあります)。
とはいえ、blocks構文の使えないAppleScriptでこのNSURLSessionを使えるようにするのは大変です。非同期通信を行うNSURLSessionを非同期処理のしにくいAppleScriptで、同期処理っぽく書かなくてはなりません。
NSURLSessionによる通信では、サーバーとの間で何回かのやりとりがあって、データを小分けに複数回受信する必要がありました。NSMutableDataというオブジェクトの存在を知ったのでいい感じに処理できるようになってきました。
NSURLSessionのメリットとして喧伝されている非同期処理は、AppleScriptから呼び出しているかぎりはそれほどメリットになりません。もう少し使い込めば活用できるかもしれませんが、まだそういう段階にはありません。
NSURLSessionの導入によって得られるメリットで最大のものは、キャッシュ機構です。NSURLConnectionでもキャッシュ機構は利用できましたが、NSURLSessionではよりAPIに深く統合されているものと理解しました。
キャッシュの効き方については、設定で何段階かに設定できるとAppleのオンラインドキュメントに書かれています。本Scriptでは、キャッシュ優先でキャッシュが存在している場合にはキャッシュ内のデータを返し、キャッシュが存在していない場合にはWebサーバーに問い合わせを行うように設定しています。
キャッシュが効いている状況なら、NSURLConnectionで1.8秒ぐらいの処理がNSURLSessionで0.1秒程度と大幅に速く処理できます。キャッシュが効いていない状況(初回実行時)だと、NSURLSessionのほうがやや処理に時間がかかるぐらいです。
処理結果について、NSURLConnection版とNSURLSession版で結果の比較を行ってみましたが、とくに差異は見られませんでした。
本サンプルScriptでは、Wikipedia(英語版)へのキーワードの問い合わせを行います。NSURLConnection版では複数回実行しても同じぐらいの速度で実行されますが、NSURLSession版では2度目から早く終わります(Script Debugger上で動かすと秒以下の精度の時間がわかります)。
macOS 10.14上で作ってmacOS 10.14.6/10.13.6上で動作確認しています(スクリプトエディタ、Script Debuggerで動作確認)。ただし、macOS 10.15上だとクラッシュします。Xcodeのプロジェクトに入れてみましたが、同様にmacOS 10.15上ではクラッシュします。
ただし、自分のmacOS 10.14.6環境はSIP解除しているのと、macOS 10.15環境のクラッシュログにSIP関連のメッセージが残っているので、OSバージョンではなくSIPのためかもしれません(macOS 10.14.6でもSIPを有効にするとクラッシュするかもしれない & macOS 10.15.xでもSIPを無効にするとクラッシュしないかもしれない)。
AppleScript名:GET method REST API v4.4_wikipedia |
— Created 2019-05-02 by Takaaki Naganoya — Modified 2020-03-29 by Takaaki Naganoya — 2020 Piyomaru Software use AppleScript version "2.5" use scripting additions use framework "Foundation" use framework "AppKit" property |NSURL| : a reference to current application’s |NSURL| property NSString : a reference to current application’s NSString property NSURLSession : a reference to current application’s NSURLSession property NSMutableData : a reference to current application’s NSMutableData property NSJSONSerialization : a reference to current application’s NSJSONSerialization property NSMutableURLRequest : a reference to current application’s NSMutableURLRequest property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding property NSURLSessionConfiguration : a reference to current application’s NSURLSessionConfiguration property retData : missing value property retCode : 0 property retHeaders : 0 property drecF : false set aQueryKeyTitle to "Steve Jobs" –キーワードの正規化が必要("戦場の絆"→"機動戦士ガンダム 戦場の絆" ) set reqURLStr to "https://en.wikipedia.org/w/api.php" set aRec to {action:"parse", page:aQueryKeyTitle, |prop|:"wikitext", format:"json"} set aURL to retURLwithParams(reqURLStr, aRec) of me set aRes to callRestGETAPIAndParseResults(aURL, 10) of me set bRes to (aRes’s valueForKeyPath:"parse.wikitext.*") as string return bRes –GET methodのREST APIを呼ぶ on callRestGETAPIAndParseResults(reqURLStr as string, timeoutSec as integer) set (my retData) to NSMutableData’s alloc()’s init() set (my retCode) to 0 set (my retHeaders) to {} set (my drecF) to false set aURL to |NSURL|’s URLWithString:reqURLStr set aRequest to NSMutableURLRequest’s requestWithURL:aURL aRequest’s setHTTPMethod:"GET" aRequest’s setTimeoutInterval:timeoutSec aRequest’s setValue:"gzip" forHTTPHeaderField:"Content-Encoding" aRequest’s setValue:"AppleScript/Cocoa" forHTTPHeaderField:"User-Agent" aRequest’s setValue:"application/json; charset=UTF-8" forHTTPHeaderField:"Content-Type" set aConfig to NSURLSessionConfiguration’s defaultSessionConfiguration() aConfig’s setRequestCachePolicy:(current application’s NSURLRequestReturnCacheDataElseLoad) set aSession to NSURLSession’s sessionWithConfiguration:aConfig delegate:(me) delegateQueue:(missing value) set aTask to aSession’s dataTaskWithRequest:aRequest aTask’s resume() –Start URL Session repeat (10 * timeoutSec) times if (my drecF) = true then exit repeat end if delay 0.1 end repeat return my parseSessionResults() end callRestGETAPIAndParseResults on URLSession:tmpSession dataTask:tmpTask didReceiveData:tmpData (my retData)’s appendData:tmpData end URLSession:dataTask:didReceiveData: on URLSession:tmpSession dataTask:tmpTask didCompleteWithError:tmpError set (my drecF) to true end URLSession:dataTask:didCompleteWithError: on URLSession:tmpSession dataTask:tmpTask willCacheResponse:cacheRes completionHandler:aHandler set (my drecF) to true end URLSession:dataTask:willCacheResponse:completionHandler: on parseSessionResults() set resStr to NSString’s alloc()’s initWithData:(my retData) encoding:(NSUTF8StringEncoding) set jsonString to NSString’s stringWithString:(resStr) set jsonData to jsonString’s dataUsingEncoding:(NSUTF8StringEncoding) set aJsonDict to NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value) return aJsonDict end parseSessionResults on retURLwithParams(aBaseURL, aRec) set aDic to current application’s NSMutableDictionary’s dictionaryWithDictionary:aRec set aKeyList to (aDic’s allKeys()) as list set aValList to (aDic’s allValues()) as list set aLen to length of aKeyList set qList to {} repeat with i from 1 to aLen set aName to contents of item i of aKeyList set aVal to contents of item i of aValList set the end of qList to (current application’s NSURLQueryItem’s queryItemWithName:aName value:aVal) end repeat set aComp to current application’s NSURLComponents’s alloc()’s initWithString:aBaseURL aComp’s setQueryItems:qList set aURL to (aComp’s |URL|()’s absoluteString()) as text return aURL end retURLwithParams on urlencodeStr(aStr) set aString to current application’s NSString’s stringWithString:aStr set aString to (aString’s stringByAddingPercentEncodingWithAllowedCharacters:(current application’s NSCharacterSet’s URLQueryAllowedCharacterSet())) as text return aString end urlencodeStr –リストに入れたレコードを、指定の属性ラベルの値で抽出 on filterRecListByLabel1(aRecList as list, aPredicate as string) set aArray to current application’s NSArray’s arrayWithArray:aRecList set aPredicate to current application’s NSPredicate’s predicateWithFormat:aPredicate set filteredArray to aArray’s filteredArrayUsingPredicate:aPredicate set bList to filteredArray as {list, missing value} return bList end filterRecListByLabel1 |
AppleScript名:GET method REST API_wikipedia |
— Created 2016-03-03 by Takaaki Naganoya — 2016 Piyomaru Software use AppleScript version "2.4" use scripting additions use framework "Foundation" set aQueryKeyTitle to "Steve Jobs" –キーワードの正規化が必要("戦場の絆"→"機動戦士ガンダム 戦場の絆" ) set reqURLStr to "https://en.wikipedia.org/w/api.php" set aRec to {action:"parse", page:aQueryKeyTitle, |prop|:"wikitext", format:"json"} set aURL to retURLwithParams(reqURLStr, aRec) of me set aRes to callRestGETAPIAndParseResults(aURL, 10) of me set aRESTres to json of aRes set bRes to (aRESTres’s valueForKeyPath:"parse.wikitext.*") as string –GET methodのREST APIを呼ぶ on callRestGETAPIAndParseResults(aURL, timeoutSec) set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL) aRequest’s setHTTPMethod:"GET" aRequest’s setTimeoutInterval:timeoutSec aRequest’s setValue:"gzip" forHTTPHeaderField:"Content-Encoding" aRequest’s setValue:"AppleScript/Cocoa" forHTTPHeaderField:"User-Agent" aRequest’s setValue:"application/json; charset=UTF-8" forHTTPHeaderField:"Content-Type" set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value) set resList to aRes as list set bRes to contents of (first item of resList) set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding) set jsonString to current application’s NSString’s stringWithString:resStr set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding) set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value) –Get Response Code set dRes to contents of second item of resList set resCode to (dRes’s statusCode()) as integer –Get Response Header set resHeaders to (dRes’s allHeaderFields()) as record return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders} end callRestGETAPIAndParseResults on retURLwithParams(aBaseURL, aRec) set aDic to current application’s NSMutableDictionary’s dictionaryWithDictionary:aRec set aKeyList to (aDic’s allKeys()) as list set aValList to (aDic’s allValues()) as list set aLen to length of aKeyList set qList to {} repeat with i from 1 to aLen set aName to contents of item i of aKeyList set aVal to contents of item i of aValList set the end of qList to (current application’s NSURLQueryItem’s queryItemWithName:aName value:aVal) end repeat set aComp to current application’s NSURLComponents’s alloc()’s initWithString:aBaseURL aComp’s setQueryItems:qList set aURL to (aComp’s |URL|()’s absoluteString()) as text return aURL end retURLwithParams on urlencodeStr(aStr) set aString to current application’s NSString’s stringWithString:aStr set aString to (aString’s stringByAddingPercentEncodingWithAllowedCharacters:(current application’s NSCharacterSet’s URLQueryAllowedCharacterSet())) as text return aString end urlencodeStr –リストに入れたレコードを、指定の属性ラベルの値で抽出 on filterRecListByLabel1(aRecList as list, aPredicate as string) set aArray to current application’s NSArray’s arrayWithArray:aRecList set aPredicate to current application’s NSPredicate’s predicateWithFormat:aPredicate set filteredArray to aArray’s filteredArrayUsingPredicate:aPredicate set bList to filteredArray as list return bList end filterRecListByLabel1 |