A very common problem you run into when programming is keeping track of a single object or class that is shared amongst multiple classes. For example, you may have one socket which is responsible for sending all data to a remote machine, but there are multiple classes which need access to the socket.
One choice is to hand a reference to that socket out to anyone who needs it. This certainly works, but then you run into problems where the socket isn't destroyed properly because some class somewhere is still holding a reference to it (or other such referencing problems). What you really want to do is use a Singleton.
A Singleton is exactly like it sounds -- a single instance of an object that can be easily shared. Ideally, you want to be able to say SomeClass.Instance() and get the singleton instance of SomeClass. However, REALbasic doesn't have the ability to create static methods on classes, so we're going to see a solution that uses Modules to do the trick instead.
The concept is easy, and the implementation follows nicely along. We want to make a module that has a private property. We'll give a public accessor to get a reference to this property so that everyone gets the same instance. Let's stick with the global TCPSocket idea for a moment and see what the code might look like.
[rbcode]
Module GlobalSocket
Private Dim mSock as TCPSocket
Protected Function Instance() as TCPSocket
// If the socket hasn't been made yet, then make it
if mSock = nil then
mSock = new TCPSocket
end if
// And then return it
return mSock
End Function
End Module
[/rbcode]
As you can see, the code is quite easy. We implement the Instance method, and it's only job is to return the one instance of the TCPSocket. So now let's peek at how you'd use this.
Let's say you've got three classes which all perform a synchronous operation using our singleton socket. Class1 does a write, Class2 does a read and Class3 does both.
[rbcode]
Class Class1
Sub DoSomething()
Dim s as TCPSocket = GlobalSocket.Instance
s.Write( "Singletons rock!" )
while s.BytesLeftToSend > 0
s.Poll
if s.LastErrorCode <> 0 then return
wend
End Sub
End Class
Class Class2
Sub DoSomethingElse()
Dim s as TCPSocket = GlobalSocket.Instance
while s.BytesAvailable < 20
s.Poll
if s.LastErrorCode <> 0 then return
wend
MsgBox s.Read( 20 )
End Sub
End Class
Class Class3
Sub DoSomethingCool()
Dim s as TCPSocket = GlobalSocket.Instance
while s.BytesAvailable < 20
s.Poll
if s.LastErrorCode <> 0 then return
wend
MsgBox s.Read( 20 )
s.Write( "Yeehaw!" )
while s.BytesLeftToSend > 0
s.Poll
if s.LastErrorCode <> 0 then return
wend
End Sub
End Class
[/rbcode]
As you can see, we just call GlobalSocket.Instance to get a handle to our global instance. We don't have to worry about releasing it because of REALbasic's reference counting system (and it won't ever be released until the Module nils out its reference). The nice thing about this is that the classes themselves don't have to bother keeping track of the socket -- that's all handled by the Singleton pattern. So the class can just request the socket and use it.
So let's say those three classes all reside in different threads. This means that they may access the socket at (roughly) the same time, which can wreak havoc if you're not careful.
Well, another handy thing about using a Singleton is that you can provide access control to it. Remember, a socket is a system resource. If you have multiple threads running and they want to use the socket, you can run into a huge mess if you just try to share instances of a socket between multiple threads. Two threads can try mucking with the socket at the same time, and access is hard to limit. However, because of the Singleton, you can use a Semaphore (with a few modifications to our rules).
[rbcode]
Module GlobalSocket
Private Dim mSock as TCPSocket
Private Dim mLock as Semaphore
Protected Function Instance() as TCPSocket
// If the socket hasn't been made yet, then make it
if mSock = nil then
mSock = new TCPSocket
end if
// If the semaphore hasn't been made yet, then make it
if mLock = nil then mLock = new Semaphore
// Enter the semaphore so that we provide our access control
mLock.Signal
// And then return the socket
return mSock
End Function
Sub Release()
// Release our lock on the semaphore so that others
// can use it as well
mLock.Release
End Sub
End Module
[/rbcode]
Obviously, this means that you need to call GlobalSocket.Release when you're done using the socket. But as you can see, this is a very simple way to limit access to the global resource so that no two threads can use it at the same time. We only have to make a few modifications to our existing classes to make them thread-safe.
Hopefully you've come away from this post with a better understanding of what a Singleton is and why it's useful. It's a pattern that you'll see time and again in applications because of the flexibility it affords the programmer. Enjoy!
[Updated: 10/24/05, 4:45pm]
Charles Yeomans was kind enough to point out that I'm not implementing the traditional form of the Singleton pattern, and he's absolutely right. Traditionally, a Singleton has the added rule which states "only one instance of the Singleton is allowed in the application." As you can see with my "singleton", it's trivial to create other instances of the class using the new keyword. The only thing singular about the class is when you access it via the module.
Charles wrote an excellent article in RB Developer going into a more complex example of how to implement a Singleton class in REALbasic using operator overloading, constructor hiding and other tricks. It's a good read, and worth checking out.
Kudos to Charles for pointing out my slip-up!
Hi Aaron,
Newbie question. When does a REALbasic module's reference become nil? You mentioned RB's reference counting. It sounds like the module reference will become nil when there is no more code using it. Or is it when the application ends?
Modules never go out of scope. Think of them like a singleton instance of a class that RB makes for you when the application starts up. There's only one "instance" of a Module, and it stays in scope for the life of your application. But, remember, modules aren't really classes, so they don't have things like constructors or destructors.
That makes sense. I think I was confused by the phrase "and it won’t ever be released until the Module nils out its reference". Now I see you were talking about the reference to the socket instance. So in the above example, once the socket instance is created, it will remain until the application ends. That's the way I would expect it to work. Thanks for your response.
what if two threads come to this line at the same time...
if mSock = nil then
still not thread safe... could cause creation of two singletons... it would be better to hold a mutex around the creation and double check for nill...
check for nill.. mutex.lock.. check for nil... create mutex.release
They can't. REALbasic uses a cooperative threading model, not a preemptive one. If we had preemptive threads though, you're absolutely right.
shows my lack of *implementation detail* knowledge :)
Never fear -- threads is one of those things where it's always better to be safe than sorry! It was an excellent point to bring up. :-)