Memory Management and Automatic Reference Counting Copying ...

hungryhorsecabinΛογισμικό & κατασκευή λογ/κού

14 Δεκ 2013 (πριν από 3 χρόνια και 10 μήνες)

96 εμφανίσεις

Memory Management and
Automatic Reference
Counting/

Copying Objects

Archiving


Copyright © 2012 by Yong
-
Gu Lee
(
lygu@gist.ac.kr
)


Memory leaking


NSObject
*
objA
= [[
NSObject

alloc
]
init
];


NSObject
*
objB
=
[[
NSObject

alloc
]
init
]
;


objA

=
objB
;//contents of
objA

is lost!



Up until the release of
Xcode

4.2 memory management was the subject of
great consternation, one that required a great deal of understanding and
forethought on the part of the programmer. Programmer had to carefully
navigate the world of
reference counts, retains, releases, and
autoreleases

in order to produce applications that judiciously used memory and
weren’t subject to crashing at the most inopportune times, often as the
result of trying to reference an object that had inadvertently been
destroyed before its time had really passed.


With the release of feature known as Automatic Reference Counting, or
ARC,
Xcode

4.2 programmers no longer have to think about memory
management issues.


Automatic Reference Counting ARC


Under the hood, reference counts are being
maintained and tracked. System determines
when to retain an object and when to
relese

it.


Strong variables


By default, all object pointer variables are strong
variables. That means that assigning an object
reference to such a variable causes that object to be
automatically retained
. Further, the old object
reference will be released before the assignment is
made. Finally, strong
variales

are initialized to zero by
default. And that’s true whether it’s an instance
variable or a local or global variable.

objA

=
objB
;//
contents of
objA

is lost!



If
objA

and
objB

are strong variables, the previous assignment
actually works like this:


[
objB

retain]; //
retain new value

[
objA

release];/
/ release the old value

objA

=
objB
;



Because all object variables are strong variables by default, you
don’t need to declare them as such. However you can explicitly do
so by using the
__strong

keyword for a variable;


__strong
NSObject

objA
;


It’s important to note that properties are not
strong

by default. Their default attribute is
unsafe_unretained

(or,
equivantly

assign
). You’ve seen
how to declare the strong attribute for a
property.


@
property

(
strong
,
nonatomic
)
NSMutableArray

*
birdNames
;


The compiler makes sure that strong properties
survive the event loop by retaining them on
assignment. No such action is taken for
properties that are
unsafe_unreained

(aka
assign
) or
weak.



If you need to hold onto an object to make
sure it doesn’t get destroyed by someone else,
you should retain it. Make sure to release the
object when you are done with it

Retain cycle

subview

(child view)

view (parent view)

retain

cycle

When two objects have strong references to each other, you create
what’s known as a
retain cycle
. The system will not destroy an
object if there’s still a reference to it. So if two objects have strong
references to each other, neither can ever be destroyed.

Weak variable

subview

(child view)

view (parent view)

A weak variable does not prevent
deallocation

of the object it
references.

When you declare a weak variable a few things happen; the system
tracks the reference that is made on assignment to that variable.
And
when that referenced object gets
deallocated
, the weak
variable gets automatically set to nil
. That prevents any crashes that
might occur by inadvertently sending a message to that variable

weak

strong


To declare a weak variable you use the __weak keyword:


__weak
UIView

*
parentView
;


or you use the weak attributes for a property


@property (weak,
nonatomic
)
UIView

*
parentView
;


Weak variables are also useful when working with
delegates. By making the variable that holds the reference
to the delegate a weak variable
, you’re assured that the
variable will be zeroed if the delegate object gets
deallocated
.

Again, this can prevent the kind of system
crashes that have caused headaches for many programmer
prior to the invention of ARC.


Note that weak variables are not supported in
iOS

4. In
such cases, you can still use the
unsafe_unretained
(or
assign) property
attriute

or declare your variable to be
__
unsafe_unretained
. However, realize that these variables
are not zeroed automatically when the referenced object is
deallocated
.

@
autoreleasepool

Blocks


You’ve seen in every example in this book so
far how the compiler generates an
@
autoreleasepool

directive inside your main
routine. This directive encloses a block of
statements that define an
autoreleasepool

context. Any objects created in that context
that are
autoreleased

(and this is done
automatically with ARC) will be destroyed by
default at the end of that
autoreleasepool

block.


Copying objects

Shallow Versus Deep Copying

NSMutableArray

*
dataArray

= [
NSMutableArray

arrayWithObjects
:


[
NSMutableString

stringWithString
: @"one"],


[
NSMutableString

stringWithString
: @"two"],


[
NSMutableString

stringWithString
: @"three"],


nil


];

NSMutableArray

*dataArray2;

NSMutableString

*
mStr
;

NSLog

(@"
dataArray
: ");

for (
NSString

*
elem

in
dataArray

)


NSLog

(@" %@",
elem
);


// make a copy, then change one of the strings

dataArray2 = [
dataArray

mutableCopy
];

mStr

= [
dataArray

objectAtIndex
: 0];

[
mStr

appendString
: @"ONE"];


NSLog

(@"
dataArray
: ");

for (
NSString

*
elem

in
dataArray

)


NSLog

(@" %@",
elem
);


NSLog

(@"dataArray2: ");

for (
NSString

*
elem

in dataArray2 )


NSLog

(@" %@",
elem
);

dataArray
:

oneONE

two

three


dataArray
:

one

two

three

dataArray2:

oneONE

two

three

To
make distinct copies of each element
of the array, you must perform a
deep
copy
. This means making copies of the
contents of each object in the array, not
just copies of the references to the
objects (and think about what that
implies if an element of an array is itself
an array object). But deep copies are
not performed by default when you use
the copy or
mutableCopy

methods with
the Foundation classes. In Chapter 19,
“Archiving,” we show you how to use
the Foundation’s archiving capabilities
to create a deep copy of an object.

Implementing the <
NSCopying
>
Protocol


We now show how you can add a copy method to your Fraction
class.


Recall that your Fraction class contains two integer instance variables,
called numerator and denominator
. To
make a copy of one of these
objects, you must
allocate space
for a new fraction and then simply copy
the values of the two integers into the new fraction.


When you implement the <
NSCopying
> protocol, your class must
implement the
copyWithZone
: method to respond to a copy message.
(The copy message just sends a
copyWithZone
: message to your class with
an argument of nil.) If you want to make a distinction between mutable
and immutable copies, as we noted, you’ll also need to
implement
the
mutableCopyWithZone
: method according to the <
NSMutableCopying
>
protocol. If you implement both methods,
copyWithZone
: should return
an immutable copy and
mutableCopyWithZone
: should return a mutable
one. Making a mutable copy of an object does not require that the object
being copied also be mutable (and vice versa); it’s perfectly reasonable to
want to make a mutable copy of an immutable object (consider a string
object, for example).


Here’s what the
@interface
directive should look like
:


@interface Fraction:
NSObject

<
NSCopying
>


Fraction
is a subclass of
NSObject

and conforms to the
NSCopying

protocol.


In the implementation file
Fraction.m
, add the following definition
for your new method:

-
(id)
copyWithZone
: (
NSZone

*) zone {

Fraction *
newFract

= [[Fraction
allocWithZone
: zone]
init
];

[
newFract

setTo
: numerator over: denominator];

return
newFract
; }


The
zone
argument has to do with
different memory zones
that
you can allocate and work with in your program
. You
need to
deal with these only if you’re writing
applications
that allocate
a lot of memory and you want to optimize the allocation by
grouping them into these zones
. You
can take the value
passed to
copyWithZone
:
and
hand it off

to a memory allocation
method called
allocWithZone
:
.This method allocates memory in a
specified zone.


After allocating a new
Fraction
object, you copy the receiver’s
numerator
and
denominator
variables into
it.The

copyWithZone
:
method is supposed to return the new copy of the object,
which you do in your method.

Archiving


In Objective
-
C terms, archiving is the process
of
saving one or more objects in a format so
that they can later be restored
. Often this
involves writing the object(s) to a file so it can
subsequently be read back in
. We
discuss two
methods for archiving data in this
chapter
:
property lists
and
key
-
valued coding
.

Archiving with
XML Property Lists


Mac OS X applications use
XML
propertylists

(or
plists
) for storing things such as
your default
preferences, application settings, and
configuration information
, so it’s useful to know
how to create them and read them back in
. Their
use for archiving purposes,
however
, is limited
because when creating a property list for a data
structure, specific object classes are not retained,
multiple references to the same object are not
stored, and the
mutability
of an object is not
preserved.


If your objects are of type
NSString
,
NSDictionary
,
NSArray
,
NSDate
,
NSData
, or
NSNumber
, you can use the
writeToFile:atomically
: method implemented
in these classes to write your data to a file. In
the case of writing out a
dictionary or an
array
, this method writes the data to the file
in

the format of an XML property list
.

NSDictionary

*glossary =

[
NSDictionary

dictionaryWithObjectsAndKeys
:


@"
ObjA
",


@"
KeyofObjA
",


@"
ObjB
",


@"
KeyofObjB
",


nil


];

if ([glossary
writeToFile
: @"glossary" atomically: YES] == NO
)



NSLog

(@"Save to file failed!");

The
writeToFile:atomically
: message is sent to your dictionary object
glossary, causing the dictionary to be written to the file glossary in
the form of a property list. The
atomically parameter

is set to YES,
meaning that you want the write operation to be done to a
temporary backup file first; once successful, the final data is to be
moved to the specified file named glossary
. This
is a safeguard that
protects the file from becoming corrupt if, for example, the system
crashes in the middle of the write operation. In that case, the
original glossary file (if it previously existed) isn’t harmed.

<?xml version="1.0" encoding="UTF
-
8"?>

<!DOCTYPE
plist

PUBLIC "
-
//Apple//DTD PLIST 1.0//EN"
"
http://www.apple.com/DTDs/PropertyList
-
1.0.dtd">

<
plist

version="1.0">

<
dict
>


<key>
KeyofObjA
</key>


<string>
ObjA
</string>


<key>
KeyofObjB
</key>


<string>
ObjB
</string>

</
dict
>

</
plist
>

You can see from the XML file that was created that the dictionary is written to the file
as a set of key (<key>...</key>) value (<string>...</string>) pairs.


When you create a property list from a dictionary, the
keys in the dictionary must all be
NSString

objects
. The
elements

of an array or the values in a dictionary can
be
NSString
,
NSArray
,
NSDictionary
,
NSData
,
NSDate
, or
NSNumber

objects.


To read an XML property list from a file into your program, you use the
dictionary
WithContentsOfFile
: or
array
WithContentsOfFile
: methods
. To
read back data,
use the
data
WithContentsOfFile
: method; to read back string objects, use the
string
WithContentsOfFile
: method.


NSDictionary

*glossary;

glossary = [
NSDictionary

dictionaryWithContentsOfFile
: @"glossary"];

for (
NSString

*key in glossary )


NSLog

(@"%@: %@", key, [glossary
objectForKey
: key]);

Above reads
back the glossary written
and
then displays its contents
.


Your property lists
don’t need to be created from an Objective
-
C program
; the
property
list can come from any source
. You
can make your own property lists
using a simple text editor, or you can use the Property List Editor program located
in the /Developer/Applications/Utilities directory on Mac OS X systems. If you
plan to work with property lists in your applications, you may want to take a look
at the
NSPropertyListSerialization

class, which allows property lists to be written
to and read from files in a machine
-
portable manner.




Archiving with
NSKeyedArchiver


A more
flexible approach enables you to save
any type of objects to a file, not just strings,
arrays, and dictionaries
. This
is done by
creating a
keyed
archive using the
NSKeyedArchiver

class.



Mac OX X has supported keyed archives since version
10.2. Before that,
sequential
archives were created
with the
NSArchiver

class. Sequential archives require
that the data in the archive be read back in precisely
the same order in which it was written.


A keyed archive is one in which
each field of the
archive has a name
. When
you archive an object, you
give it a name,
or
key
.
When

you retrieve it from the
archive, you retrieve it by the same key. In that
manner,
objects can be written to the archive and re
-

trieved

in any order
. Furthermore, if new instance
variables are added or removed to a class, your
program can account for it.


NSDictionary

*glossary =

[
NSDictionary

dictionaryWithObjectsAndKeys
:


@"
ObjA
",


@"
KeyofObjA
",


@"
ObjB
",


@"
KeyofObjB
",


nil


];

[
NSKeyedArchiver

archiveRootObject
: glossary
toFile
: @"
glossary.archive
"];

NSDictionary

*glossary;

glossary = [
NSKeyedUnarchiver

unarchiveObjectWithFile
: @"
glossary.archive
"];

for (
NSString

*key in glossary )


NSLog

(@"%@: %@", key, [glossary
objectForKey
: key]);

Writing

Reading

Writing Encoding and Decoding Methods

The
encodeWithCoder
: method is invoked each
time the
archiver

wants to encode an object
from the specified class, and the method tells it
how to do so. In a similar manner, the
initWithCoder
: method is invoked each time an
object from the specified class is to be decoded.

In general, the encoder method should specify
how to archive each instance variable in the
object you want to save. Luckily, you have help
doing this. For the basic Objective
-

C classes
described previously, you can use the
encodeObject:forKey
: method
. For basic
underlying C data types

(such as integers and
floats), you use one of the methods listed in
Table 19.1
. The
decoder method
,
initWithCoder
:
, works
in
reverse: You
use
decodeObject:forKey
: to decode basic
Objective
-
C classes and the appropriate
decoder method shown in Table 19.1 for the
basic data types.

@interface
AddressCard

:
NSObject

<
NSCoding
>

@property (copy,
nonatomic
)
NSString

*name, *email;

-
(void)
setName
: (
NSString

*)
theName

andEmail
: (
NSString

*)
theEmail
;

-
(void) print;

-
(
NSComparisonResult
)
compareNames
: (id) element;

@
end

-
(void)
encodeWithCoder
: (
NSCoder

*) encoder

{


[encoder
encodeObject
: name
forKey
: @"
AddressCardName
"];


[encoder
encodeObject
: email
forKey
: @"
AddressCardEmail
"];

}

-
(id)
initWithCoder
: (
NSCoder

*) decoder

{


name = [decoder
decodeObjectForKey
: @"
AddressCardName
"];


email = [decoder
decodeObjectForKey
:@"
AddressCardName
"];


return self;

}

-
(void)
encodeWithCoder
: (
NSCoder

*) encoder

{


[encoder
encodeObject
:
bookName

forKey
: @"
AddressBookBookName
"];


[encoder
encodeObject
: book
forKey
: @"
AddressBookBook
"];

}

-
(id)
initWithCoder
: (
NSCoder

*) decoder

{


bookName

= [decoder
decodeObjectForKey
: @"
AddressBookBookName
"];


book = [decoder
decodeObjectForKey
: @"
AddressBookBook
"];


return self;

}

@interface
AddressBook

:
NSObject

<
NSCoding
>

@property (
nonatomic
, copy)
NSString

*
bookName
;

@property (
nonatomic
, strong)
NSMutableArray

*book;





@
end


if ([
NSKeyedArchiver

archiveRootObject
:
myBook

toFile
: @"
addrbook.arch
"] == NO)


NSLog

(@"
archiving

failed
")
;



AddressBook

*
myBookFromArchive
;


myBookFromArchive

= [
NSKeyedUnarchiver

unarchiveObjectWithFile
:
@"
addrbook.arch
"];


[
myBookFromArchive

list
];

Write and read

@interface Foo:
NSObject

<
NSCoding
> {


NSString

*
strVal
;
int

intVal
; float
floatVal
;

}

@property (copy,
nonatomic
)
NSString

*
strVal
; @property
int

intVal
;

@property float
floatVal
;

@end

@implementation Foo

@synthesize
strVal
,
intVal
,
floatVal
;

-
(void)
encodeWithCoder
: (
NSCoder

*) encoder

{


[encoder
encodeObject
:
strVal

forKey
: @"
FoostrVal
"];


[encoder
encodeInt
:
intVal

forKey
: @"
FoointVal
"];


[encoder
encodeFloat
:
floatVal

forKey
: @"
FoofloatVal
"];

}

-
(id)
initWithCoder
: (
NSCoder

*) decoder

{


strVal

= [decoder
decodeObjectForKey
: @"
FoostrVal
"];


intVal

= [decoder
decodeIntForKey
: @"
FoointVal
"];


floatVal

= [decoder
decodeFloatForKey
: @"
FoofloatVal
"];


return self;

}

@end

Foo *myFoo1 = [[Foo
alloc
]
init
];

Foo *myFoo2;

[myFoo1
setStrVal
: @"This is the string"]; [myFoo1
setIntVal
: 12345];

[myFoo1
setFloatVal
: 98.6];

[
NSKeyedArchiver

archiveRootObject
: myFoo1
toFile
: @"
foo.arch
"];

myFoo2 = [
NSKeyedUnarchiver

unarchiveObjectWithFile
: @"
foo.arch
"];

NSLog

(@"%@
\
n%i
\
n%g
", [myFoo2
strVal
], [myFoo2
intVal
],[myFoo2
floatVal
]);

This is the string

12345

98.6

Using
NSData

to Create Custom
Archives


NSData

object can be used to
reserve an area of memory
into
which you can store data
. Typical
uses of this data area might be to
provide temporary storage for data that will subsequently be
written to a file or perhaps to hold the contents of a file read from
the disk
. The
simplest way to create a mutable data area is with the
data method:


dataArea

= [
NSMutableData

data];


This creates an empty buffer space whose size expands as needed
as the program
executes
.


As a simple example, let’s assume that you want to archive your
address book and one of your Foo objects in the same file. Assume
for this example that you’ve added keyed archiving methods to the
AddressBook

and
AddressCard

classes

Foo
*myFoo1 = [[Foo
alloc
]
init
];

NSMutableData

*
dataArea
;

NSKeyedArchiver

*
archiver
;

AddressBook

*myBook2
;

[myFoo1
setStrVal
:@"This is the string"];

[myFoo1
setIntVal
: 12345];

[
myFoo1
setFloatVal
: 98.6];

// Set up a data area and connect it to an
NSKeyedArchiver

object

dataArea

= [
NSMutableData

data];

archiver

= [[
NSKeyedArchiver

alloc
]
initForWritingWithMutableData
:
dataArea
];

// Now we can begin to archive objects

[
archiver

encodeObject
: myBook2
forKey
: @"
myaddrbook
"];

[
archiver

encodeObject
: myFoo1
forKey
: @"myfoo1"];

[
archiver

finishEncoding
];

// Write the archived data area to a file

if ([
dataArea

writeToFile
: @"
myArchive
" atomically: YES] == NO)


NSLog

(@"Archiving failed!");

specify the area in which to write the archived data

NSData

*dataArea3;

NSKeyedUnarchiver

*
unarchiver
;

Foo *myFoo3;

AddressBook

*myBook3;

// Read in the archive and connect an

//
NSKeyedUnarchiver

object to it

dataArea3 = [
NSData

dataWithContentsOfFile
: @"
myArchive
"];

if (! dataArea3)

{


NSLog

(@"Can’t read back archive file!");


return 1;

}

unarchiver

= [[
NSKeyedUnarchiver

alloc
]
initForReadingWithData
: dataArea3];

// Decode the objects we previously stored in the archive

myBook3 = [
unarchiver

decodeObjectForKey
: @"
myaddrbook
"];

myFoo3 = [
unarchiver

decodeObjectForKey
: @"myfoo1"];

[
unarchiver

finishDecoding
];

// Verify that the restore was successful [
myBook

list];

NSLog

(@"%@
\
n%i
\
n%g
", [myFoo1
strVal
], [myFoo1
intVal
], [myFoo1
floatVal
]);

Using the
Archiver

to Copy Objects

NSData

*data;

NSMutableArray

*
dataArray

= [
NSMutableArray

arrayWithObjects
:


[
NSMutableString

stringWithString
: @"one"],


[
NSMutableString

stringWithString
: @"two"],


[
NSMutableString

stringWithString
: @"three"],


nil


];

NSMutableArray

*dataArray2;

NSMutableString

*
mStr
;

// Make a deep copy using the
archiver

data = [
NSKeyedArchiver

archivedDataWithRootObject
:
dataArray
];

dataArray2 = [
NSKeyedUnarchiver

unarchiveObjectWithData
: data];

mStr

= [dataArray2
objectAtIndex
: 0];

[
mStr

appendString
: @"ONE"];

NSLog

(@"
dataArray
: ");

for (
NSString

*
elem

in
dataArray

)


NSLog

(@"%@",
elem
);

NSLog

(@"
\
ndataArray2: ");

for (
NSString

*
elem

in dataArray2 )


NSLog

(@"%@",
elem
);