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()
}
4
Scaraux 7 listopad 2018, 01:25

1 odpowiedź

Najlepsza 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.

4
OOPer 7 listopad 2018, 14:42