A colleague of mine, Chris, has been developing an application for iOS that manages players for a casual competition called HoopStars.
Part of the functionality of the app is downloading images that are “avatars” for players, using FTP. I had been helping out Chris with other aspects of the development of the app, however this became the most challenging.
Curiously, the issue was happening on iOS devices only; not on the simulator. After a number of files had downloaded, the app would crash on the next file. I ran the code through the debugger, and the problem (an access violation) seemed to be emanating from TApplication.DoIdle in FMX.Forms. The callstack looked like this:
Since for me, it would always crash on the same file, I put a breakpoint in the loop that iterated the file names and retrieved the file, with the breakpoint condition set to the index in the loop on which it would crash. Then, I traced through the Indy code to see where the problem was actually occurring.
It turns out that TIdIOHandler.ReadBytes is coded such that when a read returns zero bytes, an EIdConnClosedGracefully exception is thrown. This is quite normal, and TIdIOHandler.ReadStream (which calls ReadBytes) is coded to handle the exception. I haven’t quite confirmed this, however it appears once “too many” of the exceptions are thrown, it causes a side-effect of the access violation in TApplication.DoIdle. Remember, this happens only on the device; it all works ok on the simulator.
After a number of hours of hacking around in the IdIOHandler unit, I sought additional help, and rescue came from Remy Lebeau (another TeamB member). With his help, I’ve created a class descended from TIdIOHandlerStack called TIOHandlerStackEx, which overrides ReadStream, but does not call ReadBytes, and instead deals with ReadFromSource direct. This means that the OnDataChannelCreate event of TIdFTP can be used to assign an instance of TIOHandlerStackEx to the IOHandler property of the ADataChannel parameter in the event, thus (for example):
procedure TMyClass.DataChannelCreate(ASender: TObject; ADataChannel: TIdTCPConnection); begin ADataChannel.IOHandler := TIOHandlerStackEx.Create(ADataChannel); end;
This overcomes the problem of the apparent “too many” exceptions issue on iOS devices. The file IOHandlerStackEx is available for download at the link following.