Skip to content

DevicePluginManual 20

TakayukiHoshi1984 edited this page Mar 27, 2017 · 1 revision

本ドキュメントでは、DeviceConnect Codegenを使用したDevice Connectプラグインの開発方法について説明します。

DeviceConnect Codegenは、Swaggerの定義ファイルを元にプラグインのスケルトンコードなどを出力するツールになります。
詳しくは、こちらをご参照ください。

なお、下記に関する知識のある読者を対象とし、これらの解説は省略します。

  • Objective-C
  • Xcode
  • Swagger

ここでは、プラグインを開発するために必要な準備を説明します。

  • Xcode Version 8.2.1
  • DeviceConnect Codegen

DeviceConnect Codegenは、ここからインストールをしてください。

DeviceConnect Codegen を使用して、プラグインのスケルトンコードを出力します。
ここでは、DeviceConnect Codegen の簡単な使い方を説明します。

sample-profile-specs に使用したいAPIを定義した JSON ファイルを配置します。
配置する JSON ファイルは、Swagger2.0でAPIを定義したファイルになります。

標準で仕様を策定している JSON ファイルは、standard-profile-specs に格納してありますので、こちらからコピーして使用してください。

JSONファイルを配置するディレクトリは sample-profile-specs から変更することもできます。
この後に DeviceConnect Codegen で詳しく説明します。

ios-plugin.sh に DeviceConnect Codegen の設定を行います。

# スケルトンコード種別: iOSプラグイン
LANG="deviceConnectIosPlugin"

出力するスケルトンコードの種類を設定します。
ここでは、iOSプラグインを出力しますので、deviceConnectIosPluginを指定します。

# プロファイル定義ファイル
SPEC="./sample-profile-specs/swagger.json"

プラグインがサポートするAPIの定義ファイルを配置するディレクトリを指定します。

# スケルトンコード出力先
OUTPUT_DIR="./output/iOS/MyPlugin"

スケルトンコードを出力する先のディレクトリを指定します。
指定されたディレクトリが存在しない場合には、作成します。
指定されたディレクトリが存在する場合には、上書きします。

# iOSプラグインの表示名
DISPLAY_NAME="MyPlugin"

iOSプラグインのアプリケーション名を指定します。

$ ./ios-plugin.sh

シェルを実行することで、OUTPUT_DIR で指定したディレクトリにスケルトンコードを出力します。

Xcodeを起動し、「Create a new Xcode project」を押下します。

iOSから「Cocoa Touch Static Library」を選択して、Nextボタンを押下します。

プロジェクトの名前やプロジェクトの id を入力して、Nextボタンを押下します。
ここでは、以下のように値を入力します。

パラメータ名
Product Name MyPlugin
Organization Name Example
Organization Identifier org.deviceconnect.ios.plugin

MyPlugin のプロジェクトが作成されます。

Xcode のメニューから「File」->「Target」を選択します。

Cross-platform から「Aggregate」を選択して、Nextボタンを押下します。

Product Name に MyPlugin_framework を入力して、Finishボタンを押下します。

追加された MyPlugin_framework を選択して、Build Pharse を開き、プラスボタンを押下します。
メニューが開かれるので、New Run Script Phase を押下します。

Run Script の項目が追加されますので、そこに以下のスクリプトを記入します。

# Environment Variables
FRAMEWORK_NAME=${PROJECT_NAME}
FRAMEWORK_VERSION=A
FRAMEWORK_VERSION_NUMBER=1.0
CONFIGURATION="Release"
FRAMEWORK_BUILD_PATH="${SRCROOT}/build/${CONFIGURATION}-framework"
FRAMEWORK_DIR="${FRAMEWORK_BUILD_PATH}/${FRAMEWORK_NAME}.framework"
STATICLIB_BUILD_PATH="${SRCROOT}/build/${CONFIGURATION}-universal"
OUTPUT_DIR="bin"

# Clean directories
rm -rf "${FRAMEWORK_BUILD_PATH}"
rm -rf "${OUTPUT_DIR}"

# Build simulator and device binaries.
# A target to build a static library *must* have the name ${PROJECT_NAME}.
xcodebuild -project ${PROJECT_NAME}.xcodeproj -sdk iphonesimulator -target ${PROJECT_NAME} -configuration ${CONFIGURATION} clean build
xcodebuild -project ${PROJECT_NAME}.xcodeproj -sdk iphoneos -target ${PROJECT_NAME} -configuration ${CONFIGURATION} clean build

# create directories.
mkdir -p ${FRAMEWORK_DIR}/Versions/${FRAMEWORK_VERSION}/Headers
mkdir -p ${STATICLIB_BUILD_PATH}

# create symlinks
cd ${FRAMEWORK_DIR}
ln -s ../Versions/${FRAMEWORK_VERSION} Versions/Current
ln -s Versions/Current/Headers Headers
ln -s Versions/Current/${FRAMEWORK_NAME} ${FRAMEWORK_NAME}
cd ${SRCROOT}

# create the universal library, and strip internal symbols from it
lipo ${SRCROOT}/build/${CONFIGURATION}-iphoneos/lib${FRAMEWORK_NAME}.a ${SRCROOT}/build/${CONFIGURATION}-iphonesimulator/lib${FRAMEWORK_NAME}.a -create -output "${STATICLIB_BUILD_PATH}/lib${FRAMEWORK_NAME}.a"
cp "${STATICLIB_BUILD_PATH}/lib${FRAMEWORK_NAME}.a" "${FRAMEWORK_DIR}/Versions/Current/${FRAMEWORK_NAME}"

# copy files
cp -r ${SRCROOT}/${PROJECT_NAME}/Headers/ ${FRAMEWORK_DIR}/Headers/

# copy binary to bin folder
cd ${SRCROOT}
mkdir -p ${OUTPUT_DIR}
cp -R ${FRAMEWORK_DIR} ${OUTPUT_DIR}

デバッグ用にビルドを行いたい場合には、CONFIGURATION を Debug に変更します。

CONFIGURATION="Debug"

MyPlugin のプロジェクト設定から Build Phases を開き Link Binary With Libraries の項目に DConnectSDK.framework を追加します。

Search Paths の Framework Search Paths に、DConnectSDK.framework が置いてあるフォルダへのパスを入力します。

作成したプロジェクトに DeviceConnect Codegen で作成したファイルを追加します。

ビルドターゲットを MyPlugin_framework に設定して、Build を行います。

MyPlugin プロジェクトに bin フォルダが作成され、MyPlugin.framework が生成されます。

4.1. DConnectServiceの登録

プラグインが管理するサービスは、DConnectServiceを作成します。

次に、実装したDConnectServiceをDConnectPlugin::serviceProviderで取得できるDConnectServiceProviderに追加することで管理します。

Service Discoveryでサービスが検索された時に、Device Connect Plugin SDKはDConnectServiceProviderに登録されているDConnectServiceの一覧を返却します。

MyPlugin.mで以下のようにしてサービスを追加しています。

DConnectService *service = [[DConnectService alloc] initWithServiceId:@"my_service_id" plugin:self];
[service setName:@"MyPlugin Service"];
[service setOnline:YES];
[service addProfile:[MyBatteryProfile new]];
[service addProfile:[MyCanvasProfile new]];
[service addProfile:[MyDeviceOrientationProfile new]];
[self.serviceProvider addService:service];

複数のサービスを管理する場合には、必要なサービス分だけ追加します。

DConnectServiceは、initWithServiceIdに渡すサービスIDで管理していますので、ユニークな値を指定します。
また、サービスIDが重複した場合には上書きします。

4.2. DConnectProfileの追加

DConnectService::addProfile で追加したプロファイルが、そのサービスで使用できるプロファイルになります。
サービスで必要なプロファイルを実装して、追加します。

4.3. DConnectServiceの接続状態

周辺機器は常に接続されている訳ではなく、接続が切れている場合があります。
それらを識別するために DConnectService::setOnline で接続状態を管理します。
YESの場合には接続状態、NOの場合には切断状態を表します。

サービスの接続状態に合わせて、DConnectService::setOnline の値を設定します。

ここでは、DeviceConnect Codegenで出力したプロファイルの実装方法を説明します。

標準化を行なっている各プロファイルの定義は、こちらで行なっています。

また、各プロファイルの定義は、HTTPリクエストの形式で行われています。
プラグインにリクエストが配送されてくる時には、DConnectRequestMessageになっていますので、その変換方法については、こちらのManagerとプラグインの通信についてを参照してください。

確認したバッテリーの情報を取得するAPIは、MyBatteryProfile.mで登録されている以下の関数で実装しています。

// GET /gotapi/battery
[self addGetPath:@"/" api:^BOOL(DConnectRequestMessage *request, DConnectResponseMessage *response) {
    NSString *serviceId = [request stringForKey:@"serviceId"];

    // TODO ここでAPIを実装してください.
    [response setResult:DConnectMessageResultTypeOk];
    [response setBool:false forKey:@"charging"];
    [response setLongLong:0 forKey:@"dischargingTime"];
    [response setFloat:0.0f forKey:@"level"];
    [response setLongLong:0 forKey:@"chargingTime"];
    return YES;
}];

responseに設定している値を変更することで、アプリケーションに返されるレスポンスの値が変更されます。
実際にlevelの値を以下のように変更してみて確認してください。

[response setFloat:0.5f forKey:@"level"];

3.4. スケルトーンコードの実行で、行った手順を再度を行いバッテリーの値が変更されていれば、修正成功です。

レスポンスは、非同期に返却することもできます。

関数の返り値をNOにして、非同期に DConnectManager::sendResponse を使用してレスポンスを返却します。

以下のサンプルでは、1秒後にレスポンスを返却します。

// GET /battery
[self addGetPath:@"/" api:^BOOL(DConnectRequestMessage *request, DConnectResponseMessage *response) {
    NSString *serviceId = [request stringForKey:@"serviceId"];
    
    // 1秒後にレスポンスを返却します。
    double delayInSeconds = 1.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
        [response setResult:DConnectMessageResultTypeOk];
        [response setBool:false forKey:@"charging"];
        [response setLongLong:0 forKey:@"dischargingTime"];
        [response setFloat:0.0f forKey:@"level"];
        [response setLongLong:0 forKey:@"chargingTime"];
        
        // レスポンスを返却
        [[DConnectManager sharedManager] sendResponse:response];
    });
    // 非同期でレスポンスを返却するので、NOを返します。
    return NO;
}];

処理に時間がかかり即座にレスポンスを返却できない場合には、上記のように非同期で処理を行い、最後にレスポンスを返却することができます。

ただし、Device Connect Managerは、プラグインから30秒以内返答がない場合に、アプリケーションにタイムアウトエラーを返却します。

プラグインは、出来るだけ速くレスポンスを返却するようにしてください。

エラーレスポンスを返却したい場合には、DConnectResponseMessage::setErrorToXXXXを使用します。
setErrorToXXXX には、Device Connect で定義されているエラーを簡単に設定できるメソッドが用意されています。

以下のサンプルではパラメータ異常の場合のエラーを設定します。

// GET /gotapi/battery
[self addGetPath:@"/" api:^BOOL(DConnectRequestMessage *request, DConnectResponseMessage *response) {
    NSString *serviceId = [request stringForKey:@"serviceId"];;

    [response setErrorToUnknownWithMessage:@"Not implemented yet."];
    return YES;
}];

プラグインは、エラーに合わせてエラーコードとエラーメッセージを指定して返却するようにしてください。

ここでは、送信したメッセージを返却するだけの簡単なプロファイルを作成します。

以下のecho.jsonをsample-profile-specsに追加して、DeviceConnect Codegenを実行します。

{
    "swagger": "2.0",
    "info": {
        "title": "Device Connect API",
        "version": "2.0.0",
        "description": ""
    },
    "consumes": [],
    "basePath": "/gotapi",
    "paths": {
        "/echo": {
            "get": {
                "x-type": "one-shot",
                "summary": "Echo",
                "description": "送られてきたメッセージを、そのまま返却するAPI",
                "parameters": [
                    {
                        "name": "serviceId",
                        "in": "query",
                        "required": true,
                        "type": "string"
                    },
                    {
                        "name": "message",
                        "in": "query",
                        "required": true,
                        "type": "string"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Echo",
                        "schema": {
                            "$ref": "#/definitions/EchoResponse"
                        }
                    }
                }
            }
        },
        
        // ...省略 ...
        
    },
    "definitions": {
        "EchoResponse": {
            "type": "object",
            "allOf": [
                {
                    "$ref": "#/definitions/CommonResponse"
                },
                {
                    "type": "object",
                    "required": [
                        "message"
                    ],
                    "properties": {
                        "message": {
                            "type": "string",
                            "description": "メッセージ"
                        }
                    }
                }
            ]
        },
        
        // ...省略 ...
        
    }
}

DeviceConnect Codegenで生成されたプロジェクトに新たに MyEchoProfile.h と MyEchoProfile.m が作成されています。
作成されたファイルを MyPlugin プロジェクトに追加します。

echo.jsonは、Swagger2.0の仕様に沿って作成しています。
Swagger2.0の仕様を確認したい場合は、こちらをご参照してください。

MyEchoProfile.m
#import "MyEchoProfile.h"

@implementation MyEchoProfile

- (instancetype) init
{
    self = [super init];

    __weak MyEchoProfile *weakSelf = self;

    // GET /gotapi/echo
    [self addGetPath:@"/" api:^BOOL(DConnectRequestMessage *request, DConnectResponseMessage *response) {
        NSString *serviceId = [request stringForKey:@"serviceId"];
        NSString *message = [request stringForKey:@"message"];

        // TODO ここでAPIを実装してください.
        [response setResult:DConnectMessageResultTypeOk];
        [response setString:@"test" forKey:@"message"];
        return YES;
    }];
    return self;
}


#pragma mark - DConnectProfile Methods -

- (NSString *) profileName {
    return @"echo";
}

@end

出力された MyEchoProfile.m を開き、以下の箇所を修正します。

[response setString:@"test" forKey:@"message"];

を以下のように修正します。

[response setString:message forKey:@"message"];

プラグインを実行し、インストールを行います。

UIViewController を表示します。

UIViewController を格納するために StoryBoard を作成し、アプリに組み込む必要があります。 そのために Bundle を作成して、格納します。

macOS から Bundle を選択します。

Product Name に「MyPlugin_resources」を入力して Finish ボタンを押下します。

MyPlugin_resources の Build Settings を開き、Supported Platforms を iOS に設定します。
デフォルトでは、macOS になっているためにビルドできませんので、ここで変更を行います。

次に StoryBoard を作成します。

StoryBoard を選択して、Next ボタンを押下します。

MyPlugin.storyboard と名前をつけて保存します。

MyPlugin_resources の設定から Build Phases を開き、Copy Bundle Resources に MyPlugin.storyboard を追加します。

MyPlugin_framework の Run Script に Bundle のビルド設定を追加します。

# Environment Variables
FRAMEWORK_NAME=${PROJECT_NAME}
FRAMEWORK_VERSION=A
FRAMEWORK_VERSION_NUMBER=1.0
CONFIGURATION="Release"
FRAMEWORK_BUILD_PATH="${SRCROOT}/build/${CONFIGURATION}-framework"
FRAMEWORK_DIR="${FRAMEWORK_BUILD_PATH}/${FRAMEWORK_NAME}.framework"
STATICLIB_BUILD_PATH="${SRCROOT}/build/${CONFIGURATION}-universal"
OUTPUT_DIR="bin"

# Clean directories
rm -rf "${FRAMEWORK_BUILD_PATH}"
rm -rf "${OUTPUT_DIR}"

# Build simulator and device binaries.
# A target to build a static library *must* have the name ${PROJECT_NAME}.
xcodebuild -project ${PROJECT_NAME}.xcodeproj -sdk iphonesimulator -target ${PROJECT_NAME} -configuration ${CONFIGURATION} clean build
xcodebuild -project ${PROJECT_NAME}.xcodeproj -sdk iphoneos -target ${PROJECT_NAME} -configuration ${CONFIGURATION} clean build

# A target to build a bundle *must* have the name ${PROJECT_NAME}_resources. This target is optional.
xcodebuild -project ${PROJECT_NAME}.xcodeproj -sdk iphoneos -target ${PROJECT_NAME}_resources -configuration ${CONFIGURATION} clean build || true

# create directories.
mkdir -p ${FRAMEWORK_DIR}/Versions/${FRAMEWORK_VERSION}/Headers
mkdir -p ${STATICLIB_BUILD_PATH}

# create symlinks
cd ${FRAMEWORK_DIR}
ln -s ../Versions/${FRAMEWORK_VERSION} Versions/Current
ln -s Versions/Current/Headers Headers
ln -s Versions/Current/${FRAMEWORK_NAME} ${FRAMEWORK_NAME}
cd ${SRCROOT}

# create the universal library, and strip internal symbols from it
lipo ${SRCROOT}/build/${CONFIGURATION}-iphoneos/lib${FRAMEWORK_NAME}.a ${SRCROOT}/build/${CONFIGURATION}-iphonesimulator/lib${FRAMEWORK_NAME}.a -create -output "${STATICLIB_BUILD_PATH}/lib${FRAMEWORK_NAME}.a"
cp "${STATICLIB_BUILD_PATH}/lib${FRAMEWORK_NAME}.a" "${FRAMEWORK_DIR}/Versions/Current/${FRAMEWORK_NAME}"

# copy files
cp -r ${SRCROOT}/${PROJECT_NAME}/Headers/ ${FRAMEWORK_DIR}/Headers/

# copy binary to bin folder
cd ${SRCROOT}
mkdir -p ${OUTPUT_DIR}
cp -R ${FRAMEWORK_DIR} ${OUTPUT_DIR}
cp -r build/${CONFIGURATION}-iphoneos/${PROJECT_NAME}_resources.bundle ${OUTPUT_DIR}

MyPlugin_framework の Run Script を修正したら、ビルドを行います。
フォルダの下に、MyPlugin_resources.bundle が作成成功になります。

DConnectSystemProfileDataSource を実装したクラスに以下のようにして、返却した UIViewController を画面に表示します。

- (UIViewController *) profile:(DConnectSystemProfile *)sender
         settingPageForRequest:(DConnectRequestMessage *)request
{
    NSBundle *bundle = [NSBundle bundleWithURL:
                        [[NSBundle mainBundle] URLForResource:@"MyPlugin_resources"
                                                withExtension:@"bundle"]];
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MyPlugin" bundle:bundle];
    return [storyboard instantiateInitialViewController];
}

イベントの管理を行う上でDConnectEventManagerというユーティリティクラスを用意していますので、ここでは、DConnectEventManagerの使用方法を説明します。

8.1. イベントの登録

イベントを登録する場合には、以下の関数を使用します。

DConnectEventManager::addEventForRequest:(DConnectRequestMessage *)request

  • request : リクエストを渡します。
// PUT /gotapi/battery/onChargingChange
[self addPutPath:@"/onChargingChange" api:^BOOL(DConnectRequestMessage *request, DConnectResponseMessage *response) {
    NSString *serviceId = [request stringForKey:@"serviceId"];
    long long interval = [request longLongForKey:@"interval"];

    // TODO ここでAPIを実装してください.
    DConnectEventManager *mgr = [DConnectEventManager sharedManagerForClass:[MyPlugin class]];
    DConnectEventError error = [mgr addEventForRequest:request];
    if (error == DConnectEventErrorNone) {
        [response setResult:DConnectMessageResultTypeOk];

        // 以下、サンプルのイベントの定期的送信を開始.
        if (interval == LONG_LONG_MIN) {
            interval = 1000;
        }
        [weakSelf startEventTimer:request interval:interval handler:^{
            NSArray *events = [mgr eventListForServiceId:serviceId
                                                 profile:[request profile]
                                               interface:[request interface]
                                               attribute:[request attribute]];
            for (DConnectEvent *event in events) {
                DConnectMessage *message = [DConnectEventManager createEventMessageWithEvent:event];
                // サンプルのイベントメッセージを作成.
                DConnectMessage *battery = [DConnectMessage message];
                [battery setBool:false forKey:@"charging"];
                [message setMessage:battery forKey:@"battery"];
                DConnectDevicePlugin *plugin = (DConnectDevicePlugin *) weakSelf.plugin;
                [plugin sendEvent:message];
            }
        }];
    } else if (error == DConnectEventErrorInvalidParameter) {
        [response setErrorToInvalidRequestParameter];
    } else {
        [response setErrorToUnknown];
    }
    return YES;
}];

8.2. イベントの解除

イベントを解除する場合には、以下の関数を使用します。

DConnectEventManager::removeEventForRequst:(DConnectRequestMessage *)request

  • request : リクエストを渡します。
// DELETE /gotapi/battery/onChargingChange
[self addDeletePath:@"/onChargingChange" api:^BOOL(DConnectRequestMessage *request, DConnectResponseMessage *response) {
    NSString *serviceId = [request stringForKey:@"serviceId"];

    // TODO ここでAPIを実装してください.
    DConnectEventManager *mgr = [DConnectEventManager sharedManagerForClass:[MyPlugin class]];
    DConnectEventError error = [mgr removeEventForRequest:request];
    if (error == DConnectEventErrorNone) {
        [response setResult:DConnectMessageResultTypeOk];
        [weakSelf stopEventTimer:request];
    } else if (error == DConnectEventErrorInvalidParameter) {
        [response setErrorToInvalidRequestParameter];
    } else {
        [response setErrorToUnknown];
    }
    return YES;
}];

8.3. イベントの送信

登録されているイベントは、以下の関数で取得することができます。

DConnectEventManager::eventListForServiceId:profile:interface:attribute:

  • 'serviceId' : イベントを発生させるサービスのID。
  • 'profile' : 発生させるイベントのプロファイル名。
  • 'inter' : 発生させるイベントのインターフェース。(省略する場合にはnil)
  • 'attribute' : 発生させるイベントのあトリビュート。(省略する場合にはnil)

送信するイベントを取得し、DConnectEventManager#createEventMessage(Event)でEventからDConnectMessageを作成します。

作成したイベント用DConnectMessageは、DConnectDevicePlugin::sendEventを使用して、送信します。

NSArray *events = [mgr eventListForServiceId:serviceId
                                     profile:[request profile]
                                   interface:[request interface]
                                   attribute:[request attribute]];
for (DConnectEvent *event in events) {
    DConnectMessage *message = [DConnectEventManager createEventMessageWithEvent:event];
    // サンプルのイベントメッセージを作成.
    DConnectMessage *battery = [DConnectMessage message];
    [battery setBool:false forKey:@"charging"];
    [message setMessage:battery forKey:@"battery"];
    DConnectDevicePlugin *plugin = (DConnectDevicePlugin *) weakSelf.plugin;
    [plugin sendEvent:message];
}

ここでは、リソースをプラグインからアプリケーションに提供する方法を説明します。

9.1. 準備

リソースをアプリケーションに返却する為に、MediaStreamRecording の TakePhoto API を実装します。

standard-profile-specs/deviceOrientation.jsonをsample-profile-specsにコピーして、DeviceConnect Codegenでプロジェクトを出力します。

また、MediaStreamRecording を実装するプロジェクトに png 画像を MyPlugin_resources に追加します。

9.2. ファイルを利用

ファイルを利用して、リソースをプラグインからアプリケーションに返却する方法を説明します。

ファイルを利用するにあたり、Device Connect SDKでは、DConnectFileManagerというユーティリティクラスを用意しています。 この以下のサンプルでは、このDConnectFileManagerを使用してアプリケーションにリソースを渡します。

MyPlugin.h
#import <DConnectSDK/DConnectSDK.h>

@interface MyPlugin : DConnectDevicePlugin

@property DConnectFileManager *fileMgr;

- (NSString *) pathByAppendingPathComponent:(NSString *)pathComponent;

@end
MyPlugin.m
#import "MyPlugin.h"
#import "MyBatteryProfile.h"
#import "MyCanvasProfile.h"
#import "MyDeviceOrientationProfile.h"
#import "MyMediaStreamRecordingProfile.h"

@implementation MyPlugin

- (instancetype) init {
    self = [super initWithObject: self];
    if (self) {
        self.fileMgr = [DConnectFileManager fileManagerForPlugin:self];

        // TODO 以降の処理では常駐型のサービスを生成しています. 要件に適さない場合は修正してください.
        DConnectService *service = [[DConnectService alloc] initWithServiceId:@"my_service_id" plugin:self];
        [service setName:@"MyPlugin Service"];
        [service setOnline:YES];
        [service addProfile:[MyBatteryProfile new]];
        [service addProfile:[MyCanvasProfile new]];
        [service addProfile:[MyDeviceOrientationProfile new]];
        [service addProfile:[MyMediaStreamRecordingProfile new]];
        [self.serviceProvider addService:service];
    }
    return self;
}

- (NSString *) pathByAppendingPathComponent:(NSString *)pathComponent
{
    return [self.fileMgr.URL URLByAppendingPathComponent:pathComponent].standardizedURL.path;
}

@end
MyMediaStreamRecordingProfile.m
#import "MyMediaStreamRecordingProfile.h"
#import "MyPlugin.h"

@implementation MyMediaStreamRecordingProfile

- (instancetype) init
{
    self = [super init];
    
    __weak MyMediaStreamRecordingProfile *weakSelf = self;
    
    // ...省略...
    
    // POST /mediaStreamRecording/takePhoto
    [self addPostPath:@"/takePhoto" api:^BOOL(DConnectRequestMessage *request, DConnectResponseMessage *response) {
        NSString *serviceId = [request stringForKey:@"serviceId"];;
        NSString *target = [request stringForKey:@"target"];;
        
        MyPlugin *plugin = (MyPlugin *) weakSelf.plugin;
        DConnectFileManager *fileMgr = [plugin fileMgr];
        NSBundle *bundle = [NSBundle bundleWithPath:
                            [[NSBundle mainBundle] pathForResource:@"MyPlugin_resources"
                                                            ofType:@"bundle"]];
        NSString *path = [bundle pathForResource:@"dconnect_icon" ofType:@"png"];
        NSData *data = [[NSData alloc] initWithContentsOfFile:path];
        NSString *dstPath = [plugin pathByAppendingPathComponent:@"test.png"];
        [fileMgr createFileForPath:dstPath contents:data];
        [response setResult:DConnectMessageResultTypeOk];
        [DConnectMediaStreamRecordingProfile setUri:dstPath target:response];
        
        return YES;
    }];

    // ...省略...

    return self;
}

@end

9.3. Webサーバを利用

DConnectSDK.framework には CocoaHTTPServer、RoutingHTTPServer などのライブラリが組み込まれています。
今回は、その機能を使用して、画像データを返却するだけの簡単なWebサーバを用意します。

MyServer.h
#import <Foundation/Foundation.h>

@interface MyServer : NSObject

@property (nonatomic, strong) NSData *data;

- (BOOL) start;
- (void) stop;
- (BOOL) isRunning;

@end
MyServer.m
#import "MyServer.h"
#import "RoutingHTTPServer.h"

@implementation MyServer {
    RoutingHTTPServer *_httpServer;
}

-(BOOL) start {
    if (_httpServer) {
        [self stop];
    }
    
    __weak MyServer *weakSelf = self;
    
    _httpServer = [[RoutingHTTPServer alloc] init];
    [_httpServer setPort:9000];
    [_httpServer setDefaultHeader:@"content-type" value:@"image/png"];
    [_httpServer get:@"/*" withBlock:^(RouteRequest *request, RouteResponse *response) {
        [response respondWithData:weakSelf.data];
    }];
    
    NSError *error = nil;
    BOOL result = [_httpServer start:&error];
    if (!result) {
        NSLog(@"Error: %@", error);
    }
    return result;
}

- (void) stop
{
    [_httpServer stop];
    _httpServer = nil;
}

- (BOOL) isRunning
{
    return _httpServer && [_httpServer isRunning];
}

@end
MyMediaStreamRecordingProfile.m
#import "MyMediaStreamRecordingProfile.h"
#import "MyPlugin.h"
#import "MyServer.h"

@interface MyMediaStreamRecordingProfile ()

// 画像を受け渡すためのWebサーバ
@property(nonatomic, retain) MyServer *myServer;

@end


@implementation MyMediaStreamRecordingProfile

- (instancetype) init
{
    self = [super init];
    
    NSBundle *bundle = [NSBundle bundleWithPath:
                        [[NSBundle mainBundle] pathForResource:@"MyPlugin_resources"
                                                        ofType:@"bundle"]];
    NSString *path = [bundle pathForResource:@"dconnect_icon" ofType:@"png"];
    NSData *data = [[NSData alloc] initWithContentsOfFile:path];

    self.myServer = [MyServer new];
    self.myServer.data = data;
    
    __weak MyMediaStreamRecordingProfile *weakSelf = self;
    
    // POST /mediaStreamRecording/takePhoto
    [self addPostPath:@"/takePhoto" api:^BOOL(DConnectRequestMessage *request, DConnectResponseMessage *response) {        
        if ([weakSelf.myServer start]) {
            [response setResult:DConnectMessageResultTypeOk];
            [DConnectMediaStreamRecordingProfile setUri:@"http://localhost:9000/" target:response];
        } else {
            [response setErrorToIllegalServerStateWithMessage:@"Failed to start a web server."];
        }
        return YES;
    }];

    return self;
}

@end

サンプルなので、MyServerの停止は、行っていません。
一度のアクセスが完了した時点で終了にするなどの処理が必要になります。

また、Webサーバへのアクセスに制限をかけていませんので、指定されたURIにアクセスされた場合にデータを取得されてしまいます。

他のアプリからアクセスされたくない場合には、URLパスにハッシュ値などを含めて類推できないようにする必要があります。

プラグインがアプリケーションからリソースを取得する場合に、いくつかの手段があります。

MyCanvasUIViewController.h
#import <UIKit/UIKit.h>

@interface MyCanvasUIViewController : UIViewController

// 画像を表示するためのView
@property (nonatomic, strong) IBOutlet UIImageView *canvasView;
// 画像を一時的に保持する変数
@property (nonatomic, strong) UIImage *image;

@end
MyCanvasUIViewController.m
#import "MyCanvasUIViewController.h"

@implementation MyCanvasUIViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    if (_image != nil) {
        _canvasView.image = _image;
    }
}

- (IBAction)onTouchUpCloseButton:(id)sender {
    [self dismissViewControllerAnimated:YES completion:nil];
}

@end
MyPlugin.storyboard

MyPlugin.storyboard に画像を表示するための UIViewController を作成します。

UIViewController の Custom class を開き、Class を MyCanvasUIViewController に設定します。
同じ画面にある Storyboard ID に Canvas を設定します。

UIViewController に UIImageView を追加し、IBOutlet の canvasView に設定します。
また、画面を閉じるためのボタンを追加して、IBAction の onTouchUpCloseButton に設定します。

MyCanvasProfile.m
#import "MyCanvasProfile.h"
#import "MyCanvasUIViewController.h"

#define PutPresentedViewController(top) \
top = [UIApplication sharedApplication].keyWindow.rootViewController; \
while (top.presentedViewController) { \
top = top.presentedViewController; \
}

#define MyBundle() \
[NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"MyPlugin_resources" ofType:@"bundle"]]

@implementation MyCanvasProfile
- (instancetype) init
{
    self = [super init];

    __weak MyCanvasProfile *weakSelf = self;

    // POST /canvas/drawImage
    [self addPostPath:@"/drawImage" api:^BOOL(DConnectRequestMessage *request, DConnectResponseMessage *response) {
        NSString *serviceId = [request stringForKey:@"serviceId"];;
        NSString *mimeType = [request stringForKey:@"mimeType"];;
        NSString *uri = [request dataForKey:@"uri"];;
        NSData *data = [request dataForKey:@"data"];;
        int x = [request integerForKey:@"x"];;
        int y = [request integerForKey:@"y"];;
        NSString *mode = [request stringForKey:@"mode"];;

        UIImage *image = [[UIImage alloc] initWithData:data];
        if (image) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [weakSelf presentCanvasProfileViewController:response drawObject:image];
            });
            [response setResult:DConnectMessageResultTypeOk];
        } else {
            [response setErrorToInvalidRequestParameter];
        }
        
        return YES;
    }];

    // DELETE /canvas/drawImage
    [self addDeletePath:@"/drawImage" api:^BOOL(DConnectRequestMessage *request, DConnectResponseMessage *response) {
        NSString *serviceId = [request stringForKey:@"serviceId"];;

        // TODO ここでAPIを実装してください.
        [response setErrorToUnknownWithMessage:@"Not implemented yet."];
        return YES;
    }];
    return self;
}


- (UIViewController *)presentCanvasProfileViewController: (DConnectResponseMessage *)response
                                              drawObject: (UIImage *)drawObject
{
    NSString *storyBoardName = @"MyPlugin";
    UIStoryboard *storyBoard = [self storyboardWithName: storyBoardName];
    
    NSString *viewControllerId = @"Canvas";
    MyCanvasUIViewController *viewController = [storyBoard instantiateViewControllerWithIdentifier:viewControllerId];
    if (viewController != nil) {
        viewController.image = drawObject;
        UIViewController *rootView;
        PutPresentedViewController(rootView);
        [rootView presentViewController:viewController animated:YES completion:^() {
        }];
        [response setResult:DConnectMessageResultTypeOk];
    } else {
        [response setErrorToNotSupportAttribute];
    }
    
    return viewController;
}

- (UIStoryboard *)storyboardWithName: (NSString *)storyBoardName
{
    UIViewController *topViewController;
    PutPresentedViewController(topViewController);
    if (topViewController == nil) {
        return nil;
    }
    NSBundle *bundle = MyBundle();
    if (bundle == nil) {
        return nil;
    }
    return [UIStoryboard storyboardWithName:storyBoardName
                                     bundle: bundle];
}

@end

Clone this wiki locally