Mam funkcję, która pozwala mi odczytać liczbę (Integer, Double itp.) z pliku binarnego przy użyciu typów ogólnych. Na przykład, jeśli oczekuję Int64
, przeczytam 8 bajtów...
// A simple function that read n bytes from a FileHandle and returns
// the data
public func read(chunkSize: Int) -> Data {
return self.handle!.readData(ofLength: chunkSize)
}
// A function that reads the proper amount of bytes specified
// by the return type which in my case would be an integer
public func readNumber<I>() -> I? {
let data: Data = self.read(chunkSize: MemoryLayout<I>.size)
if data.count == 0 {
return nil
}
return data.withUnsafeBytes { $0.pointee }
}
readNumber
losowo zwraca zero bez powodu. Nie z czeku count
, ale z ostatniej linii.
Jednak doskonale działa, gdy rzucam do I
w ten sposób:
return data.withUnsafeBytes { $0.pointee } as I
Dlaczego ?
EDYTUJ:
Odtworzyłem to za pomocą Playgrounds :
class Test {
public func read(chunkSize: Int) -> Data {
return Data(repeating: 1, count: chunkSize)
}
public func readNumber<T>() -> T? {
let data: Data = read(chunkSize: MemoryLayout<T>.size)
if data.count == 0 {
return nil
}
return data.withUnsafeBytes { $0.pointee }
}
public func example() {
let value2: Double = readNumber()!
print(value2)
}
}
let test = Test()
for i in 0..<1000 {
test.example()
}
1 odpowiedź
Wygląda na to, że muszę trochę poprawić mój komentarz. Nawet jeśli Swift działa konsekwentnie zgodnie z programem, wynik może wydawać się losowo zmieniany, gdy masz problem z pamięcią, taki jak dostęp poza granicami.
Najpierw przygotuj magiczne rozszerzenie dla UnsafePointer
:
extension UnsafePointer {
var printingPointee: Pointee {
print(Pointee.self) //<- Check how Swift inferred `Pointee`
return self.pointee
}
}
I trochę zmodyfikuj swój kod EDIT:
class Test {
public func read(chunkSize: Int) -> Data {
return Data(repeating: 1, count: chunkSize)
}
public func readNumber<T>() -> T? {
let data: Data = read(chunkSize: MemoryLayout<T>.size)
if data.count == 0 {
return nil
}
print(T.self) //<- Check how Swift inferred `T`
return data.withUnsafeBytes { $0.printingPointee }
}
public func example() {
let value2: Double = readNumber()!
print(value2)
}
}
let test = Test()
for _ in 0..<1000 {
test.example()
}
Wynik:
Double Optional<Double> 7.748604185489348e-304 Double Optional<Double>
Wątek 1: Błąd krytyczny: nieoczekiwanie znaleziono zero podczas rozpakowywania wartości opcjonalnej
Ile pokazanych par Double
i Optional<Double>
byłoby pozornie losowe, ale przyczyna tego zachowania jest dość jasna.
W tym wierszu return data.withUnsafeBytes { $0.printingPointee }
Swift wyprowadza typ $0
jako UnsafePointer<Optional<Double>>
.
W obecnej implementacji Swift Optional<Double>
zajmuje 9 bajtów w pamięci:
print(MemoryLayout<Optional<Double>>.size) //-> 9
Tak więc $0.pointee
uzyskuje dostęp do 9 bajtów zaczynając od wskaźnika, chociaż wskaźnik wskazuje na region 8-bajtowy:
|+0|+1|+2|+3|+4|+5|+6|+7|+8|
+--+--+--+--+--+--+--+--+
01 01 01 01 01 01 01 01 ??
<-taken from the Data->
Jak wiesz, dodatkowy 9-ty (+8
) bajt nie może być przewidywalny i może pozornie być losowy, co jest wskaźnikiem nil
w Optional<Double>
.
Dokładnie to samo wnioskowanie działa w twoim kodzie. W readNumber<T>()
typ zwracany jest wyraźnie zadeklarowany jako T?
, więc w wierszu return data.withUnsafeBytes { $0.pointee }
jest bardzo naturalne, że Swift wyprowadza typ $0.pointee
jako Double?
alias Optional<Double>
.
Wiesz, że możesz kontrolować ten typ wnioskowania, dodając as T
.
Podobne pytania
Nowe pytania
swift
Swift to bezpieczny, szybki i ekspresyjny język programowania ogólnego przeznaczenia opracowany przez Apple Inc. dla platform i Linux. Swift jest źródłem open. Użyj tagu tylko dla pytań dotyczących funkcji językowych lub wymagających kodu w Swift. Użyj tagów [iOS], [iPados], [MacOS], [Watch-OS], [TVOS], [Swiftui], [Cocoa-Touch] i [Cocoa] dla (język-agnostyczne) pytania o platformach lub ramy.