Ry’s Objective-C Tutorial---Properties
2015-11-22 00:00
531 查看
Properties |
methods(getters and setters) are used as an abstraction for interacting with the object’s underlying data.
Interacting with a property via accessor methods
The goal of the
@propertydirective is to make it easy to create and configure properties by automatically generating these accessor methods. It allows you to specify the behavior
of a public property on a semantic level, and it takes care of the implementation details for you.
This module surveys the various attributes that let you alter getter and setter behavior. Some of these attributes determine how properties handle their underlying memory, so this module also serves as a practical introduction to memory management in Objective-C.
For a more detailed discussion, please refer to Memory Management.
The @property Directive
First, let’s take a look at what’s going on under the hood when we use the @propertydirective. Consider the following interface for a simple
Carclass
and its corresponding implementation.
// Car.h
#import
<Foundation/Foundation.h>
@interface
Car:
NSObject
@property
BOOL
running
;
@end
[/code]
// Car.m
#import
"Car.h"
@implementation
Car
@synthesize
running
=
_running
;
// Optional for Xcode 4.4+
@end
[/code]
The compiler generates a getter and a setter for the
runningproperty. The default naming convention is to use the property itself as the getter, prefix it with
setfor
the setter, and prefix it with an underscore for the instance variable, like so:
-
(
BOOL
)
running
{
return
_running
;
}
-
(
void
)
setRunning:
(
BOOL
)
newValue
{
_running
=
newValue
;
}
[/code]
After declaring the property with the
@propertydirective, you can call these methods as if they were included in your class’s interface and implementation files. You can also
override them in
Car.mto supply custom getter/setters, but this makes the
@synthesizedirective
mandatory. However, you should rarely need custom accessors, since
@propertyattributes let you do this on an abstract level.
Properties accessed via dot-notation get translated to the above accessor methods behind the scenes, so the following
honda.runningcode actually calls
setRunning:when
you assign a value to it and the
runningmethod when you read a value from it:
// main.m
#import
<Foundation/Foundation.h>
#import
"Car.h"
int
main
(
int
argc
,
const
char
*
argv
[])
{
@autoreleasepool
{
Car
*
honda
=
[[
Car
alloc
]
init
];
honda
.
running
=
YES
;
// [honda setRunning:YES]
NSLog
(
@"%d"
,
honda
.
running
);
// [honda running]
}
return
0
;
}
[/code]
To change the behavior of the generated accessors, you can specify attributes in parentheses after the
@propertydirective. The rest of this module introduces the available attributes.
The getter= and setter= Attributes
If you don’t like @property’s default naming conventions, you can change the getter/setter method names with the
getter=and
setter=attributes.
A common use case for this is Boolean properties, whose getters are conventionally prefixed with
is. Try changing the property declaration in
Car.hto
the following.
@property
(
getter
=
isRunning
)
BOOL
running
;
[/code]
The generated accessors are now called
isRunningand
setRunning. Note that the public property
is still called
running, and this is what you should use for dot-notation:
Car
*
honda
=
[[
Car
alloc
]
init
];
honda
.
running
=
YES
;
// [honda setRunning:YES]
NSLog
(
@"%d"
,
honda
.
running
)
;
// [honda isRunning]
NSLog
(
@"%d"
,
[
honda
running
])
;
// Error: method no longer exists
[/code]
These are the only attributes that take an argument (the accessor method name)—all of the others are Boolean flags.
The readonly Attribute
The readonlyattribute is an easy way to make a property read-only. It omits the setter method and prevents assignment via dot-notation, but the getter is unaffected. As an example,
let’s change our
Carinterface to the following. Notice how you can specify multiple attributes by separating them with a comma.
#import
<Foundation/Foundation.h>
@interface
Car:
NSObject
@property
(
getter
=
isRunning
,
readonly
)
BOOL
running
;
-
(
void
)
startEngine
;
-
(
void
)
stopEngine
;
@end
[/code]
Instead of letting other objects change the
runningproperty, we’ll set it internally via the
startEngineand
stopEnginemethods.
The corresponding implementation can be found below.
// Car.m
#import
"Car.h"
@implementation
Car
-
(
void
)
startEngine
{
_running
=
YES
;
}
-
(
void
)
stopEngine
{
_running
=
NO
;
}
@end
[/code]
Remember that
@propertyalso generates an instance variable for us, which is why we can access
_runningwithout
declaring it anywhere (we can’t use
self.runninghere because the property is read-only). Let’s test this new
Carclass
by adding the following snippet to
main.m.
Car
*
honda
=
[[
Car
alloc
]
init
];
[
honda
startEngine
];
NSLog
(
@"Running: %d"
,
honda
.
running
)
;
honda
.
running
=
NO
;
// Error: read-only property
[/code]
Up until this point, properties have really just been convenient shortcuts that let us avoid writing boilerplate getter and setter methods. This will not be the case for the remaining attributes, which significantly alter the behavior of their properties. They
also only apply to properties that store Objective-C objects (opposed to primitive C data types).
The nonatomic Attribute
Atomicity has to do with how properties behave in a threaded environment. When you have more than one thread, it’s possible for the setter and the getter to be called at thesame time. This means that the getter/setter can be interrupted by another operation, possibly resulting in corrupted data.
Atomic properties lock the underlying object to prevent this from happening, guaranteeing that the get or set operation is working with a complete value. However, it’s important to understand that this is only one aspect of thread-safety—using atomic properties
does not necessarily mean that your code is thread-safe.
Properties declared with
@propertyare atomic by default, and this does incur some overhead. So, if you’re not in a multi-threaded environment (or you’re implementing your own
thread-safety), you’ll want to override this behavior with the
nonatomicattribute, like so:
@property
(
nonatomic
)
NSString
*
model
;
[/code]
There is also a small, practical caveat with atomic properties. Accessors for atomic properties must both be either generated or user-defined. Only non-atomic properties
let you mix-and-match synthesized accessors with custom ones. You can see this by removing
nonatomicfrom the above code and adding a custom getter in
Car.m.
Memory Management
In any OOP language, objects reside in the computer’s memory, and—especially on mobile devices—this is a scarce resource. The goal of a memory management system is to make sure that programs don’t take up any more space than they need to by creating and destroyingobjects in an efficient manner.
Many languages accomplish this through garbage collection, but Objective-C uses a more efficient alternative called object ownership. When you start interacting with an object,
you’re said to own that object, which means that it’s guaranteed to exist as long as you’re using it. When you’re done with it, you relinquish ownership, and—if the object
has no other owners—the operating system destroys the object and frees up the underlying memory.
Destroying an object with no owners
With the advent of Automatic Reference Counting, the compiler
manages all of your object ownership automatically. For the most part, this means that you’ll never to worry about how the memory management system actually works. But, you do have to understand the
strong,
weakand
copyattributes
of
@property, since they tell the compiler what kind of relationship objects should have.
The strong Attribute
The strongattribute creates an owning relationship to whatever object is assigned to the property. This is the implicit behavior for all object properties, which is a safe default
because it makes sure the value exists as long as it’s assigned to the property.
Let’s take a look at how this works by creating another class called
Person. It’s interface just declares a
nameproperty:
// Person.h
#import
<Foundation/Foundation.h>
@interface
Person:
NSObject
@property
(
nonatomic
)
NSString
*
name
;
@end
[/code]
The implementation is shown below. It uses the default accessors generated by
@property. It also overrides
NSObject’s
descriptionmethod,
which returns the string representation of the object.
// Person.m
#import
"Person.h"
@implementation
Person
-
(
NSString
*
)
description
{
return
self
.
name
;
}
@end
[/code]
Next, let’s add a
Personproperty to the
Carclass. Change
Car.hto
the following.
// Car.h
#import
<Foundation/Foundation.h>
#import
"Person.h"
@interface
Car:
NSObject
@property
(
nonatomic
)
NSString
*
model
;
@property
(
nonatomic
,
strong
)
Person
*
driver
;
@end
[/code]
Then, consider the following iteration of
main.m:
// main.m
#import
<Foundation/Foundation.h>
#import
"Car.h"
#import
"Person.h"
int
main
(
int
argc
,
const
char
*
argv
[])
{
@autoreleasepool
{
Person
*
john
=
[[
Person
alloc
]
init
];
john
.
name
=
@"John"
;
Car
*
honda
=
[[
Car
alloc
]
init
];
honda
.
model
=
@"Honda Civic"
;
honda
.
driver
=
john
;
NSLog
(
@"%@ is driving the %@"
,
honda
.
driver
,
honda
.
model
);
}
return
0
;
}
[/code]
Since
driveris a strong relationship, the
hondaobject takes ownership of
john.
This ensures that it will be valid as long as
hondaneeds it.
The weak Attribute
Most of the time, the strongattribute is intuitively what you want for object properties. However, strong references pose a problem if, for example, we need a reference from
driverback
to the
Carobject he’s driving. First, let’s add a
carproperty to
Person.h:
// Person.h
#import
<Foundation/Foundation.h>
@class
Car;
@interface
Person:
NSObject
@property
(
nonatomic
)
NSString
*
name
;
@property
(
nonatomic
,
strong
)
Car
*
car
;
@end
[/code]
The
@class Carline is a forward declaration of the
Carclass. It’s like telling the compiler,
“Trust me, the
Carclass exists, so don’t try to find it right now.” We have to do this instead of our usual
#importstatement
because
Caralso imports
Person.h, and we would have an endless loop of imports. (Compilers
don’t like endless loops.)
Next, add the following line to
main.mright after the
honda.driverassignment:
honda
.
driver
=
john
;
john
.
car
=
honda
;
// Add this line
[/code]
We now have an owning relationship from
hondato
johnand another owning relationship from
johnto
honda.
This means that both objects will always be owned by another object, so the memory management system won’t be able to destroy them even if they’re no longer needed.
A retain cycle between the
Carand
Personclasses
This is called a retain cycle, which is a form of memory leak, and memory leaks are bad. Fortunately, it’s very easy to fix this problem—just tell one of the properties to
maintain a weak reference to the other object. In
Person.h, change the
cardeclaration
to the following:
@property
(
nonatomic
,
weak
)
Car
*
car
;
[/code]
The
weakattribute creates a non-owning relationship to
car. This allows
johnto
have a reference to
hondawhile avoiding a retain cycle. But, this also means that there is a possibility that
hondawill
be destroyed while
johnstill has a reference to it. Should this happen, the
weakattribute
will conveniently set
carto
nilin order to avoid a dangling pointer.
A weak reference from the
Personclass
to
Car
A common use case for the
weakattribute is parent-child data structures. By convention, the parent object should maintain a strong reference with it’s children, and the children
should store a weak reference back to the parent. Weak references are also an inherent part of the delegate design pattern.
The point to take away is that two objects should never have strong references to each other. The
weakattribute makes it possible to maintain a cyclical relationship without
creating a retain cycle.
The copy Attribute
The copyattribute is an alternative to
strong. Instead of taking ownership of the existing
object, it creates a copy of whatever you assign to the property, then takes ownership of that. Only objects that conform to the
NSCopyingprotocol can
use this attribute.
Properties that represent values (opposed to connections or relationships) are good candidates for copying. For example, developers usually copy
NSStringproperties instead of
strongly reference them:
// Car.h
@property
(
nonatomic
,
copy
)
NSString
*
model
;
[/code]
Now,
Carwill store a brand new instance of whatever value we assign to
model. If you’re working
with mutable values, this has the added perk of freezing the object at whatever value it had when it was assigned. This is demonstrated below:
// main.m
#import
<Foundation/Foundation.h>
#import
"Car.h"
int
main
(
int
argc
,
const
char
*
argv
[])
{
@autoreleasepool
{
Car
*
honda
=
[[
Car
alloc
]
init
];
NSMutableString
*
model
=
[
NSMutableString
stringWithString:
@"Honda Civic"
];
honda
.
model
=
model
;
NSLog
(
@"%@"
,
honda
.
model
);
[
model
setString:
@"Nissa Versa"
];
NSLog
(
@"%@"
,
honda
.
model
);
// Still "Honda Civic"
}
return
0
;
}
[/code]
NSMutableStringis
a subclass of
NSStringthat can be edited in-place. If the
modelproperty didn’t create a
copy of the original instance, we would be able to see the altered string (
Nissan Versa) in the second
NSLog()output.
Other Attributes
The above @propertyattributes are all you should need for modern Objective-C applications (iOS 5+), but there are a few others that you may encounter in older libraries or documentation.
The retain Attribute
The retainattribute is the Manual
Retain Release version of
strong, and it has the exact same effect: claiming ownership of assigned values. You shouldn’t use this in an Automatic Reference Counted environment.
The unsafe_unretained Attribute
Properties with the unsafe_unretainedattribute behave similar to
weakproperties, but they
don’t automatically set their value to
nilif the referenced object is destroyed. The only reason you should need to use
unsafe_unretainedis
to make your class compatible with code that doesn’t support the
weakproperty.
The assign Attribute
The assignattribute doesn’t perform any kind of memory-management call when assigning a new value to the property. This is the default behavior for primitive data types, and
it used to be a way to implement weak references before iOS 5. Like
retain, you shouldn’t ever need to explicitly use this in modern applications.
Summary
This module presented the entire selection of @propertyattributes, and we hope that you’re feeling relatively comfortable modifying the behavior of generated accessor methods.
Remember that the goal of all these attributes is to help you focus on what data needs to be recorded by letting the compiler automatically determine how it’s
represented. They are summarized below.
Attribute | Description |
---|---|
getter= | Use a custom name for the getter method. |
setter= | Use a custom name for the setter method. |
readonly | Don’t synthesize a setter method. |
nonatomic | Don’t guarantee the integrity of accessors in a multi-threaded environment. This is more efficient than the default atomic behavior. |
strong | Create an owning relationship between the property and the assigned value. This is the default for object properties. |
weak | Create a non-owning relationship between the property and the assigned value. Use this to prevent retain cycles. |
copy | Create a copy of the assigned value instead of referencing the existing instance. |
© 2012-2014
RyPress.com
All Rights Reserved
Terms of Service
Privacy Policy
相关文章推荐
- Objective-c(3)
- Ry’s Objective-C Tutorial---Classes
- Ry’s Objective-C Tutorial---Founctions
- Ry’s Objective-C Tutorial---C Basics
- 【iOS】如何在Objective-C中声明Block?
- lua 垃圾回收标记函数 reallymarkobject
- Object-C 学习第一天
- Objective-C编码规范:26个方面解决iOS开发问题
- jsonObject获取json串的值
- Objective-C中NSString对象的retainCount
- java中Object类与string类及其字符串处理方法
- 针对【ObjectStateManager 中已存在具有同一键的对象。ObjectStateManager 无法跟踪具有相同键的多个对象。】的解决方案
- objective c实现配置文件+反射
- Can not deserialize instance of java.lang.String out of START_OBJECT token
- Objective-C 的属性与合成方法使用详解
- object-UI之基本控件
- object-UI之基本控件
- Objective-c单例模式的正确写法
- Objective-C加强-block代码块和protocol协议
- iOS开发系列—Objective-C之Foundation框架