Unsafe в Swift

от автора

Создатели современных языков программирования всеми силами пытаются увести программистов от прямой работы с указателями и памятью, либо вообще не включая в язык подобные возможности (например, Java) либо маркируя их страшными словами unsafe (C#). Пишущим на swift повезло, этот язык попал во вторую категорию, и хотя это не рекомендуется, а в документации встречаются предупреждения о возможных утечках памяти и прочих страшилках возможность такая есть!

В этой статья я хотел бы рассмотреть работу с указателями в swift, а так же порассуждать для чего это вообще может понадобиться.

И так, первый вопрос Зачем?

В большинстве случаев программируя на swift под iOS работать с указателями не приходится, более того большинство программистов вообще никогда не работали с указателями явным образом. Тем не менее подобный низкоуровневый код в некоторых случаях позволяет оптимизировать программу либо по скорости, либо по количеству используемой памяти. Коме того подобная возможность интересна с академической точки зрения.

Как?

Типичная работа с указателями состоит из трех шагов: выделение памяти, работа с выделенным пространством и освобождение памяти.

Например:

let p = UnsafeMutablePointer<Int>.alloc(1) p.memory = 20 p.dealloc(1) 

С точки зрения swift, указатель это generic соответствующего типа (в данном случае целочисленный)
Первая строка тут выделяет память, вторая инициализирует, последняя освобождает.

Впрочем, подобным образом возится с одним целочисленным значением не особо полезно, другое дело если нам нужен временный массив:

let a = UnsafeMutablePointer<Int32>.alloc(N) memset(a, 0, sizeof(Int32) * N) 

Тут мы выделили память под массив «a», с типом элементов Int32 размера N и инициализировали его установив значения sizeof(Int32) * N байт в 0.

Теперь, скопируем массив, команда:

var b = a 

в отличии от swift массивов копирует не сами данные, а указатель на них. Данные можно скопировать так:

let b = UnsafeMutablePointer<Int32>.alloc(N) memcpy(b, a, sizeof(Int32) * N) 

Итерацию по такому массиву можно сделать достаточно просто:

for var i = 0; i < N; i++ {     a[i] = <...> } 

А можно с использованием математики указателей:

var p = a while p < a + N {     p.memory = <...>     p++ } 

(Прибавляя к указателю целое число мы смещаем адрес на который он указывает на соответсвующее количество элементов)

Впрочем, довольно скучной теории, попробуем решить с помощью указателей какую-нибудь задачу.
Например, получим размер картинки из файла формата GIF.

Для этого нам понадобится подопытный файл и описание заголовка формата:

Offset Length Contents
0 3 bytes «GIF»
3 3 bytes «87a» or «89a»
6 2 bytes Width
8 2 bytes Height

Сначала, прочитаем GIF файл и получим указатель на начало последовательности прочитанных байт:

let fileName = NSBundle.mainBundle().pathForResource("SomeGif", ofType: "gif")! let data = NSData(contentsOfFile: fileName)!          guard data.length > 10 else {     return }  var p = UnsafeMutablePointer<Void>(data.bytes) 

Таким образом указатель «p» ссылается на начало буфера с данными из GIF файла, мы так же проверили что размер буфера более 10 байт (именно столько мы собираемся прочитать).

Согласно описанию формата, первые 3 байта в заголовке должны быть строкой «GIF», для проверки создадим Swift строку на основе первых трех байт из буфера и проведем проверку:

let str = String(bytesNoCopy: p, length: 3, encoding: NSASCIIStringEncoding, freeWhenDone: false)          guard str == "GIF" else {     return } 

Таким образом мы создаем Swift строку интерпретируя первые 3 байта на которые ссылается указатель p, как набор символов в кодировке ASCII, самое примечательное что сами данные из буфера не копируются!

Далее, если проверка прошла успешно, прочитаем интересующий нас размер картинки. Для этого используем структуру из двух 16 битных целых:

struct GifSize {     var width: UInt16     var height: UInt16 } 

Согласно формату, нужно сместиться относительно начала файла на 6 байт (первые три из которых мы ранее интерпретировали как строку) и интерпретировать следующие 4 байта как два 16 битных числа:

p += 6 let pSize = UnsafeMutablePointer<GifSize>(p) NSLog("Gif width = \(pSize.memory.width), height = \(pSize.memory.height)") 

Таким образом мы получили размер картинки в GIF файле прочитав его заголовок без сторонних библиотек используя указатели!

(Вообще говоря с подобным использованием структур в swift есть ряд подводных камней и ограничений, но они достойны отдельной заметки)

ссылка на оригинал статьи https://habrahabr.ru/post/276005/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *