• Keine Ergebnisse gefunden

Allocating Memory for Objects

Im Dokument Oriented Software (Seite 124-134)

In Objective C, memory for new objects is allocated using class methods defined in the Object class. Object defines two principal methods for this purpose, alloe and alIoeFromZone: .

+ alloc;

+ allocFrornZone: (NXZone *)zone;

Allocation and Initialization 107

These methods allocate enough memory to hold all the instance variables for an object belonging to the receiving class. They don't need to be overridden and modified in subclasses.

The argument passed to allocFrornZone: determines where the new object will be located in memory. It permits you to group related objects into the same region of memory for better performance.

Zones

In a multitasking environment like NeXTSTEP, users typically run several applications at once. These applications can easily require more memory than will physically fit on the user's system.

To solve this problem, NeXTSTEP, like most modem systems, makes use of virtual memory-a system for addressing more information than can actually be accommodated in main memory. Whenever an application references some information, the system determines whether the memory page containing that information resides in main memory.

If it doesn't, the page with the requested information must be read in. If there's no room for the new page, a page of resident memory must be stored to the disk to make room.

This swapping of pages in and out of main memory, to and from the disk, is much slower than a direct memory reference. It slows the execution of applications, and, in a

multitasking environment, can degrade the overall responsiveness of the system. Reducing swapping to a minimum can greatly increase system performance.

One way to reduce swapping is to improve locality of reference, the chance that the next piece of information the system needs to reference will be located close to the last piece of information referenced, perhaps on the same page, or at lcast on a page recently referenced and so still in main memory. The idea is to minimize the number of pages that must be resident for a given operation by putting related information on the same page (or the same few pages) and keeping unrelated, or rarely used, information on other pages.

To this end, NeXTSTEP lets you partition dynamic memory into zones and direct which zone objects (and other data structures) should be allocated from.

Zones are recorded in NXZone structures, one per zone. These structures are provided by the system; you don't have to allocate memory for them or make copies. You also don't need to look inside the structure or manipulate its fields. You can simply regard pointers to the structures as zone identifiers.

108 Chapter 4: The Run-Time System

The system creates one default zone for each application, which is returned by NXDefaultMallocZoneO.

NXZone *defaultZone

=

NXDefaultMallocZone();

Other zones can be created by the NXCreateZoneO function.

NXZone *newZone

=

NXCreateZone(vrn-page_size * 2, vrn-page_size, YES);

This function takes three arguments:

• The initial size of the zone in bytes.

• The granularity of the zone (how much it should grow or shrink by).

• Whether it's possible to free memory from the zone. For most zones, this normally is YES. However, it can be NO if a zone is to be used temporarily, then destroyed (with NXDestroyZoneO). Destroying a zone effectively deallocates all the memory within it.

The initial size of a zone and its granularity should be set to small multiples of a page size, since a page is the smallest amount of memory handled by the virtual memory system. The size of a page can vary from installation to installation; its current value is stored in the vm_page_size global variable declared in machlmach_init.h.

Ideally, zones should be moderate in size. Large zones may fail to group related data onto a small number of pages; they're prone to the same problem that zone allocation is meant to correct: the fragmentation of data across many pages.

It's also not a good idea to have a large number of zones with very little information in them.

The free space in one zone won't be available for allocation from other zones, so an application could end up using more memory than it should.

Allocating from a Zone

The allocFromZone: method permits you to cluster related objects (such as a Matrix and its Cells) in the same region of memory. It takes a pointer to a zone as its argument:

NXZone *rnatrixZone = NXCreateZone(vrn-page_size, vrn-page_size, YES);

id newObject

=

[[Matrix allocFrornZone:rnatrixZone] init];

The zone method returns the zone of the receiver and can be used to make sure one object is allocated from the same zone as another object. For example, a Matrix could be allocated from the same zone as the Window it will be displayed in:

id aMatrix

=

[[Matrix allocFromZone: [my Window zone]] init];

Allocation and Initialization 109

The NXZoneMalloeO function lets you specify a zone when dynamically allocating memory for data structures that aren't objects. It's arguments are a zone and the number of bytes to be allocated:

float *points

=

(float *)NXZoneMalloc(NXDefaultMallocZone() I

sizeof(float) * numPoints);

Allocation methods and functions that don't specify a zone, such as the alloe method, take memory from the default zone. The standard C malloeO function allocates from the default zone, or from memory outside any zone.

Objects that are commonly used together should be kept together in the same zone, along with any related data structures the objects use. For example, all the objects that contribute to a particular document and its display (the Window object, View objects, text data structures, and so on) could be kept together in the same zone, one created just for the document. When the document isn't open, none of the pages in the zone will clutter main memory.

It's equally important to keep rarely used objects separate from those that are used more frequently. For example, users only occasionally refer to an application's information panel (usually only when first becoming familiar with the application). If the objects that contribute to the panel share pages with objects that are used regularly, they will take up space in main memory even when they're not needed.

If your application often both allocates and frees a certain type of object, there are a couple of considerations to keep in mind. First, freeing tends to fragment memory. It might be best to keep all these objects in the same zone to prevent the fragmentation of other zones.

Second, freeing takes a bit of time, because newly freed memory must be coalesced with memory already free. Rather than free each object individually, you might locate them all in a temporary zone that can't free memory, then destroy the whole zone at once (through a call to NXDestroyZoneO). Such a zone can allocate memory quickly, but can only grow in size, so you should use this technique only if you will soon destroy the zone.

Initializing New Objects

The alloe and alloeFromZone: methods initialize a new object's isa instance variable so that it points to the object's class (the class object). All other instance variables are set to O. Usually, an object needs to be more specifically initialized before it can be safely used.

This initialization is the responsibility of class-specific instance methods that, by convention, begin with the abbreviation "init". If the method takes no arguments, the

110 Chapter 4: The Run-Time System

method name is just those four letters, init. If it takes arguments, labels for the arguments follow the "init" prefix. For example, a View can be initialized with an initFrame: method.

Every class that declares instance variables must provide an init ... method to initialize them. The Object class declares the isa variable and defines an init method. However, since isa is initialized when memory for a new object is allocated, all Object's init method does is return self. Object declares the method mainly to establish the naming convention described above.

The Object Returned

An init ... method normally initializes the instance variables of the receiver, then returns it.

It's the responsibility of the method to return an object that can be used without error.

However, in some cases, this responsibility can mean returning a different object than the receiver. For example, if a class keeps a list of named objects, it might provide an initName: method to initialize new instances. If there can be no more than one object per name, initName: might refuse to assign the same name to two objects. When asked to assign a new instance a name that's already being used by another object, it might free the newly allocated instance and return the other object-thus ensuring the uniqueness of the name while at the same time providing what was asked for, an instance with the requested name.

In a few cases, it might be impossible for an init ... method to do what it's asked to do. For example, an initFromFile: method might get the data it needs from a file passed as an argument. If the file name it's passed doesn't correspond to an actual file, it won't be able to complete the initialization. In such a case, the init ... method could free the receiver and return nil, indicating that the requested object can't be created.

Because an init ... method might return an object other than the newly allocated receiver, or even return nil, it's important that programs use the value returned by the initialization method, not just that returned by alloc or allocFromZone:. The following code is very dangerous, since it ignores the return of init.

id anObject = [SorneClass alloc];

[anObject init];

[anObject someOtherMessage];

It's recommended that you combine allocation and initialization messages:

id anObject = [[SomeClass alloc] init];

[anObject someOtherMessage];

Allocation and Initialization 111

If there's a chance that the init ... method might return nil, the return value should be checked before proceeding:

id anObject

=

[[SomeClass alloc] init];

if ( anObject )

[anObject someOtherMessage];

else

Arguments

An init... method must ensure that all of an object's instance variables have reasonable values. This doesn't mean that it needs to provide an argument for each variable. It can set some to default values or depend on the fact that (except for is a) all bits of memory allocated for a new object are set to O. For example, if a class requires its instances to have a name and a data source, it might provide an initName:fromFile: method, but set nonessential instance variables to arbitrary values or allow them to have the null values set by default. It could then rely on methods like setEnabled:, setFriend:, and

setDimensions: to modify default values after the initialization phase had been completed.

Any init ... method that takes arguments must be prepared to handle cases where an inappropriate value is passed. One option is to substitute a default value, and to let a null argument explicitly evoke the default.

Coordinating Classes

Every class that declares instance variables must provide an init ... method to initialize them (unless the variables require no initialization). The init ... methods the class defines initialize only those variables declared in the class. Inherited instance variables are initialized by sending a message to super to perform an initialization method defined somewhere farther up the inheritance hierarchy:

- initName: (char *)string

if ( self

=

[super init] ) {

name

=

(char *)NXZoneMalloc([self zone], strlen(string) + 1);

strcpy(name, string);

return self;

return nil;

112 Chapter 4: The Run- Time System

The message to super chains together initialization methods in all inherited classes.

Because it comes first, it ensures that superclass variables are initialized before those declared in subclasses. For example, a Matrix object must be initialized as an Object, a Responder, a View, and a Control before it's initialized as a Matrix. (See Figure 7 in Chapter 2 for the Matrix inheritance hierarchy.)

The connection between the initName: method illustrated above and the inherited init method it incorporates is diagrammed in the figure below:

- init Class A

Class B - initName:

I

I

J

Figure 15. Incorporating an Inherited Initialization Method

A class must also make sure that all inherited initialization methods work. For example, if class A defines an init method and its subclass B defines an initName: method, as shown in the figure above, B must also make sure that an init message will successfully initialize B instances. The easiest way to do that is to replace the inherited init method with a version that invokes initName:.

- init

return [self initName:"default"]i

The initName: method would, in tum, invoke the inherited method, as was shown in the example and figure above. That figure can be modified to include B's version of init.

Allocation and Initialization. 113

Class A

Class B

(

- init

- init - initName:

I

.J

Figure 16. Covering an Inherited Initialization Method

Covering inherited initialization methods makes the class you define more portable to other applications. If you leave an inherited method uncovered, someone else may use it to produce incorrectly initialized instances of your class.

In the example above, initN arne: would be the designated initializer for its class (class B).

The designated initializer is the method in each class that guarantees inherited instance variables are initialized (by sending a message to super to perform an inherited method).

It's also the method that does most of the work, and the one that other initialization methods in the same class invoke. It's a NeXTSTEP convention that the designated initializer is always the method that allows the most freedom to determine the character of a new instance (the one with the most arguments).

It's important to know the designated initializer when defining a subclass. For example, suppose we define class C, a subclass ofB, and implement an initNarne:frornFile: method.

In addition to this method, we have to make sure that the inherited init and initNarne:

methods also work for instances of C. This can be done just by covering B's initNarne:

with a version that invokes initNarne:frornFile:.

- initName: (char *)string

return [self initName:string fromFile:NULL] i

114 Chapter 4: The RUl1-Time System

For an instance of the C class, the inherited init method will invoke this new version of initName: which will invoke initName:fromFile:. The relationship between these methods is diagrammed below.

I - init

Class B - initName:

Class C -" - initName:

..310. - initName:fromFile:

~

Figure 17. Covering the Designated Initializer

This figure omits an important detail. The initName:fromFile: method, being the designated initializer for the C class, will send a message to super to invoke an inherited initialization method. But which of B's methods should it invoke, init or initName:? It can't invoke init, for two reasons:

• Circularity would result (init invokes C's initName:, which invokes initName:fromFile:, which invokes init again).

• It won't be able to take advantage of the initialization code in B's version of in it Name:.

Therefore, initName:fromFile: must invoke initName:.

- initName: (char *)string fromFile: (char *)pathname if ( self

=

[super initName:string] )

Allocation and Initialization 115

The general principle is this:

The designated initializer in one class must, through a message to super, invoke the designated initializer in an inherited class.

Designated initializers are chained to each other through messages to super, while other initialization methods are chained to designated initializers through messages to self.

The figure below shows how all the initialization methods in classes A, B, and C are linked.

Messages to self are shown on the left and messages to super are shown on the right.

- init Class A

- init Class B ,. - initName:

Class C - initName:

-'" initName:fromFile:

--

..,....

Figure 18. Initialization Chain

116 Chapter 4: The Run- Time System

I

J

~

Note that B's version of init sends a message to self to invoke the initName: method.

Therefore, when the receiver is an instance of the B class, it will invoke B's version of initName:, and when the receiver is an instance of the C class, it will invoke C's version.

Im Dokument Oriented Software (Seite 124-134)