🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Bluetooth

Bluetoothとは短距離の間でデータを交換するための無線通信規格である。固定・モバイル両方のデバイスから、短波の電波送信を行うことで、高いセキュリティをもつパーソナルエリアネットワーク(PAN)を構築する。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

3回答

612閲覧

CBATTRequestは値渡しか参照渡しか

saku_panda

総合スコア20

Bluetooth

Bluetoothとは短距離の間でデータを交換するための無線通信規格である。固定・モバイル両方のデバイスから、短波の電波送信を行うことで、高いセキュリティをもつパーソナルエリアネットワーク(PAN)を構築する。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2020/01/06 04:59

##前提・実現したいこと
didReceiveReadで受け取ったCBATTRequestを、別の関数で識別・request.valueの書き換えを行い、呼び出し元の関数でrespondするプログラムを作成しました。
Swiftは基本的にinoutをつけない限り値渡しである認識しています。
つまり、別の関数でrequest.valueを書き換えたとしても、呼び出し元のdidReceiveReadへは反映されないと考えていました。
しかし、下記コードは正常に動作し、Centralで受け取ったデータは正しく変更されています。
なぜこのコードで正常動作するのかがわかりません。

また試しに呼び出し先の引数requestへinoutをつけましたが、
didReceiveReadの引数requestはletの変数なのでinoutはつけられないとエラーが出ました。

どなたか何故このような挙動ができるのかお分かりになる方がいらっしゃいましたら
教えていただけると幸いです。

##該当のソースコード

Swift

1static var counter: UInt16 = 0 2 3...... 4 5let prop1: CBCharacteristicProperties = [.read] 6let perm1: CBAttributePermissions = [.readable] 7let prop2: CBCharacteristicProperties = [.read,.write] 8let perm2: CBAttributePermissions = [.readable] 9 10/// skipCharacteristicのvalueを作成 11var Number: UInt16 = 12345 12var byteArray = [UInt8]() 13byteArray.append(contentsOf: withUnsafeBytes(of: &Number){ $0.map{ UInt8($0) } }) 14let data: Data = Data(byteArray) 15 16skipCharacteristic = CBMutableCharacteristic(type: UUID1, properties: prop1, value: data, permissions: perm1) 17inCharacteristic = CBMutableCharacteristic(type: UUID2, properties: prop2, value: nil, permissions: perm2) 18 19...... 20 21func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) { 22 /// readRequestの処理 23 let canAnswerRequest = answerReadRequest(request: request) 24 25 if canAnswerRequest { 26 peripheralManager.respond(to: request, withResult: CBATTError.success) 27 } else { 28 peripheralManager.respond(to: request, withResult: CBATTError.invalidHandle) 29 } 30} 31 32/// request を個別に処理する関数 33func answerReadRequest(request: CBATTRequest) -> Bool{ 34 var result: Bool = true 35 36 if ( request.characteristic.uuid == UUID1 ) { 37 print("recieve skipCharacteristic") 38 request.value = skipCharacteristic!.value 39 peripheralManager.respond(to: request, withResult: .success) 40 } else if ( request.characteristic.uuid == UUID2 ) { 41 print("recieve inCharacteristic") 42 let newData = makeData() 43 request.value = data 44 inCharacteristic.value = data 45 _ = peripheralManager.updateValue(inCharacteristic!.value!, for: inCharacteristic!, onSubscribedCentrals: nil) 46 } else { 47 /// ここに入った場合のみ失敗 48 print("unknown request") 49 print("uuid = (request.characteristic.uuid)") 50 result = false 51 } 52 53 return result 54} 55 56func makeData() -> Data{ 57 myClass.counter += 1 58 var array = [UInt8]() 59 array.append(contentsOf: withUnsafeBytes(of: &myClass.counter){ $0.map{ UInt8($0) } }) 60 61 let returnData = Data(array) 62 63 return returnData 64}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

Swiftは基本的にinoutをつけない限り値渡しである認識しています。

 Swiftに限らず、多くの言語では値渡しで行われます。ただ、値渡しで渡される内容が異なります。
Swiftの場合、Intなどのプリミティブやstructなどはオブジェクト全体が値渡しされます。一方で、classはオブジェクトの参照の値が渡されます。

CBATTRequestはクラスなので参照の値が渡されます。そのため、requestを書き換えても呼び出し元のrequestは書き換わりません(ただし、現在のSwiftは仮引数をlet扱いするのでそもそも書き換えができません)が、値(=参照)が指し示す先であるrequestの中身は書き換えることができます。その一方で、inoutを付加すると参照渡しになるので、呼び出し元のrequst自身を書き換えることができます。

投稿2020/01/06 06:48

編集2020/01/06 06:55
AOKINAO

総合スコア268

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

saku_panda

2020/01/06 07:35

ご回答ありがとうございます。 とても分かりやすく、助かりました。 クラスはオブジェクトの参照値を渡すんですね。 inoutをつけた場合とそうでない場合も記述していただけ、理解が深まりました。 ありがとうございました。
guest

0

「値/参照渡し」は迂闊に使うと戦争が起こる怖い言葉なので
Swiftのリファレンスに沿って説明します。(もうベストアンサーついてますが。)

リファレンスのInoutを読むと

「関数のパラメータはデフォルトで定数である。関数内でパラメータの値を書き換えようとするとコンパイルエラーとなる」
「関数内でパラメータの値を変更して、関数が終わってもその変更を維持したい場合にin-out parameterを使う」
とあります。
これを読んでもたしかに質問のようなことを疑問に思うかもしれませんが、
ここで重要なのは「パラメータの値」が何を意味しているかです。

渡されているパラメータの型:CBATTRequestclassです。
リファレンスのClassについて読むと、
classReference Typeである。
と書かれています。
これに続いてReference Typeとは、
「代入時や関数へ渡されるときに、コピーではなく同じインスタンスへの参照(reference)が使われる。」
と書かれています。

というわけで、質問文のコードにおいて、
request.valueの書き換えは「requestが参照しているインスタンスのvalueプロパティの書き換え」であって
requestの値(=どのインスタンスを参照しているか)は変更してないのでinoutをつけなくても合法です。

(なので仮に request = ...というコードを書く場合はinoutを付ける必要があります。)

投稿2020/01/06 08:08

編集2020/01/06 08:12
ozwk

総合スコア13551

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

saku_panda

2020/01/06 08:59 編集

公式を引用してのご解説ありがとうございます。 また > request.valueの書き換えは「requestが参照しているインスタンスのvalueプロパティの書き換え」であって > requestの値(=どのインスタンスを参照しているか)は変更してないのでinoutをつけなくても合法です。 上記の理解を深めたいと思っていたので、助かりました。 ベストアンサー決定後にも関わらず詳しい解説をありがとうございました。
guest

0

Swiftは基本的にinoutをつけない限り値渡しである認識しています。

引数にしたときに参照渡しされるか値渡しになるかは、変数の種類によって変わります。

classは参照渡しです。structとenumは値が渡されます。

CBATTRequestはclassなので、参照渡しです。inoutがなくても、もともと参照渡しなので、渡されたオブジェクトの内容を変更することができます。

投稿2020/01/06 06:08

eytyet

総合スコア803

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

saku_panda

2020/01/06 07:35

ご回答ありがとうございます。 クラスの参照渡しについての理解が不足しておりました。 ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問