Smalltalk offers various ways to store and retrieve objects to/from the external world.
Beside the wellknown storeOn:
method, binary storage is supported by any object.
Binary storage is both more dense (i.e. requires less space) and faster than
textual storage in the normal case. In addition, the format used by the binary storage mechanism
allows recursive and cyclic objects to be handled correctly,
which is not possible
with the ascii representation used by the normal storeOn:
mechanism.
storeOn:
message, which
asks the object to append a textual (i.e. ascii) representation of itself to
a stream, from which this object can be reconstructed.
|myObject outStream|
myObject := Array with:'hello world'
with:1.2345
with:#(1 2 3 4 5)
with:('one' -> #one).
outStream := 'data' asFilename writeStream.
myObject storeOn:outStream.
outStream close.
stores the array (myObject) in the file named "data
".
If you inspect this file, you will notice that it simply contains a smalltalk
expression which recreates the original array. Thus, the object can be read back
by asking the compiler to evaluate that expression:
the above has been wrapped into an easier to use method, which is understood
by any class:
|string|
string := 'data' asFilename readStream contents.
myObject := Compiler evaluate:string
Thus, any object can be stored by sending it
|inStream|
inStream := 'data' asFileName readStream.
myObject := Object readFrom:inStream.
storeOn:
and retrieved by sending
readFrom:
to Object or a class.
|a1 a2 hello inStream outStream|
hello := 'hello'.
a1 := Array with:hello
with:hello
with:'hello'
outStream := 'data' asFilename writeStream.
a1 storeOn:outStream.
outStream close.
inStream := 'data' asFileName readStream.
a2 := Object readFrom:inStream.
inStream close.
(a1 at:1) == (a1 at:2) "evaluates to true"
(a2 at:1) == (a2 at:2) "evaluates to false"
(a2 at:1) = (a2 at:2) "evaluates to true"
The above limitation makes that mechanism unusable when objects are to be stored which
depend on identity (for example: identityDictionaries, identitySets and some others)
For example:
The above is of course a non realistic example; however, objects like trees,
doubly linked lists etc. are typical examples.
|a1 a2 hello inStream outStream|
a1 := Array new:3.
a1 at:1 put:'hello'.
a1 at:2 put:'world'.
a1 at:3 put:a1.
outStream := 'data' asFilename writeStream.
a1 storeOn:outStream.
outStream close.
inStream := 'data' asFileName readStream.
a2 := Object readFrom:inStream.
inStream close.
a2 inspect
You will notice a message on the Transcript, when the object is stored, and find the self reference being lost in the inspector.
storeOn:
format, this format is not meant
to be human readable. Also, since it uses all 8bits of a byte, it may not be possible
to send binary encoded objects via some transport mechanisms
(i.e. electronic mail without using some uu-encoding).
Binary storage has the disadvantage that it is not compatible between different smalltalk implementations (since no common standard exists yet).
Use is pretty much the same as above:
The above can be used on any stream which supports reading/writing of bytes.
(i.e. FileStreams, WriteStream on a ByteArray, Sockets, Pipes etc).
|a1 a2 hello inStream outStream|
hello := 'hello'.
a1 := Array with:hello
with:hello
with:'hello'
outStream := 'data' asFilename writeStream.
a1 storeBinaryOn:outStream.
outStream close.
inStream := 'data' asFileName readStream.
a2 := Object readBinaryFrom:inStream.
inStream close.
(a1 at:1) == (a1 at:2) "evaluates to true"
(a2 at:1) == (a2 at:2) "evaluates to true"
Smalltalk/X offers an error handling mechanism to catch cases when an object is restored for which no valid class exists. The error is signalled using the exception mechanism (see Exception handling and signals).
It is possible to catch the error and either:
BinaryIOManager binaryLoadErrorSignal
BinaryIOManager invalidClassSignal
BinaryIOManager nonExistingClassSignal
ObsoleteObject
and the loaded object is made an instance of
it.
See example code in "doc/coding/BOSS-errors"
To do so, the binaryLoader will raise the requestConversion
exception, passing the existing class and the obsolete object as
arguments. The handler should somehow try to convert the obsolete object
and proceed with the new object as value.
The existing class can provide a conversion method (cloneFrom:
),
which has to create and return
an instance of itself given an obsolete object as a template.
A default cloneFrom:
method is provided, which creates an
object with all named and indexed instance variables preserved. However,
for special needs, your class may redefine this method and do whatever is
required for conversion (or even decide to return nil ...)
See example code in "doc/coding/BOSS-errors"
Copyright © Claus Gittinger Development & Consulting, all rights reserved
(cg@ssw.de)