The typical current requirements of the customers I work with, so they tell me, are to stick with Windows + VCL thanks to the long-term projects they have ongoing. Because of this I’ve not really spent too much time getting to know FireMonkey in excruciating detail since it was introduced. However, given that the quintessence of the XE4 release has been iOS support and this typically† revolves around using FireMonkey, I figured maybe it was about time to roll the sleeves up and get stuck in.
This post is a summary run-though of the things I bumped into, how I got on, what I figured out, what problems I encountered and how I achieved a few things whilst checking out the latest platform-targeting in the Delphi product line.
Environment setup
First things typically come first, so top of the agenda was setting up the development environment to work on iOS projects. This was straightforward enough. The online documentation (to be honest I don’t install the help – I just use Embo’s docwiki site) covers the setup quite thoroughly. There’s a general run-through and then also in the iOS tutorials page there are links to detailed setup for the Mac and setup for Windows.
To develop apps for Mac (OS X) or for iDevices (iOS) requires the use of a Mac – Apple make this a requirement as they supply some necessary tools for parts of the job (such as signing the app or simulating an iDevice) and those come with Apple’s Xcode and only run on a Mac. There’s no real need to dash out and buy a hugely over-priced MacBook Pro unless you have spare cash at your disposal; a Mac Mini is more than adequate and much more sensibly priced.
Delphi or RAD Studio will run on a Windows machine, and a helper app called PAServer (the Platform Assistant) runs on the Mac to:
- liaise with any Mac tools that are required
- automate the iPhone/iPad Simulator when running iOS apps against the simulator
- act as a remote debugger when debugging a Mac or iOS application (either on a device or in the simulator)
My first attempt at iOS development had my development environment (Delphi XE4) on a Windows laptop, talking across my network to the Mac Mini. This actually worked just fine, though the deployment step took about 10-20 seconds each time, sending the app files and the debug symbols across the network. Hardly a long time, but it got annoying when I kept needing to make small changes and see their effect.
I ended up settling on a Windows virtual machine running on the Mac Mini via VMWare Fusion and installed Delphi XE4 in that. This made the deployment step joyfully rapid. I also then had the luxury of working directly with the Mac Mini, or working remotely on my laptop using VNC. I can have one session connected to the Windows virtual machine (VMWare Fusion acts can have each virtual machine optionally act as a VNC server) and another session talking to the Mac itself to see apps running in the simulator.
I should point out I don’t have any iDevices – all my apps are just tested in the simulator and that’s that for the time being.
To build iOS apps for the device and the simulator the IDE makes use of two of the five Delphi compilers shipped in RAD Studio XE4 (or Delphi XE4 with the mobile add-on), which are the latter two in this compiler list:
- dcc32 – Embarcadero Delphi for Win32 compiler version 25.0 – your regular Win32 Delphi, the latest version updated over many versions since Delphi 2
- dcc64 – Embarcadero Delphi for Win64 compiler version 25.0 – the 64-bit Windows targeting compiler
- dccosx – Embarcadero Delphi for Mac OS X compiler version 25.0 – the Delphi compiler that generates 32-bit OS X apps
- dccios32 – Embarcadero Delphi Next Generation for iPhone Simulator compiler version 25.0 – the NextGen Delphi compiler that builds Intel executables that run in the iOS simulator
- dcciosarm - Embarcadero Delphi Next Generation for iPhone compiler version 25.0 – the NextGen Delphi compiler that builds ARM executables that run on iOS devices
Simple FireMonkey apps
To check that FireMonkey behaves basically the same as VCL I created a FireMonkey mobile application and threw some code on the form. It did as expected, and I noted the pervasive StyleLookup
property that lets you customise all the controls. For example with a button there are pre-defined styles to make the button look like the various buttons that commonly crop up in iOS applications.
This style system helps you put together a UI that will fit in with other apps in the world of iOS. This is an important issue. When you build an iOS app you’ll be creating a UI specifically for the mobile devices – laid out to look appropriate on an iPhone and/or iPad. Apple are very specific about how UIs on their OSs should look and have comprehensive User Experience guidelines both for OS X and for iOS. You should digest these guidelines and take note of how existing applications look to ensure your app doesn’t stick out like a sore thumb. The UI should be carefully designed for the target platform.
<soapbox>
Consequently, the repeated platitude we hear from the bods at Embo about building an app once and recompiling it for different platforms should be taken for what it is: marketing fluff. Sure, back-end code could follow that ideal, but anything tightly related to the UI will doubtless need to be re-implemented for each target platform. You owe it to your users to respect them enough to do the right thing with your app’s UI.</soapbox>
All that notwithstanding, it was a breeze to build a simple iPhone app and have it run in the simulator. Seemed very much akin to building a VCL application at the basic level I was working at, which was quite encouraging.
Compiler changes
If you’re looking to share a bunch of existing code with an iOS Delphi application you should remember that the iOS compiler is a next generation Delphi compiler, with a few language changes in. I gave a list of the changes (including proposed changes currently represented by warnings) in the compiler in a previous blog post here.
The removal of some of the string types and some of the other obvious changes in it might make it more difficult than you expect to pull in a lot of old code, but obviously your mileage will vary on this one. If you want to write new code that will be shared across platforms, the fact that the next-gen compiler employs ARC for all Delphi objects (not just strings, dynamic arrays and interfaces) means that the shared code will need to call Free
, which is a no-op on iOS. iOS-only code can forget all about calling Free
, which is nice.
Certainly what I hear from clients I work with is that their primary goal for the iOS compiler is to build some small standalone applications that will, via some connectivity option or other, perhaps connection to a web service or DataSnap server, present some representation of a portion of an existing desktop application to users, or provide reports and stats from an existing system to management types. That seems like a very achievable and sensible plan to me.
New dependency keyword
A new keyword that slipped in under the radar in the NextGen ARM compiler is dependency
. This is really a convenience that precludes the need to tweak the options to get certain apps to work. Specifically this keyword lets you tell the linker of additional libraries to link in when declaring an external library function. So, for example, the MIDAS static link library midas.a has a dependency on the Standard C++ library libstdc++.dylib. A dylib on a Mac can be dynamically loaded at runtime or statically linked. Apple decrees that iOS apps cannot use any custom dynamic libraries, so any library code must be statically linked into the app.
This means that any app that uses the MIDAS library must statically link the Standard C++ library in as well. Without the dependency
keyword this would involve modifying the ARM linker options. This could be done by going into the project options (Ctrl+Shift+F11
) and doing either of these:
-
selecting Delphi Compiler, Compiling in the options tree, then selecting Other options, Additional options to pass to the compiler and entering a suitable portion of a dcciosarm.exe comand-line like:
--linker-option:"-L $(BDSLIB)\$(Platform)\$(Config) -lstdc++"
-
selecting Delphi Compiler, Linking in the options tree, then selecting Options passed to the LD linker and entering:
-L $(BDSLIB)\$(Platform)\$(Config) -lstdc++
However, dependency
means you can forget all that and write your import declaration like this one, from DataSnap.DSIntf.pas:
{$IF DEFINED(IOS) and DEFINED(CPUARM)}
function DllGetDataSnapClassObject(const [REF] CLSID,
[REF] IID: TGUID; var Obj): HResult; cdecl;
external 'libmidas.a' name 'DllGetDataSnapClassObject'
dependency 'stdc++';
{$ENDIF IOS and CPUARM}
This approach is used more widely for the InterBase DBExpress client code in Data.DbxInterbase.pas. Here we find a variety of imported functions coming from libsqlib.a, which have a dependency on both Standard C++ and also libibtogo.dylib. So there are a whole bunch of declarations like:
function DBXBase_Close(
Handle: TDBXCommonHandle): TDBXErrorCode; cdecl;
external 'libsqlib.a' name 'DBXBase_Close'
dependency 'ibtogo', 'stdc++';
FireMonkey versus CocoaTouch
If you spend some time browsing through the FireMonkey and iOS-specific RTL source you’ll become familiar (at one level or another) with the relationship between FireMonkey and the iOS Objective-C API. Very similar to how the VCL presents nice components, but those components are built on the Windows UI infrastructure of windows and window handles and how VCL applications operate by participating in the regular Windows message processing behaviour, FireMonkey iOS applications also immerse themselves in the regular iOS infrastructure.
What this means is that a FireMonkey form is based on an Objective-C UIView
or, more specifically, a GLKView
in the case of a GPU-backed form. The application start-up is done through a call to UIApplicationMain
and there is an application delegate (UIApplicationDelegate
protocol implementation) used to deal with when the app starts, goes to the background, comes back to the foreground and terminates.
Here and there you might find it useful to call upon some iOS APIs, which requires a heads-up on some basics. The following sections endeavour to give you such a heads-up.
Working with NSStrings
Objective-C uses NSString
objects where Delphi uses strings. If you need to pass a string to an iOS API that expects an NSString
you can use the NSStr
function in iOSapi.Foundation.pas to translate it over.
You can also do the reverse by borrowing (ok, copying) the NSStrToStr
function that is (inexplicably) tucked away in the implementation section of FMX.MediaLibrary.iOS.pas.
[Update: Note that this function turns the NSString to UTF8 before turning it back to a Delphi string. This is a bit of a round trip, and any characters outside UTF8-space get rather mangled. You might benefit from looking at Chris Rolliston’s approach to the same problem.]
Additionally, any time you are presented with a UTF8 string from an Objective-C method (which would be represented as type _PAnsiChr
), such as the UTF8String
method of NSString
or the systemVersion
method of a UIDevice
, you can turn it into a Delphi string with UTF8ToString
.
Delphi’s representation of Objective-C objects
Much as Delphi's classes all stem from TObject
, Objective-C classes all have a common ancestor of NSObject
. The large number of classes in the full tree are split into branches of classes called frameworks. For example UIKit is the framework containing all the basic UI-related stuff and MapKit wraps up all the mapping stuff.
Delphi represents these Objective-C classes by interfaces. For example, NSObject
is declared in iOSapi.CocoaTypes.pas and contains declarations of the instance methods of an Objective-C object of type NSObject
. Where Delphi has an interface for an Objective-C class's instance methods there is also another interface for all the class methods. In the case of NSObject
it's called NSObjectClass
.
In order to make use of these interfaces a helper class is defined alongside the instance interface and class interface for the Objective-C type. The helper class is a generic class that inherits from TOCGenericImport<C,T>
and wraps up the process of importing the Objective-C class into Delphi. For NSObject
the helper class is TNSObject
and gives you a means to create an Objective-C object of type NSObject
and also wrap up an object id for such an Objective-C object into an NSObject
interface reference. That sets out the pattern of types used to represent imported Objective-C objects.
In Objective-C constructing an object is a two stage process. The memory is allocated by a call to the class alloc
method and then the returned object is initialised through a call to a class initialiser, such as the default init
or maybe a custom one. Various classes have custom initialisers, for example controls have one called initWithFrame
. We may be more familiar with the one-stop construction achieved by calling a Delphi class constructor, such as Create
, but of course behind the scenes the same two steps have to occur - the memory for the instance is allocated and then the body of Create
initialises that instance.
Let's take an example of creating an alert view - basically an iOS message box. Clearly in a FireMonkey project you would just use ShowMessage
or MessageDlg
, which will work out how to do all this on iOS platform, but for the sake of example let's follow it through. The alert view is represented in iOS by UIAlertView
, so iOSapi.UIKit.pas defines interfaces UIAlertView
and UIAlertViewClass
as well as class TUIAlertView
. If you want to construct an instance of UIAlertView
using alloc
and the default init
initialiser then a call to TUIAlertView.Create
will do the job and return an interface reference to a UIAlertView
. You can then tailor the alert view by calling its setTitle
, setMessage
and addButtonWithTitle
methods and display it by calling show
. Something like this:
var
alertView: UIAlertView;
...
alertView := TUIAlertView.Create;
alertView.setTitle(NSStr('Delphi for CocoaTouch'));
alertView.setMessage(NSStr('Hello world'));
alertView.setCancelButtonIndex(
alertView.addButtonWithTitle(NSStr('OK')));
alertView.show;
alertView.release;
However if you want to use a custom initialiser you can allocate an uninitialised instance by calling the helper class's Alloc
method and then call the custom intialiser on that. However the custom intialiser will return an actual Objective-C object (as opposed to an interface that represents it), which is represented as a raw pointer. This pointer can be considered the Objective-C object id. To turn this object id back into a usable Delphi interface reference you feed it into the helper class's Wrap
method (just be careful with Wrap
as it does not check for nil
being passed in and so will happily crash if given the chance - see QC 115791). So we could rewrite the above like this:
var
alertView: UIAlertView;
...
alertView := TUIAlertView.Alloc;
alertView := TUIAlertView.Wrap(alertView.initWithTitle(
NSStr('Delphi for CocoaTouch'), //title
NSStr('Hello world'), //message
nil, //delegate
NSStr('OK'), //cancel button caption
nil)); //other button captions
alertView.show;
alertView.release;
So that shows how to create a new instance of an existing Objective-C class and call instance methods. To call class methods you use the OCClass
property. The UIDevice
iOS class has a currentDevice
read-only property that is documented to return a UIDevice
instance that represents the current device. Similarly UIScreen
has a mainScreen
read-only property that returns a UIScreen
instance that represents the main screen (i.e. not an external screen).
var
currentDevice: UIDevice;
mainScreen: UIScreen;
iOSversionStr, screenDimensionsStr: string;
...
currentDevice := TUIDevice.Wrap(
TUIDevice.OCClass.currentDevice);
iOSversionStr := UTF8ToString(
currentDevice.systemVersion.UTF8String);
mainScreen := TUIScreen.Wrap(TUIScreen.OCClass.mainScreen);
screenDimensionsStr := Format('%d x %d',
[Round(mainScreen.bounds.size.width),
Round(mainScreen.bounds.size.height)]);
From a given interface reference to an Objective-C object, obj
, you can access its object id (the pointer to the actual Objective-C object) using this:
(obj as ILocalObject).GetObjectID
Objective-C properties
Objective-C objects offer various properties just like Delphi objects do. These are implemented with getter and setter functions as you'd expect. However if building a Delphi iOS app you should know that all the Delphi interfaces representing Objective-C classes don't have these properties brought through. Instead you'll need to call the setter procedure (e.g. setFrame()
for a UIView
object's frame
property or setStatusBarHidden()
for the UIApplication
class statusBarHidden
property) or getter function (e.g. frame()
for the frame
property or isStatusBarHidden()
for the statusBarHidden
property.
Yeah, as you may notice there the property getter either has the same name as the property or uses an is
prefix... You can read the formal documentation on property accessor naming on Apple's site here.
Objective-C method names
As explained in various places on the Arpanet (or Internet as we now call it), such as this method naming post from Ry's Objective-C tutorial, Objective-C has an interesting approach to method naming. The parameter names become part of the method name to aid self-description and to minimise opportunities for ambiguity. Let's have a look at some example methods defined in the UIApplicationDelegate
protocol. Firstly the method that triggers after the application has started up:
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
This is a function method that returns a Boolean
and takes two arguments, a reference to a UIApplication
and a reference to an NSDictionary
. To the person implementing the method the arguments are called application
and launchOptions
but the arguments are described in the full method name, which is formed from all items that have colon suffixes. The Objective-C method name is application:didFinishLaunchingWithOptions:
and this differentiates it from other methods in the same protocol, such as application:shouldSaveApplicationState:
and application:willChangeStatusBarFrame:
.
Problems start cropping up over on the Delphi side when you try to translate these methods to a Delphi representation. Here are a selection of methods from this protocol that we might wish to translate:
(BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
(BOOL)application:(UIApplication *)application
shouldSaveApplicationState:(NSCoder *)coder
(void)application:(UIApplication *)application
willChangeStatusBarFrame:(CGRect)newStatusBarFrame
Now here is the best we can do in Delphi to translate them over accurately:
function application(application: UIApplication;
didFinishLaunchingWithOptions:NSDictionary): Boolean; overload;
function application(application: UIApplication;
shouldSaveApplicationState: NSCoder): Boolean; overload;
procedure application(application: UIApplication;
willChangeStatusBarFrame: CGRect); overload;
You'll notice each method has the same name and so requires overloading. But we're okay so far as each overload has a unique signature. Let's add in some more methods:
(BOOL)application:(UIApplication *)application
shouldRestoreApplicationState:(NSCoder *)coder
(void)application:(UIApplication *)application
didChangeStatusBarFrame:(CGRect)oldStatusBarFrame
You can probably see the problem rearing its head now. When we translate these into additional methods in a Delphi UIApplicationDelegate
interface the compiler won't accept the overloads as we have the same signatures as already defined:
function application(application: UIApplication;
shouldRestoreApplicationState: NSCoder): Boolean; overload;
procedure application(application: UIApplication;
didChangeStatusBarFrame: CGRect); overload;
So this leads to the realisation that the general case is that we cannot adequately represent any given Objective-C class or protocol by using a Delphi interface. You can do some of it, but as soon as matching signatures come along this approach falls down. If you hunt out the UIApplicationDelegate
interface in iOSapi.UIKit.pas you'll see a number of the methods commented out for this exact reason.
This puts a bit of a downer on the whole affair until you find out that given enough desire you can work around this limitation of Delphi's method representations and set things up manually. In fact that's exactly what FireMonkey does in order to set up an application delegate on iOS application start-up. It uses low-level Objective-C Runtime routines to set up Delphi routines to implement given Objective-C methods. If you look in FMX.Platform.iOS.pas and locate TPlatformCocoaTouch.Create
you can see this being done.
It's a bit of a faff, but does allow Delphi to work with any Objective-C objects. I've used the approach myself in a couple of test applications and can confirm that it's workable, but really it's not entirely productive to do so. I plan to write more on the subject at a later point, but for now I'll leave the FireMonkey source as an adequate reference.
[Update: apparently I’d rather overlooked the fact that this issue had already been acknowledged by the Embo devs and a neat workaround exists – ah well, you live and learn. Indeed this neat workaround is used to work around the problem in the RTL, for example in the CLLocationManagerDelegate interface. This is achieved using the handy [MethodName()] attribute through which you can map an arbitrarily named Delphi method to a specifically named Objective-C method. Thanks to Embo’s Darren Kosinski for pointing me to this]
ARC and Delphi and Objective-C
When Delphi first arrived it supported the string
type. A string
is really a pointer to a data structure that is automatically memory-managed for you. This memory management includes reference counting to cater for when strings are passed into subroutine calls and assigned to variables. When nothing references a string any longer the code generated by the compiler ensures all the memory occupied by the string data structure is freed.
When interfaces were added to Delphi 3 we again enjoyed reference counted memory management. The compiler worked out when to call the _AddRef
and _Release
methods on interface references based on assignments, parameter passing and scope and if _Release
caused the internal reference count to reach 0 the object destroyed itself.
With the NextGen Delphi compiler the programmer is afforded the luxury of automatic reference counting (ARC) for all objects, as detailed in this Dr Dobbs article by Embo's JT and Marco Cantù. The compiler will identify where to make calls to TObject
's __ObjAddRef
and __ObjRelease
for you. Again, when the internal reference count gets to 0 the object destroys itself. Hence there is no longer a need to worry about calling Free
and wrapping construction and destruction code up in a pair inside a try
/finally
statement. That said, it doesn't matter if you do call Free
as in the NextGen compiler TObject.Free
simply sets the object reference to nil
, hastening the decrement of the object reference.
When you program iOS apps in Objective-C you are also blessed with ARC. Just as with Delphi it's a compiler thing - the compiler works out where it's appropriate to make calls to retain
and release
to ensure the internal reference counts will drop to 0 when a given object is no longer used by anything.
So the Delphi (NextGen) compiler does ARC against all Delphi objects and the Objective-C compiler does ARC against all Objective-C objects, but what about Objective-C objects in a Delphi program?
Unfortunately there is no ARC for the iOS objects represented by the import wrapper class and the interfaces discussed above. When dealing with iOS objects manually you will need to call retain
and release
yourself at the correct points. Allocating an iOS object starts the reference count off at 1; calling release will drop it to 0 and have the object destroy itself.
Apple’s Instruments app
A really useful tool supplied as part of Xcode (or possibly just shipped a part of OS X, I’m not positive) is Instruments. If you read up on Instruments you’ll see that this can help identify memory leaks in your OS X or iOS apps. I've been using it to check on iOS object leaks in FireMonkey applications and also in apps written in Delphi that use no FireMonkey business at all. It's been quite illuminating as I've happened upon a couple of simple shortcomings in the FireMonkey code.
The principle I've used is to have all the code that does stuff I want to leak-check in secondary forms launched from a main form. In the case of FireMonkey I'm just using regular forms and launching them with ShowModal
. I launch the app in the iOS simulator and then launch Instruments and choose the Leaks mode. Next I use the Choose Target dropdown and from the Attach to Process submenu I select my app process from the System list. Pressing the Record button then starts tracking object and memory allocations. For any given iOS object type you can see how many instances still exist, look at a list of them all and for any instance see the allocation and all the retain
/release
calls.
Going through this process with a simple FireMonkey app that launched a secondary form showed that a UIView
and various other helper objects were being allocated each time the form was displayed but not freed when it was closed. Tracking the problem eventually yielded some fixes to the FireMonkey source that resolve the problem, as documented in QC 115914. In short you need to:
- copy FMX.Platform.iOS.pas from $(BDS)\source\fmx
- change
TPlatformCocoaTouch.DestroyWindow
to be:procedure TPlatformCocoaTouch.DestroyWindow(
const AForm: TCommonCustomForm);
begin
if Assigned(AForm.Handle) then
begin
WindowHandleToPlatform(AForm.Handle).View.
removeFromSuperview;
WindowHandleToPlatform(AForm.Handle).View.release;
end;
end; - in
TPlatformCocoaTouch.ShowWindowModal
change thefinally
part of thetry
/finally
statement to:finally
BackView.removeFromSuperview;
BackView.release;
end;
Further experimentation showed that the iOS implementation of the message box routines (ShowMessage
, MessageDlg
and InputQuery
) were also leaking objects; in this case UIAlertView
references. QC 115966 contains the report and a proposed source fix. As above you change a local copy of FMX.Platform.iOS.pas. This time you change TPlatformCocoaTouch.MessageDialog
. At the end of it add this statement:
AlertView.release;
Now in TPlatformCocoaTouch.InputQuery
, imediately after the assignment: Result := Delegate.Result = mrOk
add in the statement:
AlertView.release;
Accessing the Objective-C shared application object
iOS apps often need access to the Objective-C shared application object (a singleton UIApplication
object that represents the running application). One example would be when using a TWebBrowser
object you can indicate to the user that network activity is taking place as the web page is brought down by using a property of the application object to control the iDevice's network activity icon on the status bar. The browser object's OnDidStartLoad
event marks the start if the page download and OnDidFinishLoad
or OnDidFailLoadWithError
tell you when it's over. The application object has a networkActivityIndicatorVisible
property that controls the status bar icon.
In iOS you retrieve the shared application object by using the sharedApplication
class function of the UIApplication
class.
Here is how you could build a helper function to expose the shared Objective-C application object. Note that a function just like this is implemented in (although not exposed from) both these iOS FireMonkey units: FMX.MediaLibrary.iOS.pas and FMS.Pickers.iOS.
uses
iOSapi.UIKit;
function GetSharedApplication: UIApplication;
begin
Result := TUIApplication.Wrap(
TUIApplication.OCClass.sharedApplication);
end;
Given this function you could write event handlers for a web browser component like this:
procedure TBrowserForm.WebBrowserDidStartLoad(ASender: TObject);
begin
GetSharedApplication.setNetworkActivityIndicatorVisible(True);
end;
procedure TBrowserForm.WebBrowserDidFinishLoad(
ASender: TObject);
begin
GetSharedApplication.setNetworkActivityIndicatorVisible(
False);
end;
procedure TBrowserForm.WebBrowserDidFailLoadWithError(
ASender: TObject);
begin
GetSharedApplication.setNetworkActivityIndicatorVisible(True);
ShowMessage('Web page failed to load for an unknown reason');
end;
In this case the failure event handler cannot tell the user what the failure is as the NSError
object given the equivalent iOS event handler is not surfaced to FireMonkey, even in some abstract manner. I've reported this to Quality Central as QC 115652.
Logging to the OS X Console app
It is common in iOS apps to emit logging statements that are picked up by OS X’s Console application using the NSLog
API. This is a global function, not a method, and takes an NSString
, but because it is outside the scope of the Delphi helper objects and interfaces it expects a PNSString
(a pointer to an NSString
) - an Objective-C object id for the string in question. To make it easy to turn a Delphi string into a PNSString
you can build a helper function like this:
uses
iOSapi.Foundation, Macapi.ObjectiveC;
...
function PNSStr(const AStr: String): PNSString;
begin
Result := (NSStr(AStr) as ILocalObject).GetObjectID
end;
While you are at it you could build a more usable Log
function:
procedure Log(const AStr: String); overload;
procedure Log(const AStr: String; const Args: array of const); overload;
...
procedure Log(const AStr: String);
begin
{$IFDEF IOS}
NSLog(PNSStr(AStr));
{$ELSE}
{$MESSAGE WARN 'Only currently implemented for iOS'}
{$ENDIF}
end;
procedure Log(const AStr: String; const Args: array of const);
begin
Log(Format(AStr, Args))
end;
Numeric type definitions
For the most part you'll not bump into this being an issue, but the initial release of XE4 has NSInteger
and NSUInteger
both incorrectly defined in iOSapi.CocoaTypes.pas. NSInteger
is defined to be Integer
and NSUInteger
to be LongWord
, which are both always 32-bits regardless of the underlying platform. The Apple documentation for NSInteger
and NSUInteger
clearly states that on 32-bit platforms the types are 32 bits and on 64-bit platforms they are 64 bits.
It would be rare to find these wrong definitions a problem, but I did bump into it as an issue when implementing a C calling convention (cdecl
) routine that was called by Objective-C and returned an NSInteger
. When the stack was cleared up by the Objective-C side the differing size of the return value meant the function's return address was in "the wrong place" and so the app immediately crashed. I sorted this in the project by defining local versions of the types that were correct.
This has been reported as QC 115789.
Debug source stepping
The keener among us Delphi users like to step through the RTL and VCL or FMX source. With iOS apps the installation neglects to add one of the key iOS RTL directories to the browsing path in the initial XE4 release. This results in various iOS units not being found when trying to step through the source. The issue has been reported on QC, and I submitted a duplicate report before learning of that initial post.
To fix the problem go to Tools, Options..., Delphi Options, Library. Now add $(BDS)\source\rtl\ios
to the Browsing path
† the reason for the use of typically is that you can actually build a regular CocoaTouch application using Delphi for iOS if you really want to. I've done this myself with a few little apps, working through a variety of technical stumbling blocks along the way. I'm in the process of writing up my findings on the subject, so I'll hopefully blog about an article on the subject in the near future.
"... in the NextGen compiler TObject.Free simply sets the object reference to nil ..."
ReplyDeleteActually, FreeAndNil just sets the reference to nil, Free does nothing.
Bah!
ReplyDeleteThanks for the correction, gabr.
It's quite frustrating as I had it written correctly, then saw something that suggested it was as you quote it, so changed it just before posting without checking...
That'll teach me :o)
Actually 'obj.Free' is identical to 'obj := nil', so it decreases the refcount and sets the reference to nil.
ReplyDeleteGiel
Lol. I shall have to look at the code generated to see for myself now I've had 2 conflicting comments on it. Maybe I was write in the first place :o)
DeleteYes, the codegen suggests I had it right in the first place - I've reverted it back.
DeleteNote to self: always check before making changes...
We do keep you busy :-)
DeleteFor the record, TObject.Free in System.pas contains this comment: "under ARC, this method isn't actually called since the compiler translates the call to be a mere nil assignment to the instance variable, which then calls _InstClear"
Giel
Yes, that sounds familiar - I had a feeling I had seen a concrete reason for making the original comment in the first place. That's probably what it was :o)
DeleteThanks Brian. Great article!
ReplyDeleteBrian, you know how to capture inform the selected button in UIAlertView ?
ReplyDeleteThanks Brian. Great article!
Rene
Hi Rene, glad you found it useful. It requires passing in an object to act as the alert view delegate and implementing the alertView:clickedButtonAtIndex: method. You can likely see Embo code doing it if you look at the iOS-specific implementation underlying MessageDlg.
DeleteI'll try and rustle up a small example in isolation, but I'm not sure when that will happen as I'm a bit tied up preparing a talk on something entirely different just now. If I get the code sorted, I'll post it separately.
Dear Brian, could you at least tell me where I can find FMX.Platform.iOS.pas so I can study it?
DeleteI'm not finding. SBP only. DCU.
Thank you.
Rene
Not sure what SBP means, but that source file lives in $(BDS)\source\fmx
DeleteI did throw something together, but it leaks an Objective-C object. It works though, ignoring that issue. I wanna fix the leak when I get time, but if you send me an email (use the Contact Me page if you haven't got the email address) I'll send you what I currently have.
Deletesorry, meant to be. PAS
DeleteI just sent an email to you.
Rene
In the directory $(BDS)\source\ my Delphi has just DUnit folder. Do not have the folder FMX. Why is that?
DeleteIf you don't have the source, that suggests you installed a trial version, I think. The full version has complete source code.
DeleteThat is correct. The trial version did not have the source. This made it hard to really evaluate iOS development using Delphi as there were things I needed to do and couldn't.
DeleteThe source is supplied with the purchased versions.
Great article, btw.
Hello Brian,
ReplyDeleteI'd like to link to this article from several areas of The TIndex but as you currently have it structured I can't link directly to the relevant sections. Any chance you could add HTML anchors to each of the headings?
Hi Lachlan, no problem - I think it should be done now. Let me know if it hasn't worked.
Delete