пятница, 30 января 2015 г.

Как я боролся с iOS 8.1.3

Давно я не брал в руки йОС, но давеча мне говорят: "ну-ка, покажи нам на айпаде пару новых идей"! Я говорю: "сей момент"! И показываю. А потом, пока я ещё радостный и добрый, айпад тихо так говорит, что хочет чего-то новенького - обновиться желает. И я широким жестом разрешаю, а мне - оп-па! - и геморрой хидэйк на сутки - Unable to install package. (e800003a). и Unable to install package. (e8008016).

Ребята, кому есть до этого дело:
https://www.danielwolf.eu/blog/2015/1458-delphi-mobile-und-ios-8-1-3
http://blogs.embarcadero.com/ao/2013/05/24/39472

четверг, 22 января 2015 г.

Как хранить числа в объектах TStringList

Как хранить числа в объектах TStringList прекрасно известно: MyStrings.AddObject( MyString, TObject( MyNumber ) )...казалось бы. Однако, мне (да вот - дошла очередь и до меня) пришлось убедиться, что к словам "прекрасно известно" следует добавить слово "было" - прекрасно известно было. Увы, двадцать лет работавший в Win32 и даже в Win64 способ теперь уже не работает. Точнее, не работает на мобильных платформах, т.е. там, где действует незримая власть коварного AUTOREFCOUNT aka ARC.

Я наступил на эти грабли (а читать книжки - для чайников), когда в мобильном приложении надо было выполнить две простые задачи. Первая - заполнять два связанных комбобокса: заполняешь одно поле, и если второе ещё не задано, то взять для него значение по-умолчанию. Вторая задача - красить элементы списка в зависимости от связанного значения - красный, жёлтый, голубой -  выбирай себе любой.


Цвет идёт к справочнику в виде строки #rrggbb - тут StringToAlphaColor() сразу в руки. Получаем бинарное значение TAlphaColor, которое на самом деле = type Cardinal, т.е. то, что отличнейшим образом укладывается в AddObject() рядом со строчкой из справочника. Теперь, получая данные, я легко в справочнике найду соответствующий цвет: 'критично'-#ff0000, 'неважно'-#cccccc, и т.д. Один раз цвета распознал, а потом пользуйся сколько влезет. Только обязательно надо не забыть включить для цвета канал Alpha (например, Result := TAlphaColorRec.Alpha + AColor), а то нарисованное будет настолько прозрачным, что его станет совершенно невозможно разглядеть.

Связка справочников - тоже штука банальная. У меня в одном из двух справочников дано дефолтное значение из второго. Надо при заполнении TStringList просто бежать по первому списку и искать в предварительно заполненном втором IndexOf() и класть в объекты первого списка найденные индексы, а текущий счётчик класть, разумеется, в объекты второго списка с этими индексами. Тут, правда, есть нюанс: нулевое значение nil, которым инициализируются объекты списков, будет в виде целого числа равно нулю, что означает не пустое значение, а индекс первого элемента. Надо быть внимательнее - ставить -1 везде, где только можно, или при сохранении найденных индексов добавлять единицу, а потом об этом надо не забывать, и оперировать уже с отниманием единицы. Я лично предпочитаю второй способ - точно не нарвёшься на случайный ноль.


Не долго думая, я сделал прототип на Win32. И всё у меня прекрасно заработало: метки красились нужным цветом, словари дружно связывались. Ура! Осталось только скомпилить под Андроид, который требовался Заказчику, и ... Вот тут-то и обнаружилось, что связки не связываются, а цвета не цветут. Как же так?

Чтобы не рвать куски из проекта, возьмём надуманный пример. Создадим проект пустого мобильного приложения, добавим в дереве проекта платформу Win32 и вставим на событие OnCreate главной и единственной формы код, что дан ниже (имя формы только подкорректируйте под себя). Ах, да - не забудем ещё бросить на форму TMemo c Align = alClient ( все эти манипуляции читатель может делать мысленно ).

const
  Labels: array [1..5] of String = ('Lorem','ipsum','dolor','sit','amet');
 
procedure TForm6.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  with TStringList.Create do
  try
    for I := Low(Labels) to High(Labels) do
      AddObject( Labels[ I ], TObject( I ) );
    Sort;
    for I := 0 to Count-1 do
      Strings[ I ] := Format( '%s=%d', [ Strings[ I ], Integer( Objects[ I ] ) ] );
    Memo1.Lines.Text := Text;
  finally Free;
  end;
end;

Ожидаемый результат получен:


А вот Андроидная версия скорее всего при запуске выдаст чёрный экран, упёршись в первую встречу с AddObject(). Не будем разбирать на части коробку передач, а удовлетворимся фактом, что нейтраль - тут, а чтобы ехать - там. Ясно, что использовать непонятные адреса для объектов, когда тут и там ссылки считают - это как ходить по коровьему пастбищу с закрытыми глазами.

А у меня в проекте этот фокус прошёл. Я поназаполнял словарей, развесил там индексы и цвета и пошёл дальше читать уже данные. И только когда стал доставать из закромов все свои богатства, то обнаружил сплошные nil. И сильно удивился - мыши съели?! - Так нет здесь мышей! Ну да говорят, что удивляться - это даже полезно.

Что делать? Проект - вон он уже дышит и играет мускулами! Не будешь же перетрахивать (© Лукашенко А.Г.) его из-за какой-то мелочи. Сейчас дадим таблеточку, и будет как здоровый!

type
  TData<T> = class
    FValue: T;
    constructor Create( const AValue: T );
  end;
 
const
  Labels: array [1..5] of String = ('Lorem','ipsum','dolor','sit','amet');
 
procedure TForm6.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  with TStringList.Create( True ) do
  try
    for I := Low(Labels) to High(Labels) do
      AddObject( Labels[ I ], TData<Integer>.Create( I ) );
    Sort;
    for I := 0 to Count-1 do
      Strings[ I ] := Format( '%s=%d', [ Strings[ I ], TData<Integer>( Objects[ I ] ).FValue ] );
 
    Memo1.Lines.Text := Text;
  finally Free;
  end;
end;
 
{ TData<T> }
 
constructor TData<T>.Create(const AValue: T);
begin
  FValue := AValue;
end;

Просто? Да! Мне даже как-то понравилось - появилось ещё "пустое значение": nil=Null. Так что и игры с плюс-минус единицей для индексов можно выкинуть. А если, например, нужно Альфа-канал цвета устанавливать по месту, то уже ни за что не спутаешь чёрный цвет с прозрачным - 0 уже не равен nil! И кода лишнего - с гулькин нос. Главное - при создании TStringList включить  параметр OwnsObjects, чтобы объекты прибивались самим списком, и всё. Красота!


...............

Конечно, старики в стойбище ворчат, что на новой мягкой земле уже нельзя просто оставить пойманную рыбу, как раньше на льду, а надо держать в садке, привязанном верёвкой к колышку, чтобы рыба не стухла, или не стащил кто. И чтобы оттуда достать рыбу, надо теперь этот садок вытягивать из воды, лезть в него рукой и там шарить. Как раньше хорошо было! А теперь таскайся с этой штукой. Это же, конечно, ещё и денег каких-то стоит!  Ох уж эти новые края с их климатом! Как тут другие люди живут?...

вторник, 20 января 2015 г.

Как несовершенны константные аргументы

Я тут попал в такие чудеса на виражах, что уже хотел застрелить компилятор и повеситься на шею кому-нибудь с жилеткой, чтобы рыдать аки дитя малое. Меня обидел компилятор! И причём не где-нибудь, а в Win32!!! Ну, посудите сами - он ссылки на строку посчитать не может, и втихаря вместо одной переменной, присваивает значения двум! Вот сейчас расскажу как.

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

И что-то не совсем правильно работает. Я смотрю - после присвоения локальной переменной непустого значения аргумент процедуры вдруг - раз, и стал равным этому присвоению!  Да, вот константный аргумент вдруг взял и поменялся сам. Причем, если аргумент приходил пустой - то в него ничего не пишется после занесения нового значения в локальную переменную, всё такой же он пустой остаётся как и был.

Полазил я в CPU на предмет совпадения адресов - но это так, чисто посмотреть с укором в подкапотное пространство. А дай, думаю, присвою той локальной переменной значение аргумента. Присваивается, идёт дальше, и вот в момент "волшебного" присвоения - Бах! - неверная операция с указателем! И только тогда, когда аргумент не пуст.

Стал я думать-гадать-искать и понял, что переданная мной непустая строка принадлежит тому элементу списка, который убивается внутри процедуры перед присвоением нового значения локальной строке. А константные аргументы как передаются? - А в том числе и по ссылке! И видимо, поскольку я строковое свойство кладу в неизменном виде, как раз такой вариант развития событий и был выбран компилятором. И выходит, что в магический момент ссылка уже битая, и совпадает как-то хитрослучайно точно с адресом локальной переменной. 

Это просто счастливое совпадение, что мёртвая ссылка точно легла на локальную переменную! 

Лечение прошло банально - передачей параметра по значению. Но каково?! Как компилятор не отследил, что область памяти занята процедурой?! 

К сожалению, я не смог воспроизвести ситуацию на простом примере. И если кто-нибудь понял что за оптимизация такая у компилятора происходит, то пусть попробует создать пример в пробирке для quality.embarcadero.com. А я свой рассказ заканчиваю. Спасибо за внимание!

Как побег лямбды из цикла удивляет компилятор

Это такая шутка - заставить компилятор выдать [dcc32 Fatal Error] InternalError.dpr(15): F2084 Internal Error: C22972.

Попробуйте написать в цикле лямбда-выражение, в котором есть оператор управления циклом - break или continue. Причём не важно, какой цикл - for, while, repeat. Компилятор от этого не умрёт, но и сказать ничего умного не сможет.

program InternalError;
{$APPTYPE CONSOLE}
begin
  while True
  (procedure begin
    break
  end)();
end.
 
Так ему! Пусть не думает, что умнее паровоза! Пусть выучит и повторяет: BREAK or CONTINUE outside of loop!

..........................

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

Ну, ладно. Иди сюда мой хороший, давай я тебя обниму. Ну, что ты, что ты? Расстроился? Ну, ничего, не плачь, всё будет хорошо... А вот мы сейчас с тобой ещё одну программку покомпилим, хорошую программку... А та програмяка - бяка! Фу, брось её! Пусть там на quality.embarcadero.com с ней разбираются, а нам она не нужна, у нас и так программок много. Пойдём, моя радость!

суббота, 17 января 2015 г.

Как я стал недоверять try-except

Я уже имел возможность убедиться, что try-except - жутко тормозная штука. Но я всё ещё думал, что это штука надёжная. По крайней мере, она никогда не подводила меня в мире Win32. И вот я пошёл с этими представлениями в мир мобильных приложений. И пришёл к печальному выводу - try-except работает ещё и отнюдь не всегда. Увы, "кто умножает познания, умножает скорбь"/широко известный источник/

Вот пример, где я пытался замерить скорость работы try-except при попытке доступа к неассигнованным объектным переменным:

procedure TForm5.Button1Click(Sender: TObject);
var
  dt: TDateTime;
  I: Integer;
  A: array[ 0..9 ] of TStringList;
 
  function V: Integer;
  begin
    try
      Result := A[ I mod 10 ].Count;
    except
      Result := 0;
    end;
  end;
  function V2: Integer;
  var
    Index: Integer;
  begin
    Index := I mod 10;
    if Assigned( A[ Index ] ) then
      Result := A[ Index ].Count
    else
      Result := 0;
  end;
var
  Summ: Integer;
begin
  FillChar( A, SizeOf( A ), 0 );
  for I := Low(A) to High(A) do
  if Odd( I ) then
    A[I] := TStringList.Create;
 
  dt := Now;
  try
 
    Summ := 0;
    for I := 0 to 1000000 do
    Summ := Summ + V2;
 
    ShowMessage( IntToStr( Summ ) + ': ' + FormatDateTime( 'hh:nn:ss:zzz', Now-dt ) );
 
  finally
    for I := Low(A) to High(A) do
    if Odd( I ) then
      FreeAndNil( A[I] );
  end;
 
end;

Сначала я пробую цикл с if Assigned(... - результат мгновенный: 0: 00:00:00:006. Теперь меняю Summ + V2 на Summ + V, и...


!!! Что же это за кака-бяка такая!!! И как дальше жить? Теперь, выходит, кроме создаваемых в коде исключений, отлавливать ничего не получится? Тоже не верно - перехватчик самого приложения всё-таки отработал, выдал сообщение, корректно завершил метод и даёт таким образом нам возможность нажимать на кнопку сколько угодно раз и сколько угодно раз любоваться этим "Access violation at address ..., accessing address 00000000." Но где это работает, а где нет? Где играть, где не играть, где рыбу заворачивать? Ы-ы-ы-ы-ы...


ЗЫ: Спасибо Владимиру Натановичу Винокуру за певца Григория Долголоба, служащего в Чугуевской филармонии.

Как лучше брать поля объектных полей? Или Assigned() VS try-except.

Как лучше брать поля объектных полей? Что предпочесть - ясный и простой код с исключением в стиле Delphi, или проверки с повторным чтением объекта или использованием  промежуточных значений в стиле Pascal? Вот пример:

procedure TForm5.Button1Click(Sender: TObject);
var
  dt: TDateTime;
  I: Integer;
  A: array[ 0..9 ] of TStringList;
 
  function V: Integer;
  begin
    try
      Result := A[ I mod 10 ].Count;
    except
      Result := 0;
    end;
  end;
  function V2: Integer;
  var
    Index: Integer;
  begin
    Index := I mod 10;
    if Assigned( A[ Index ] ) then
      Result := A[ Index ].Count
    else
      Result := 0;
  end;
var
  Summ: Integer;
begin
  FillChar( A, SizeOf( A ), 0 );
  try
for I := Low(A) to High(A) do if Odd( I ) then A[I] := TStringList.Create;   dt := Now;   Summ := 0; for I := 0 to 100000 do Summ := Summ + V;   ShowMessage( IntToStr( Summ ) + ': ' + FormatDateTime( 'hh:nn:ss:zzz', Now-dt ) );   finally for I := Low(A) to High(A) do if Odd( I ) then FreeAndNil( A[I] ); end;   end;

Это событие нажатия на кнопку,  в результате работы которого у меня на экране компьютера появляется сообщение с такими цифрами: 0: 00:04:41:248(*). Т.е. около пяти минут на 100 тыс. записей. Много ли это? Скажу только, что изначально я ставил 1 миллион, и просто замучился ждать.

И вот, вместо Summ + V я ставлю Summ + V2, запускаю, жму кнопку... Оп! И 0: 00:00:00:002!


!!! Так вот как должен работать процессор с восемью ядрами на частоте 4 ГГц! И миллион операций ведёт к увеличению времени до 0.021 секунды. И 10 миллионов - лишь малая доля секунды...

Красивый, простой как 2 копейки код с try-exсept против жуть какого старого, извилистого, но всё ещё очень и очень доброго if... Если кто-то опять мне будет говорить, что новее - значит лучше, то скорее всего, я снова ему не поверю.

Кстати, надо погонять это дело на телефоне...

Примечание *: Такое ужасно время получено из-за запуска по F9 - с отладкой. Для такого рода замеров следует запускать Shift-Ctrl-F9, т.е. без отладки. Без отладки время ф-ии V будет уже 0,677 секунды, что ни в какое сравнение не идёт с 5-ю минутами. Однако, это всё же более, чем в 300 раз дольше варианта с V2. И к тому же совершенно очевидно, что отладка программы, где происходит исполнение такого рода кода, может стать весьма утомительным занятием. Спасибо Alexey Kazantsev за замечание по поводу отладчика.