1.8K Views
August 24, 24
スライド概要
iOSDC Japan 2024 day2のLT資料です。
https://fortee.jp/iosdc-japan-2024/proposal/ae98baba-0a88-4c1a-b8d7-b8e1ff8a1f58
SwiftとLEGOとBluetooth LEが好きなプログラマ
あぶないDataプログラミング 大庭 慎一郎 @ooba / @bricklife
Data型 import Foundation // GET let (data, _) = try await URLSession.shared.data(from: url) let repository = try JSONDecoder().decode(Repository.self, from: data) // POST let postData = "user=ooba&password=ilovelego".data(using: .utf8) var request = URLRequest(url: url) request.httpMethod = "POST" request.httpBody = postData try await URLSession.shared.data(for: request) // ファイル書き込み let pngData = uiImage.pngData() try pngData?.write(to: fileUrl)
Core BluetoothでもData型でやりとり カラーセンサーが赤を検知した モーターAを50%のパワーで回せ Data([0x05,0x00,0x45,0x01,0x09]) Data([0x08,0x00,0x81,0x00,0x11,0x51,0x00,0x32])
Array<UInt8>みたいに使える
Data ≠ Array<UInt8> 1. Data.SubSequence = Data 2. startIndexが0になるとは限らない
問題のコード
func getUInt32(from data: Data) -> UInt32 {
switch data.count {
case 0:
return 0
case 1:
return UInt32(data[0]) // !
case 2, 3:
return UInt32(data[1]) * 256 + UInt32(data[0]) // ☠
default:
return data.withUnsafeBytes { $0.load(as: UInt32.self) } // #
}
}
問題のコード
func getUInt32(from data: Data) -> UInt32 {
switch data.count {
case 0:
return 0
case 1:
return UInt32(data[0]) // !
case 2, 3:
return UInt32(data[1]) * 256 + UInt32(data[0]) // ☠
default:
return data.withUnsafeBytes { $0.load(as: UInt32.self) } // #
}
}
getUInt32(from: Data([1, 2]).dropFirst()) // !でクラッシュ
getUInt32(from: Data([1, 2, 3]).dropFirst()) // ☠でクラッシュ
getUInt32(from: Data([1, 2, 3, 4, 5]).dropFirst()) // #でクラッシュ
startIndex ≠ 0なSubSequenceを作る • dropfirst(_:) • drop(while:) • suffix(_:) • suffix(from:) • subscript(_:) • など
Collection.SubSequence https://developer.apple.com/documentation/swift/collection/subsequence
Data.SubSequenceのインデックス共有 let data1 = Data([0x0a, 0x0b, 0x0c, 0x0d, 0x0e]) // Data型 let data2 = data1.dropFirst() // Data型 let data3 = data1.dropLast() // Data型 data1 0 1 2 3 4 0x0a 0x0b 0x0c 0x0d 0x0e 0x0b 0x0c 0x0d 0x0e 0x0b 0x0c 0x0d data2 data3 0x0a
Array.SubSequence = ArraySlice let array1 = [UInt8]([0x0a, 0x0b, 0x0c, 0x0d, 0x0e]) // Array<UInt8>型 let array2 = array1.dropFirst() // ArraySlice<UInt8>型 let array3 = array1.dropLast() // ArraySlice<UInt8>型 array1 0 1 2 3 4 0x0a 0x0b 0x0c 0x0d 0x0e 0x0b 0x0c 0x0d 0x0e 0x0b 0x0c 0x0d array2 array3 0x0a
Array型のstartIndexは常に0 https://developer.apple.com/documentation/swift/array/startindex
関数の前半部分を直す
func getUInt32(from data: Data) -> UInt32 {
switch data.count {
case 0:
return 0
case 1:
return UInt32(data[0])
case 2, 3:
return UInt32(data[1]) * 256 + UInt32(data[0])
default:
return data.withUnsafeBytes { $0.load(as: UInt32.self) }
}
}
startIndexからのオフセットにする
func getUInt32(from data: Data) -> UInt32 {
let s = data.startIndex
switch data.count {
case 0:
return 0
case 1:
return UInt32(data[s])
case 2, 3:
return UInt32(data[s + 1]) * 256 + UInt32(data[s])
default:
return data.withUnsafeBytes { $0.load(as: UInt32.self) }
}
}
別解:インデックスを使わないで書く
func getUInt32(from data: Data) -> UInt32 {
if data.count < 4 {
let firstByte = data.first ?? 0
let secondByte = data.dropFirst().first ?? 0
}
return UInt32(secondByte) * 256 + UInt32(firstByte)
} else {
return data.withUnsafeBytes { $0.load(as: UInt32.self) }
}
関数の後半
func getUInt32(from data: Data) -> UInt32 {
let s = data.startIndex
switch data.count {
case 0:
return 0
case 1:
return UInt32(data[s])
case 2, 3:
return UInt32(data[s + 1]) * 256 + UInt32(data[s])
default:
return data.withUnsafeBytes { $0.load(as: UInt32.self) }
}
}
https://developer.apple.com/documentation/swift/unsaferawbufferpointer/load(frombyteoffset:as:)
メモリを別の型に割り当てる let data = Data([0x00, 0x00, 0x20, 0xc0]) data.withUnsafeBytes { $0.load(as: UInt32.self) } // 3,223,322,624 data.withUnsafeBytes { $0.load(as: Int32.self) } // -1,071,644,672 data.withUnsafeBytes { $0.load(as: Float.self) } // -2.5
不適切なメモリ配置だとクラッシュ let data = Data([0xff, 0xff, 0xff, 0x00, 0x00, 0x20, 0xc0, 0xff]) data.dropFirst(3).withUnsafeBytes { $0.load(as: Float.self) } // # data.suffix(from: 3).withUnsafeBytes { $0.load(as: Float.self) } // # data[3...6].withUnsafeBytes { $0.load(as: Float.self) } // # data.withUnsafeBytes { $0.load(fromByteOffset: 3, as: Float.self) } // #
Swift 5.7でloadUnalignedメソッドが登場 https://github.com/swiftlang/swift-evolution/blob/main/proposals/0349-unaligned-loads-and-stores.md
完成した関数
func getUInt32(from data: Data) -> UInt32 {
let s = data.startIndex
switch data.count {
case 0:
return 0
case 1:
return UInt32(data[s])
case 2, 3:
return UInt32(data[s + 1]) * 256 + UInt32(data[s])
default:
return data.withUnsafeBytes {
$0.loadUnaligned(as: UInt32.self)
}
}
}
Unsafe系はなんか怖い!
init(bitPattern:) let data = Data([0xff, 0xff, 0xff, 0x00, 0x00, 0x20, 0xc0, 0xff]) let s = data.startIndex + 3 let uint32 = UInt32(data[s + 3]) << 24 + UInt32(data[s + 2]) << 16 + UInt32(data[s + 1]) << 8 + UInt32(data[s]) print(uint32) // 3,223,322,624 print(Int32(bitPattern: uint32)) // -1,071,644,672 print(Float(bitPattern: uint32)) // -2.5
ありがとうございました