This is the code of the Delphi media player:
type
TAVMedia = class(TMedia)
private
FPlayer: AVPlayer;
FPlayerItem: AVPlayerItem;
public
constructor Create(const AFileName: string); override;
destructor Destroy; override;
end;
constructor TAVMedia.Create(const AFileName: string);
var aURL: NSUrl;
begin
inherited Create(AFileName);
FPlayerItem := TAVPlayerItem.Wrap(TAVPlayerItem.OCClass.playerItemWithURL(URL));
FPlayerItem.retain;
FPlayer := TAVPlayer.Wrap(TAVPlayer.OCClass.playerWithPlayerItem(FPlayerItem));
FPlayer.retain;
end;
destructor TAVMedia.Destroy;
begin
FPlayer.release;
FPlayer := nil;
FPlayerItem.release;
FPlayerItem := nil;
inherited Destroy;
end;
I don't quite understand why they need to do FPlayerItem.retain
and FPlayer.retain
? FPlayerItem
and FPlayer
are object fields and not local variables so there is always a strong reference to them. So what is the purpose of the retain
here?
It seems that doing FPlayer.release;
will also deallocate the FPlayerItem
, so when later FPlayerItem.release;
is called sometimes it triggers access violation (strangely not always).
Note: i still can't understand why i have an eaccessviolation so i decided to put here the full code of exactly what i did :
type
TMyMedia = class(TObject)
private
FPlayer: AVPlayer;
FPlayerItem: AVPlayerItem;
public
constructor Create;
destructor Destroy; override;
end;
constructor TMyMedia.Create;
begin
inherited Create;
P := TNSUrl.OCClass.URLWithString(StrToNSStr(aDataSource)); // Creates and returns an NSURL object initialized with a provided URL string
if P = nil then raise EFileNotFoundException.Create(SFileNotFound); // If the URL string was malformed or nil, returns nil.
aURL := TNSUrl.Wrap(P);
try
FPlayerItem := TAVPlayerItem.Wrap(TAVPlayerItem.OCClass.playerItemWithURL(URL));
FPlayerItem.retain;
finally
aURL.release; // << if i don't do this then i will not have any exception at the end ???
aURL := nil; // <<
end;
FPlayer := TAVPlayer.Wrap(TAVPlayer.OCClass.playerWithPlayerItem(FPlayerItem));
FPlayer.retain;
end;
destructor TAVMedia.Destroy;
begin
ALLog('FPlayer.retainCount', inttostr(FPlayer.retainCount)); // => show 1
ALLog('FPlayerItem.retainCount', inttostr(FPlayerItem.retainCount)); // => show 6
FPlayer.release;
FPlayer := nil;
ALLog('FPlayerItem.retainCount', inttostr(FPlayerItem.retainCount)); // => show 1
FPlayerItem.release; => here i receive Access violation at address 2156565 accessing address 68684458
FPlayerItem := nil;
inherited Destroy;
end;
FPlayer
and FPlayerItem
are Delphi object wrappers around Objective-C raw object pointers.
While both Delphi for iOS and underlying iOS frameworks use reference counting to manage lifetime of object instances all similarities end there. Those are two separate reference counting mechanisms.
While keeping strong reference to FPlayer
and FPlayerItem
ensures lifetime of the Delphi wrapper instances, calling retain
increases reference count on wrapped Objective-C object instance and keeps that object instance alive during the lifetime of wrapper itself.
Without calling retain
wrapped object could get released by OS while Delphi wrapper still uses it.
Of course, to decrease reference count on wrapped object it is necessary to use matching release
call when wrapper is destroyed.
As to why exceptions happen during FPlayerItem.release;
it is hard to tell. It could be threading issue, bug in FMX part or even underlying OS frameworks.
As far as wrapped Objective-C instances are concerned they keep their own strong references where needed so releasing order is not important as far as they are concerned and it is also unlikely that OS is the culprit here (I cannot say for sure).
But if I would have to write above destructor code I would use following pattern to avoid issues on Delphi side.
destructor TAVMedia.Destroy;
var
tmpPlayer: AVPlayer;
tmpPlayerItem: AVPlayerItem;
begin
tmpPlayer := FPlayer;
tmpPlayerItem := FPlayerItem;
FPlayer := nil;
FPlayerItem := nil;
tmpPlayer.release;
tmpPlayerItem.release;
inherited Destroy;
end;