Работаем с таймерами.

До появления вот этой темы на РСДНе я как-то не предавал особого значения таймерам в Mac OS X. Ну есть они и есть, как будет нужно так разберусь. Но как оказалось, стандартных POSIX вызовов, таких как timer_create/timer_delete у нас нет, а то что есть не всегда подходит.

Хорошенько поковырявшись в документации я пришел к следующему выводу. Всего доступны 3 способа работы с таймерами, дающие приблизительно одинаковый эффект:

  • NSTimer из Foundation;
  • RunLoop из CoreFoundation;
  • Таймеры из Mach. Хотя у меня данный пункт вызвал некоторые трудности.

Так что по порядку, с краткими иллюстрациями.

Continue reading

cURL и корневые сертификаты

В описанном раньше способе работы с HTTPS соединением есть одна проблема – необходимо каким-то способом тащить за собой актуальный список корневых сертификатов. А тащить, обновлять, следить за изменениями… Вобщем задача не из тривиальных.
Не знаю как на Венде, а в Mac OS X можно все это дело переложить на Apple:

sudo security find-certificate -a -p /System/Library/Keychains/SystemRootCertificates.keychain > roots.pem

После чего передать полученный список корневых сертификатов в cURL.

HTTPS & cURL

В пятницу, работая над одной из наших подсистем, я занимался прикручиванием cURL и тихо охреневал с того, что пишет народ народ в интернете по поводу реализации работы с HTTPS при использовании cURL. Лично я всегда считал что HTTPS необходим для установки защищенного соединения. Из чего следует что, во-первых, данные передаются в шифрованном виде, и во-вторых, мы знаем с кем общаемся.
Так вот, как выяснилось на практике, если с шифрованием ни у кого вопросов не возникает, то вот на авторизацию все кладут болт.
Причем основательно, например так:

curl_easy_setopt( curl_, CURLOPT_SSL_VERIFYPEER, 0 );

Что отключает проверку полученного сертификата. Ну а особо одаренные делают даже так:

curl_easy_setopt( curl_, CURLOPT_SSL_VERIFYHOST, 0 );

Что отключает проверку соответствия хоста, указанного в сертификате, с хостом, с которым мы общаемся.

Что интересно, если проверку канала обычно отключают из за глупости и лени, то проверку хоста может отключить разве что феерический идит, но, как показал гугл, их полно. Подобное решение для меня большая загадка, зачем нужно шифровать данные, если не известно с кем идет общение?

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

error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed

В то же время информации том, что делать в такой ситуации в интернете по большому счету нет. Но все не так уж и страшно, всего-то немного терпения и экспериментов.

Все решается довольно просто. Необходимо скачать набор сертификатов с сайта cURL и передать их в библиотеку при помощи CURLOPT_CAINFO. Хотя в документации к библиотеке и говорится что эта функция не работает под Windows, это не соответствует действительности. Она отлично работает как в Mac OS X так и в Windows. В cURL для установки сертификатов будет вызвана OpenSSL функция SSL_CTX_load_verify_locations, соответственно все сертификаты должны быть в PEM формате.

Собственно это все, после этого все должно отлично работать.

GDB. Легко и даже изящно.

Хотя в начале GDB вызывал у меня ощущение ужаса и сильно отторжение, сейчас я к нему привык и он мне даже начал нравится. Что интересно, пришлось выучить не так уж и много команд.

Итак, по группам.

Запуск отладки

attach pid – присоединение к процессу с идентификатором pid для отладки.
set args args – установка args в качестве аргументов приложения.
run – запустить открытое приложение на отладку.
continue – продолжить выполнение приложения (например после того как сработала точка останова или в случае в присоединением к процессе).

Установка точек останова.

break file:line – установить точку останова на заданную линию заданного файла.
break func_name – установить точку останова на заданную функцию.
break – установить точку останова на текущую линию в файле.
watch – установка точки наблюдения за изменением состояния переменной (write).
rwatch addr – установка точки наблюдения за изменением состояния переменной (read). Например команда rwatch *0xfeedface, означает что необходимо наблюдать за изменением данных находящихся по адресу 0xfeedface.

Сигналы

handle SIGNAL_NAME nostop – не останавливаться на сигнале SIGNAL_NAME. Бывает очень полезно для SIGPIPE.

Управление отладкой

n – шаг, не заходя в функцию.
s – шаг в функцию.
fin – закончить функцию.

Просмотр состояния объектов

whatis – информация о типе объекта по указателю на него.
ptype – детальное описание типа.
print – распечатать значение объекта.
Кроме того, необходимо скачать файл stl-views.gdb, который легко гуглится по фразе “STL GDB evaluators/views/utilities”. Благодаря нему появляется возможность выводить содержимое контейнеров STL. Например, появляются команды pvector, pmap и т.д.

Сбор дополнительной информации

bt – показать вызовов.
i sharedlibrary – показать список загруженный динамических библиотек.
i threads – показать все активные потоки.
i line numb – отобразить информацию о линии с номером numb.
i thread – детальная информация по текущему потоку.
thread apply all command – применить команду command ко всем потокам. Очень полезно использовать thread apply all bt для вывода стека всех потоков.
frame – текущая позиция в отлаживаемом коде.
list – отобразить линию либо функцию. Если вызывается без аргументов, отображается текущая линия.
disassemble start end – диассемблировать код начинающийся по адресу start и заканчивающийся на адресе end. Команда бывает полезна в купе с i line.

Далее. Крайне желательно установить скрип который можно найти у IBM вот тут.
Благодаря ему открываются такие прекрасные команды как:

reg – вывести содержимое регистров.
dd addr – вывести содержание памяти по адресу addr в формате HEX | ASCII.

Самописные ништяки

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

define bsave
shell rm -f brestore.txt
set logging file brestore.txt
set logging on
info break
set logging off
# reformat on-the-fly to a valid gdb command file
shell perl -n -e 'print "break $1n" if /^d+.+?( S+)$/g' brestore.txt > brestore.gdb
shell rm -f brestore.txt
end
document bsave
store actual breakpoints
end
define brestore
source brestore.gdb
end
document brestore
restore saved breakpoints
end

И вывод значений wchar_t строки:

define pwchar
echo "
set $c = ( wchar_t*)$arg0
while ( *$c )
if ( *$c > 0x7f )
printf "
[ %x]", *$c
else
printf "
%c", *$c
end
set $c++
end
echo "
n
end

Вобщем как-то так. Еще планирую написать о том, как искать падения по дампам при помощи GDB, что тоже довольно занимательно.

CodeReview. Теория и практика.

CodeReview – классная штука, не правда-ли? Если отвлеченно подумать об открывающихся возможностях и количестве потенциальных ошибок, найденных при проведении CodeReview, то начинает казаться что практика просто великолепна и мега полезна.
Обычно, путь к внедрению этой практики извилист и тернист, но ништяки, которые по легенде ожидают прошедшего, заставляют забыть о сложной дороге.
Как ни странно, первым делом, обычно, создается стандарт кодирования. Думаю, это вызвано тем что любая уважающая себя компания должна иметь собственный стандарт. В стандарте обязательно нужно прописать где ставить скобочки, а где пробелы, какие индентификаторы писать с прописной буквы, а какие со строчной. Стандарт просто необходимо согласовать, проревьювить раз надцать и утвердить. После этого весь код будет однородным и очень красивым, не зря же стандарт составляли лучшие из лучших компании! Continue reading

Objective-C биндинги

Наверное, наиболее привлекательной возможностью в Objective-C я бы назвал биндинги. И если про то, как связать данные с графическое представлением при помощи InterfaceBuilder написано очень и очень много (даже на русском ), то вот о том, как и почему вся эта магия работает можно найти разве что на сайте Apple.
Итак, вся технология биндингов базируется на двух паттернах: Key-Value Coding и Key-Value Observing. Думаю, для начала стоит написать именно про эти паттерны, а потом перейти к самим биндингам.

Итак, Key-Value Coding позволяет обратиться к данным класса по имени, включая вложенную обращения с учетом вложенной иерархии. Для начала небольшой пример иллюстрирующий, о чем же идет речь.

Пусть будет класс MyClass, следующего вида:

@interface MyClass 
@property NSString *stringProperty;
@property NSInteger integerProperty;
@property MyClass *linkedInstance;
@end

Обычно, доступ к данным этого класса реализуется как-то так:

MyClass *myInstance = [[MyClass alloc] init]; 
NSString *string = myInstance.stringProperty;
myInstance.integerProperty = 2;

MyClass *anotherInstance = [[MyClass alloc] init];
anotherInstance.integerProperty = 5;
myInstance. linkedInstance = anotherInstance;
myInstance. linkedInstance. integerProperty = 10;

В то же время, при использовании Key-Value Coding можно написать так:

MyClass *anotherInstance = [[MyClass alloc] init]; 
[anotherInstance setValue:5 forKey:@"integerProperty"];
[myInstance setValue:anotherInstance forKey:@"linkedInstance"];
[myInstance setValue:[NSNumber numberWithInt:10]
forKeyPath:@"linkedInstance.integerProperty"];

Для работы KVO паттерна необходимо чтобы класс соответствовал следующим условиям:
– Должны присутствовать методы –, -is, либо должна существовать переменная класса <key>;
– Для изменяемых значений так же необходимо наличи метода -set:;
– Метод -set: не должен проверять устанавливаемое значение.
– Если проверка устанавливаемого значения все-же необходима, в классе должен присутствовать метод -validate:error:.
На мой взгляд, самый простой способ поддержать эти требования – это создать это использовать свойства.

Для получения значений атрибутов используется метод valueForKey:. В случае если необходимо запросить значение с использованием пути, необходимо воспользоваться методом valueForKeyPath:. В случае, если запрашиваемый ключ отсутствует возбуждается исключение NSUndefinedKeyException.

Задание значения происходит полностью по аналогии с получением значения. Есть методо setValue:forKey:, устанавливающий значение указанного атрибута и setValue:forKeyPath: если необходимо задать путь. При отсутствии заданного ключа, так же будет возбуждено исключение NSUndefinedKeyException.

А вот функционал операций с коллекциями меня приятно удивил. В Objective-C есть операторы коллекций (Collection Operators), благодаря которым можно манипулировать с коллекциями по подобию SQL-запросов. Само собой, возможности очень ограниченные, тем не менее полезные.
Операторы коллекций записываются следующим образом:
путьВКоллекции.@операторКоллекции.путьКСвойству

Операторы коллекций делятся на 3 группы: простые операторы, операторы объектов и операторы массивов.
К простым операторам относятся @avg, @count, @max, @min, @sum. Их использование выгляди следующим образом. Допустим есть массив объектов Transaction с полями payee, amount и date. Тогда для получения среднего значения по полю amount можно написать такой запрос:

NSNumber *transactionAverage=[transactions valueForKeyPath”@avg.amount”];

А вот с операторы объектов радуют еще больше, например для получения выборки уникальных значений поля payee можно воспользоваться вот таким запросом:

NSArray *payees=[transactions valueForKeyPath"@distinctUnionOfObjects.payee"];

Кроме того, есть еще один оператор объектов – @unionOfObjects.

Операторы массивов, вобщем-то тоже самое что и операторы объектов, только применяются к нескольким массивам, содержащим однотипные объекты, одновременно. Какой-то практической пользы я в них не нашел.

Разве что отсутствие возможности добавить собственный оператор для работы с коллекциями меня огорчило. Может я и не прав, но никакой информации о том, как добавить еще один оператор я не нашл.

Вторая составляющая механизма биндингов это Key-Value Observing (KVO). KVO позволяет получать уведомления об изменении заданных свойств других объектов. Проще говоря, это callback механизм, работающий по заданному стандарту.

Для регистрации observer используется сообщение addObserver:forKeyPath:options:context:. Например для того чтобы получать сообщения об изменениях свойства openingBalance объекта account необходимо послать следующее сообщение:

[account addObserver:self
forKeyPath:@"openingBalance" options:(NSKeyValueObservingOptionNew) context:NULL];

В результате чего, при изменении значения openingBalance объекта account будет вызван метод observeValueForKeyPath:ofObject:change:context: для self. В качестве context можно передать любой C-указатель.

Нотификации об изменении свойства приходит в метод observeValueForKeyPath:ofObject:change:context:. Нужно не забывать о том, что полученное сообщение должно быть проброшено дальше:

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
{
// to-do smth.
[super observeValueForKeyPath:keyPath
ofObject:object change:change
context:context];
}

Если получать нотификации больше не требуется, необходимо вспользоваться методом removeObserver:forKeyPath:.

Несмотря на то, что в большинстве случаев вполне достаточно автоматического извещения об изменениях свойств, уведомления можно сделать и в ручную. Лично мне эта возможность пригодилась только однажды – для оптимизации скорости работы GUI. Дело в том, что при отображении массива динамически изменяемых данных, интерфейс начинает перерисовываться при изменении каждого из элементов массива. И, как следствие, вместо одного обновления всех элементов разом, происходило N обновлений, что требовало дополнительных ресурсов.
К счастью, при помощи метода willChangeValueForKey: можно сообщить о том, что началось изменение значений для определенного ключа и закончить его вызовом метода didChangeValueForKey:.

Честно сказать, после того как становится понятно с KVO и KVC говорить про биндинги уже особо и нечего. Для создания биндинга используется сообщение bind:toObject:withKeyPath:options:, которое говорит получателю поддерживать заданный атрибут в синхронном состоянии. Например сообщение [joystick bind:@”angle” toObject:GraphicController withKeyPath:@”selection.shadowAngle” options:options]; можно интерпретировать следующим образом:
– bind: @”angle” если значение ассоциируемое с angle изменится,
– toObject: GraphicController то необходимо сообщиться заданному объекту GraphicController
– withKeyPath: @”selection.shadowAngle” что его значение selection.shadowAngle было изменено
– options: options используя трансформер options

Так же стоит заметить, что в iOS метод bind:toObject:withKeyPath:options: вообще не доступен. И при необходимости реализуется самостоятельно.

Для упрощения работы с биндигами, существует ряд стандартных контроллеров, облегчающих работу с массивами и древовидными структурами данных. Это NSArrayController, NSTreeController и NSDictionaryController. Кроме того, существуют контроллеры для управления видами NSViewController, объектами NSObjectController и пользовательскими настройками UserDefaultsController.