Bei Kunden, die ich besuche, kommt es immer wieder mal zu Problemen oder Missverständnissen bei der Installation oder Lizenzeingabe von Delphi, deshalb hier ein paar Hinweise:
Vergangene Woche haben wir mit Embarcadero Deutschland erfolgreich die nunmehr dritte Coderage über die Bühne gebracht.
Hier findet ihr sämtliche Webinare zum nächträglichen Anschauen. Vielen Dank an Matthias Eissing – für die nicht unerhebliche Arbeit und auch die Geduld, alle Videos der Sprecher einzusammeln und aufzubereiten.
Hier mein Beitrag, der zeigt, wie man in FireMonkey iOS Apps den Apple Schlüsselbund aktiviert, um bei Login-Feldern dem Benutzer die Arbeit zu erleichtern. Den in der Session erwähnten Quelltext findet ihr hier.
FireMonkey Apps für iOS und Android werden in Delphi im Rahmen des Bereitstellungsprozesses automatisch signiert – dies ist inbesondere für iOS Apps unabdingbar und wurde daher in der Delphi IDE auch entsprechend umgesetzt.
Bei Windows Anwendungen (egal ob 32 oder 64 Bit) ist dagegen keine Signatur-Option in Delphi zu finden. Dies dürfte dazu führen, dass viele Delphi Apps unsigniert verteilt werden. Dies ist jedoch spätestens seit Windows 10 nicht mehr zu empfehlen, denn der mit Windows 10 eingeführte „SmartScreen-Filter“ blockiert standardmäßig „nicht vertrauenswürdige“ Anwendungen. Eine unsignierte Anwendung (EXE) ist per se „nicht vertrauenswürdig“, da ihre Herkunft nicht verifiziert werden kann.
If your Xcode version updates (manually or by Apple’s updating mechanism) then make sure to re-import the iOS/macOS SDK from within Delphi.
To build and deploy iOS/macOS apps with Delphi, you need Xcode for the final steps, even though the actual binary is compiled by Delphi. Apple frequently delivers minor updates to Xcode. Current version as of the writing of this article is 8.3.2. This version number of the Xcode build used for preparing apps is apparently checked by Apple, when uploading an IPA file to App Store/iTunes Connect. They don’t do any spooky things, they just check the DTXcodeBuild key in your app’s info.plist file:
This info.plist file is generated by Delphi, and contains various essential settings, such as version number, device requirements etc. Many of these settings can directly be configured, by modifying the values in Delphi – Project – Options – Version Information
DTXCodeBuild is filled in by Delphi automatically though. When you import iOS/macOS SDK, then PAServer obviously issues this command:
/usr/bin/xcodebuild -version -sdk
That returns the available SDK versions and the Xcode build number:
iPhoneOS10.3.sdk - iOS 10.3 (iphoneos10.3) SDKVersion: 10.3 Xcode 8.3.2 Build version 8E2002
This build version number is apparently stored and used to fill the DTXcodeBuild key’s value.
To bring this number in Delphi in sync with the actual Xcode version, after Xcode was updated (or switched with xcode-select), you have to delete the SDK from Delphi and re-import it using Delphi – Tools – Options – SDK Manager.
This is especially important if you imported the 10.3 SDK while having Xcode 8.3.0 installed. In that case your Delphi iOS apps would be tagged with being built with exactly that version – no matter if you downgraded to Xcode 8.2.1 or applied my „Package Application fix“ – which is required due to a tool chain change that Apple imposed with Xcode 8.3 and up. Any iOS app tagged with being built with Xcode 8.3.0 will be refused by Apple, as that version has been deprecated.
Since Apple updated Xcode to version 8.3.x, iOS IPA deployment is broken with Delphi up to 10.2 (Tokyo). There is an official workaround, which basically instructs to download Xcode 8.2 and use that for now:
Unfortunately though, Apple started sending out notifications, that it won’t accept any builds created with Xcode versions older than 8.3.2 anymore. This is what I’ve received from Apple a few days ago:
We have discovered one or more issues with your recent delivery for „AppEvents“. Your delivery was successful, but you may wish to correct the following issues in your next delivery:
Deprecated Xcode Build – Due to resolved app archives issues, we will be deprecating Xcode 8.3 on May 10, 2017, at which time app archives built with 8.3 will no longer be accepted by the App Store. Download Xcode 8.3.2 or newer, rebuild your app and resubmit.
After you’ve corrected the issues, you can use Xcode or Application Loader to upload a new binary to iTunes Connect.
The App Store team“
The problem with Delphi and Xcode 8.3 is basically a tool, that Apple decided to remove from its tool chain, but which Delphi still depends on. So the solution is to copy that missing tool „PackageApp“ from 8.2 to 8.3. Below are the steps that made my Delphi 10.2 (Tokyo) iOS deployment fully working again:
- Install current Xcode from Appstore into your applications folder – currently 8.3.2
- Download Xcode 8.2.1 from
- Unzip Xcode_8.2.1.xip to a separate folder (e.g. /tmp/Xcode)
- Make a backup copy of your current Xcode version:
cp -R /Applications/Xcode.app /tmp/Xcode.8.3.app (might take a minute or two)
- Copy the following file from 8.2.1 to your current Xcode app:
sudo cp /tmp/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/PackageApplication /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/
Apple requires you to specify if your apps are using any form of encryption. If you do not use encryption, you have to confirm that in your app settings on iTunes Connect – this is not new, Apple started asking for that long time ago. Important to notice is though that since a while Apple requires your app to have a special entry in it’s info.plist file – even if do not use any encryption. Without that your app will fail to verify for Testflight and Appstore deliveries.
It’s easy to add that entry in Delphi. Go to Project – Options – Version information. The list/grid is actually editable, just go to the last row and press cursor down. Then add „ITSAppUsesNonExemptEncryption – false“ as shown in the screenshot
Embarcadero just made a hotfix for Delphi/RADStudio 10.2 Tokyo available, which fixes several debugger issues and – important for you C++ guys – it fixes a string related security issue, which could potentially overwrite memory with affected scanf and scanf_s methods.
If you need to „pump“ records from one data source to an other, then TFDBatchmove is for you.
Using FireDAC’s BatchMove component allows you to easily map the fields of one query to an other, regardless if they belong to the same database/connection or two totally different databases. You can actually even copy records from text files to „real“ databases and vice versa.
Just build a BatchMove chain like this:
SourceData and DataTarget are regular TFDQueries, Reader and Writer are TFDBatchmoveDatasetReader/Writer components, that just connect the queries with the TFDBatchmove component in the middle.
The actual magic is done by adding field mappings, from source to target:
In this case source and target fieldnames are mostly the same, which wouldn’t even require adding mappings. There are a couple of calculated fields in the source query too. They are used to format/modify the data, so that it would match the format requirements in the destiantion query.
These fields are calculated in QOPCQuery.OnCalcFields like this:
procedure TDMMain.QOPCSourceDataCalcFields(DataSet: TDataSet); begin if QOPCSourceDataoSchnittgruppe.IsNull then DataSet.FieldByName('FANr').AsString := '' else DataSet.FieldByName('FANr').AsString := GenFANr(QOPCSourceDataoSchnittgruppe.AsInteger);
The calculated fields work fine, but when calling BatchMove.execute it will raise an exception. The reason is simple:
FireDAC’s internal logic depends on TDataSet.FieldDefs, but calclulated fields won’t show up there. You have to use „InternalCalc“ instead of „Calculated“, to push your calculated fields to its datataset’s FieldDefs collection.
UPDATE: The issue described here has been marked „resolved“ on May, 5th 2017, so we should see corrected behaviour in one of the next deliveries.
I am using TTask.Run quite a lot in projects and in many of my demos. I won’t go into the details of how to use TTask here, but I only want to highlight that there is still an open issue, which may cause interfaced objects to be destroyed much later, than you would expect. Ultimately this may lead to a much higher memory footprint of your application.
Interfaced objects use automatic reference counting for lifecycle management:
var LFoo : IFoo begin LFoo := TFoo.Create; //work with LFoo end; //LFoo goes out of scope, its reference counter will go to zero, // thus it will be destroyed here automatically.
With TTask.Run you can start background threads, that will execute code passed in as anonymous method:
var LFoo : IFoo begin LFoo := TFoo.Create; TTask.Run(procedure begin Foobar(LFoo); //Work with LFoo end; ).Waitfor; //Wait for task/thread to be completed end; //LFoo goes out of scope, its reference counter SHOULD go to zero, // thus it SHOULD be destroyed here automatically.
Unfortunately the above does not work as expected. The internals of TTask (actually TThreadpool) keep a reference to the (already finished) task and prevent the instance of TFoo, which is referenced by LFoo, to be destroyed automatically. At least when the TThreadPool gets destroyed, all those kept references will be released and the instances will be destroyed, thus they won’t report as memory leak (with ReportMemoryLeaksOnShutDown).
In other words: you will have to set LFoo to nil manually – until this issue gets solved in one of the next updates.
TTask.Run(procedure begin Foobar(LFoo); //Work with LFoo end; ).Waitfor; //Wait for task/thread to be completed LFoo := nil; //Depending on your logic, this could also be done inside TTask.Run end;
When you start implementing cross-platform applications in Delphi’s FireMonkey framework, then you might consider reusing TDataMdoule instances from your existing VCL applications. If your code is clean enough, i.e. your DataModules are separated from any UI code, that is contain no references to WinApi, Forms, Controls or other UI relevant units, then this is actually a reasonable approach.
You might run into a weird problem though: The IDE will sometimes insist on adding VCL-dependant units to your DataModul, even if you actually compile for FireMonkey. One example is FireDAC.VCL.Wait which will get added automatically under certain conditions (and which won’t compile under FMX).
There is a property that controls this behaviour in shared TDataModule instances:
ClassGroup is basically a „Pseudo Property“, which adds some magic to the project so that the IDE will behave differently depending on the properties value. To make your DataModule really platform independent set it to: System.Classes.TPersistent
That way, the IDE stops inserting references to either FMX or VCL units in the DataModule’s uses clause. In the case of FireDAC applications, it will just add FireDAC.ConsoleUI.Wait, which will compile just fine on all platforms. You would then add the actual Wait unit in the app’s main form (or in the DPR).