Создатели современных языков программирования всеми силами пытаются увести программистов от прямой работы с указателями и памятью, либо вообще не включая в язык подобные возможности (например, 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 есть ряд подводных камней и ограничений, но они достойны отдельной заметки)
Автор: RedRover