Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2015-11-23

Instantiating a Swift class unknown at compile time at runtime

In objective-C it was possible to instantiate a class at runtime just from the name with NSClassFromString. In Swift this is no longer possible, however we could still use the old objective-C method with a little extra effort to obtain the necessary names etc. All in all it does not look 'nice' to me. There should be a better way...

In general, it could be said that instantiating a class completely unknown at compile time is a questionable practise. Sure there are some situations in which we can justify anything, but usually we do know something about the class, for example that it implements a certain protocol...

And that is all that is needed to use a more Swift-like way to hop around the aforementioned restriction.

If the unknown class has to implement a known protocol we can demand that the unknown class inherits from a known class. And we can specify for a known class that it must implement a "newFromSelf" method. And we can then use the newFromSelf method to create as many new objects as necessary at runtime.

In the example below a library function is made that needs to create instances of a "data end detector". That is, a network server receives data from a client and has to know when all data has been received. However the nature of the data is not known to the library. Hence a call-back or closure is necessary that identifies the end-of-data.
A further complication arises because the end-of-data closure must be able to handle segmented data transfers. I.e. the closure must have associated internal parameters that are preserved across invocations. Hence the closure is no longer a closure but becomes a class.
And since the library function should be able to spawn multiple threads each processing its own data stream, the library function should be able to create as many instances of the end-of-data detector as necessary.

    /// An child class of this is used to find the end of the data.
    
    class EndOfDataDetector {
        
        
        /// When 'endReached' is called, this buffer pointer will point to the newly received data.
        ///
        /// - Note: The value will most likely be different on each call to 'endReached'.
        
        var buffer: UnsafePointer<UInt8>?
        
        
        /// When 'endReached' is called, bufferSize will indicate how many bytes were received.
        
        var bufferSize: Int?
        
        
        /// This function should only return 'true' when it detects that the received data is complete. It is likely that this function is called more than once for each data transfer. I.e. if the method to detect the end of the incoming data is not a unique single byte, a child implementation must be able to handle segmented reception. Every received byte-block is only presented once to this function.
        
        func endReached() -> Bool {
            return false
        }
        
        
        /// All child classes must implement the newFromSelf function if SocketUtils.accept() is used. The newFromSelf function should set the internal state of the new object into a state that allows 'endReached' to be called for the first time.
        
        func newFromSelf() -> EndOfDataDetector {
            assert(false)
        }
    }

Now the library user can create a child class from EndOfDataDetector and implement the endReached() as well as the newFromSelf() function. When the library needs a new instance for a new connection request it can simply call newFromSelf() to create a new data end detector.

An example for a detector that is used to find the end of a JSON message:

class JsonMessageComplete: SocketUtils.EndOfDataDetector {
    
    var countOpeningBraces = 0
    var countClosingBraces = 0
    
    override func endReached() -> Bool {
        for i in 0 ..< bufferSize! {
            if (buffer! + i).memory == ASCII_BRACE_OPEN { countOpeningBraces++ }
            if (buffer! + i).memory == ASCII_BRACE_CLOSE {
                countClosingBraces++
                if countOpeningBraces == countClosingBraces {
                    return true
                }
            }
        }
        return false
    }
    
    override func newFromSelf() -> SocketUtils.EndOfDataDetector {
        return JsonMessageComplete()
    }
}

In the library function we can now use something like: let newEndDetector = endDetector.newFromSelf() assuming that endDetector is the argument that the library function received.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

No comments:

Post a Comment