Pull to refresh

Unsafe в Swift

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

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

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

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

Как?

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

Например:

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

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

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

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 есть ряд подводных камней и ограничений, но они достойны отдельной заметки)
Tags:
Hubs:
Total votes 12: ↑10 and ↓2+8
Comments15

Articles