I’m not very good at communication, so it may seem like I don’t contribute to the community much. Like many of you, I’m pretty dang busy most of the time. I have made a pretty big contribution to the Delphi community available on GitHub, although most of you probably have simply never downloaded it, because I’m pretty bad at communicating what it actually does.
If you want a giant set of reusable useful, albeit poorly documented, classes for Delphi. Go to GitHub and check out adaloveless/commonx. This repository is a Batman toolbelt of everything and anything I find useful. I do MOST of my work in this library, as I’m pretty obsessed with making everything reusable and portable.
Getting into commonx is a deep rabbit hole, so I figure I’ll just introduce it by starting with something really simple and really basic that it can provide:
Alternative Garbage Collection Techniques for Delphi
Typical Delphi usage of a class involves some very repetitive tasks that the broader language-agnostic programming community has evolved away from. Typically you would use a class in a pattern similar as follows.
var MyThing := TSomeObject.create;
In languages with garbage collection…. all of this could be compressed into just a single line….
Here, 6 lines of code becomes just 1! The creation of TSomeObject would return a reference that could be immediately used and then automatically freed. This kind of Automatic Reference Counting (ARC) system was met with much resistance because of all the legacy code (and legacy coders) out there that were simply not used to it. Also it didn’t help that Embarcadero made every single platform they targeted use a different set of rules, infuriating just about everyone. (They further fueled the fire by being super indecisive about whether strings should be 0 or 1 based… following different rules on every platform.)
ARC was subsequently scrapped on ALL platforms… and I must admit I like that it is consistent now… however, I always wondered… why can’t I have ARC optionally? It is convenient for lots of cases, and avoiding it is only necessary in the most performance sensitive of situations.
I then sought out to come up with a way to implement ARC-like functionality for the purposes of avoiding memory management nightmares and try..finally ugliness all over the place and aimed to package it in the simplest way I possibly could.
I came up with a few interesting mechanisms that I will describe in this article. THolder<>, which implements a “smart pointer” in Delphi. TAutoScope, which cleans up anything you put into it when it goes out of scope… and for an added bonus, I’ll talk about TGiverOf<> which implements a simple-to-use object pool.
To gain access to commonx, get it from GitHub and set a reference to it in your project settings. Start a new app and include the path to “commonx” in your search path. If you’re building for VCL also include “commonx\vcl” in your search path. FMX project should include “commonx\fmx“.
Take a look at betterobject.pas
Interfaces in Delphi are reference-counted with ARC. So a simple solution I thought would be to simply come up with a generic interface that could hold onto any kind of object and I came up with IHolder<> and THolder<>. THolder<> can be assigned to it’s counterpart IHolder<> without casting. Rewriting the aforementioned code snippet to use THolder results in code that looks like this.
var MyThing : IHolder<TSomeObject> := THolder<TSomeObject>.create(TSomeObject.create);
You could get it down to one line with a simple cast, but I’ve never tested this and it’s not really a goal to have it down to 1 line.
The real magic here is that try .. finally is not required and you have to worry less about leaked objects that you forgot to free. The only thing I don’t really like about this is that you have to dereference the property “o” which contains the actual object you’ve wrapped. But I’ve gotten used to doing this in ALL my projects now and it is a big time saver.
If you don’t want to dereference “o” each time, you really only have to dereference it once. You may choose to do to reduce the number of dereferences… speeding up your code… or simply making it less-ugly. Type inference is really a godsend here.
var MyThingH : IHolder<TSomeObject> :=
var MyThing := MyThingH.o;
THolder/IHolder furthermore solves those situations where a class is returned from a function, but nobody is sure who is responsible for freeing it. For example I have code in my projects that returns a JSON document, but that JSON document might also be stored in a cache. It would crash the program if I destroyed an object from the cache while some thread was using it somewhere… so a reference count would have to be maintained… destroying the documents only when all the references were removed…. IHolder<> comes to the rescue!
function GetJSONDocumentCached(url: string): IHolder<TJSON>;
result := GetFromCache(url);
if result=nil then
result := PutInCache(GetFromWeb(url));
var jsonHolder := GetJSONDocumentCached('whateverurl');
Somewhere else in the code there could be a simple
TArray<IHolder<TJSON>> or a
TList<IHolder<TJSON>> controlled by a mutex…. (or someday maybe I’ll cover my btree class and show you how you could declare
TBTree<IHolder<TJSON>> … there are endless possible time savings that you can dream of.
Using this pattern, I never have to worry about managing the lifetimes of my JSON documents. It can also be used for other classes derived from TObject…. using my TBetterObject as a base is not required.
It also opens up lots of interesting function possibilities that return things like TStringList, like ParseStringH() in stringx.pas which returns a IHolder<TStringList>… so there’s no need for a separate call to Free()!
Let’s say that you already have a bunch of code out there… and maybe it’s some ugly bad code that someone else wrote into a 40,000 line function that you just want to quickly mod without indenting 40,000 lines of code with a new try..finally… and screwing up your SVN merge. Enter TAutoScope… or really IAutoScope.
I’ve conveniently provided a single function, simply called AutoScope() in betterobject.pas that allows for you to instantiate a garbage collector for any scope you want. When the scope ends so does the AutoScope and every object you tell it to track. It looks a little something like this:
var someObjectIDontWantToFreeLater := TAnything.create();
var Scope := AutoScope;
end; //<--AutoScope and everything Track()ed are freed here
IAutoScope and IHolder<> can be placed in any scope…. so they can be attached to inline vars inside begin..end, at the function level, global, inside a class, a record, or even inside an array or even an array of records! Free() will be called automatically every time the object is no longer being used!
TGiver and it’s generified, more useful variant, TGiverOf<>, implements object pooling for any kind of object. To use it, simply declare an instance of TGiverOf<TSomeObject>. In my examples, we’ll imagine we’re sharing network connections, because that’s often useful.
ConnectionPool := TGiverOf<TMyConnectionClass>.create();
One nice thing about TGiverOf<> is that it understands IHolder…. so it will return holders to objects simplifying the how and when the objects are returned to the pool.
var connection_holder := ConnectionPool.Need();
If the type inference is confusing you in the above example, here it is expanded:
var connection_holder: IHolder<TMyConnectionClass> := ConnectionPool.Need();
You can limit the number of objects in the pool with the Limit property.
ConnectionPool.Limit := 10;
You can also override GivenIsGivable() to setup rules for cleaning up the pool when objects expire, although upon reading this code I am thinking I should extend it with an anonymous method property. I typically manage the pool by overriding ShouldGive() and ShouldReturn() in TBetterObject… but that would only apply if you’re using GiverOf to serve up TBetterObject variants (not always the case,but the most typical scenario for me.)
Anyway… I’m all up for ideas and suggestions. If there’s anything you’d like to see in these classes, fire me a comment, and I’ll see what I can do! Or better yet… become a contributor to the GitHub project!