简介
It’s a superset of the C programming language and provides object-oriented capabilities and a dynamic runtime.
Objective-C
继承了 C
的语法,基本类型,控制语句,在此之上添加了定义类和方法的语法。
You have a large library of existing objects available for your use, provided by Cocoa (for OS X) and Cocoa Touch (for iOS).
Class
define classes in Objective-C by declaring an interface
This interface includes the list of messages that the class can receive, so you also need to provide the class implementation, which contains the code to be executed in response to each message.
Some classes define objects that are immutable. This means that the internal contents must be set when an object is created, and cannot subsequently be changed by other objects. In Objective-C, all basic NSString
and NSNumber
objects are immutable. If you need to represent a different number, you must use a new NSNumber
instance.
Some immutable classes also offer a mutable version. such as NSMutableString
.
Objective-C defines a root class from which the vast majority of other classes inherit, called NSObject
.
In Objective-C, the interface and implementation are usually placed in separate files so that you only need to make the interface public.
The Objective-C syntax used to declare a class interface
looks like this:
1
2
3
@interface SimpleClass : NSObject
@end
Objects often have properties intended for public access.
Declarations for properties should be added inside the interface, like this:
1
2
3
4
5
6
@interface Person : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
Property Attributes Indicate Data Accessibility and Storage Considerations
The examples shown so far all declare properties that are intended for complete public access. This means that other objects can both read and change the values of the properties.
1
2
3
4
5
@interface Person : NSObject
@property (readonly) NSString *firstName;
@end
Method Declarations Indicate the Messages an Object Can Receive
In Objective-C terms, one object sends a message to another object by calling a method on that object.
1
- (void)someMethod;
-
indicates that it is an instance method
An Objective-C method declaration includes the parameters as part of its name, using colons, like this:
1
- (void)someMethodWithValue:(SomeType)value;
1
- (void)someMethodWithFirstValue:(SomeType)value1 secondValue:(AnotherType)value2;
and in fact the secondValue: portion of the method declaration is part of the name of the method:
1
someMethodWithFirstValue:secondValue:
Class Names Must Be Unique
It’s important to note that the name of each class must be unique within an app, even across included libraries or frameworks.
Objective-C provides a preprocessor directive - #import
, for this purpose. It’s similar to the C #include
directive, but makes sure that a file is only included once during compilation.
1
2
3
4
5
6
7
#import "XYZPerson.h"
@implementation XYZPerson
- (void)sayHello {
NSLog(@"Hello, World!");
}
@end
Objective-C string literal, @"Hello, world!"
Strings are one of several class types in Objective-C that allow a shorthand literal syntax for their creation.
Specifying @"Hello, world!"
is conceptually equivalent to saying “An Objective-C string object that represents the string Hello, world!.”
Working with Objects
send messages
1
2
[someObject doSomething];
[someObject doSomethingWithFirstValue:value1 secondValue:value2];
For the most part, the Automatic Reference Counting (ARC) feature of the Objective-C compiler takes care of these considerations for you.
Whenever you’re writing a method implementation, you have access to an important hidden value, self
. Conceptually, self
is a way to refer to “the object that’s received this message.
1
2
3
4
5
6
7
8
@implementation XYZPerson
- (void)sayHello {
[self saySomething:@"Hello, world!"];
}
- (void)saySomething:(NSString *)greeting {
NSLog(@"%@", greeting);
}
@end
There’s another important keyword available to you in Objective-C, called super
. Sending a message to super
is a way to call through to a method implementation defined by a superclass further up the inheritance chain. The most common use of super is when overriding a method.
1
2
3
4
5
6
@implementation XYZShoutingPerson
- (void)saySomething:(NSString *)greeting {
NSString *uppercaseGreeting = [greeting uppercaseString];
[super saySomething:uppercaseGreeting];
}
@end
As described earlier in this chapter, memory is allocated dynamically for an Objective-C object. The first step in creating an object is to make sure enough memory is allocated not only for the properties defined by an object’s class, but also the properties defined on each of the superclasses in its inheritance chain.
The NSObject
root class provides a class method, alloc
, which handles this process for you:
1
+ (id)alloc;
You need to combine a call to alloc
with a call to init
, another NSObject method:
1
- (id)init;
The init
method is used by a class to make sure its properties have suitable initial values at creation.
1
NSObject *newObject = [[NSObject alloc] init];
Class Factory Methods Are an Alternative to Allocation and Initialization
1
2
3
4
+ (NSNumber *)numberWithBool:(BOOL)value;
+ (NSNumber *)numberWithFloat:(float)value;
+ (NSNumber *)numberWithInt:(int)value;
+ (NSNumber *)numberWithLong:(long)value;
1
NSNumber *magicNumber = [NSNumber numberWithInt:42];
Use new to Create an Object If No Arguments Are Needed for Initialization
1
2
3
XYZObject *object = [XYZObject new];
// is effectively the same as:
XYZObject *object = [[XYZObject alloc] init];
Literals Offer a Concise Object-Creation Syntax
1
NSString *someString = @"Hello, World!";
This is effectively the same as allocating and initializing an NSString or using one of its class factory methods:
1
2
NSString *someString = [NSString stringWithCString:"Hello, World!"
encoding:NSUTF8StringEncoding];
The NSNumber
class also allows a variety of literals:
1
2
3
4
NSNumber *myBOOL = @YES;
NSNumber *myFloat = @3.14f;
NSNumber *myInt = @42;
NSNumber *myLong = @42L;
Determining Equality of Objects
If you need to test whether two objects represent the same data, you need to call a method like isEqual:
, available from NSObject
:
1
2
3
if ([firstPerson isEqual:secondPerson]) {
// firstPerson is identical to secondPerson
}
This isn’t necessary for object pointers to initialize scalar variables at the time you declare them, because the compiler will automatically set the variable to nil
if you don’t specify any other initial value.
A nil
value is the safest way to initialize an object pointer if you don’t have another value to use, because it’s perfectly acceptable in Objective-C to send a message to nil
. If you do send a message to nil
, obviously nothing happens.
Note: If you expect a return value from a message sent to nil
, the return value will be nil
for object return types, 0
for numeric types, and NO
for BOOL types. Returned structures have all members initialized to zero.
Get or Set Property Values
1
2
NSString *firstName = [somePerson firstName];
[somePerson setFirstName:@"Johnny"];
By default, these accessor methods are synthesized automatically for you by the compiler, so you don’t need to do anything other than declare the property using @property
in the class interface.
The synthesized methods follow specific naming conventions:
-
The method used to access the value (the getter method) has the same name as the property.
The getter method for a property calledfirstName
will also be calledfirstName
. -
The method used to set the value (the setter method) starts with the word “set” and then uses the capitalized property name.
The setter method for a property calledfirstName
will be calledsetFirstName:
.
1
@property (getter=isFinished) BOOL finished;
1
@property (readonly, getter=isFinished) BOOL finished;
Dot Syntax Is a Concise Alternative to Accessor Method Calls
As well as making explicit accessor method calls, Objective-C offers an alternative dot syntax to access an object’s properties.
1
2
NSString *firstName = somePerson.firstName;
somePerson.firstName = @"Johnny";
Dot syntax is purely a convenient wrapper around accessor method calls. When you use dot syntax, the property is still accessed or changed using the getter and setter methods mentioned above:
Getting a value using somePerson.firstName
is the same as using [somePerson firstName]
Setting a value using somePerson.firstName = @"Johnny"
is the same as using [somePerson setFirstName:@"Johnny"]
This means that property access via dot syntax is also controlled by the property attributes. If a property is marked readonly, you’ll get a compiler error if you try to set it using dot syntax.
Access Instance Variables Directly from Initializer Methods
A typical init method looks like this:
1
2
3
4
5
6
7
8
9
- (id)init {
self = [super init];
if (self) {
// initialize instance variables here
}
return self;
}
You should always access the instance variables directly from within an initialization method because at the time a property is set, the rest of the object may not yet be completely initialized.
1
2
3
4
5
6
7
8
9
10
- (id)initWithFirstName:(NSString *)aFirstName lastName:(NSString *)aLastName {
self = [super init];
if (self) {
_firstName = aFirstName;
_lastName = aLastName;
}
return self;
}
Use Strong and Weak Declarations to Manage Ownership
1
@property (weak) id delegate;
Copy Properties Maintain Their Own Copies
1
2
3
4
@interface XYZBadgeView : NSView
@property (copy) NSString *firstName;
@property (copy) NSString *lastName;
@end
Customizing Existing Classes
Objects should have clearly-defined tasks, such as modeling specific information, displaying visual content or controlling the flow of information.
Objective-C allows you to add your own methods to existing classes through categories and class extensions.
Categories
A category can be declared for any class, even if you don’t have the original implementation source code (such as for standard Cocoa or Cocoa Touch classes). Any methods that you declare in a category will be available to all instances of the original class, as well as any subclasses of the original class.
declare a category
1
2
3
@interface ClassName (CategoryName)
@end
1
2
3
4
5
#import "XYZPerson.h"
@interface XYZPerson (XYZPersonNameDisplayAdditions)
- (NSString *)lastNameFirstNameString;
@end
Even though any methods added by a category are available to all instances of the class and its subclasses, you’ll need to import the category header file in any source code file where you wish to use the additional methods, otherwise you’ll run into compiler warnings and errors.
As well as just adding methods to existing classes, you can also use categories to split the implementation of a complex class across multiple source code files.
Because the methods declared in a category are added to an existing class, you need to be very careful about method names.
In order to avoid undefined behavior, it’s best practice to add a prefix to method names in categories on framework classes, just like you should add a prefix to the names of your own classes.
Extensions
A class extension bears some similarity to a category, but it can only be added to a class for which you have the source code at compile time (the class is compiled at the same time as the class extension).
The methods declared by a class extension are implemented in the @implementation block for the original class so you can’t, for example, declare a class extension on a framework class, such as a Cocoa or Cocoa Touch class like NSString.
1
2
3
@interface ClassName ()
@end
Because no name is given in the parentheses, class extensions are often referred to as anonymous categories
.
Working with Protocols
Objective-C allows you to define protocols, which declare the methods expected to be used for a particular situation.
A class interface declares the methods and properties associated with that class. A protocol, by contrast, is used to declare methods and properties that are independent of any specific class.
1
2
3
@protocol ProtocolName
// list of methods and properties
@end
Objective-C uses angle brackets to indicate conformance to a protocol. This example declares a weak property for a generic object pointer that conforms to the XYZPieChartViewDataSource protocol.
1
2
3
4
@interface XYZPieChartView : UIView
@property (weak) id <XYZPieChartViewDataSource> dataSource;
...
@end
Protocols Can Have Optional Methods
By default, all methods declared in a protocol are required methods. This means that any class that conforms to the protocol must implement those methods.
It’s also possible to specify optional methods in a protocol. These are methods that a class can implement only if it needs to.
1
2
3
4
5
6
@protocol XYZPieChartViewDataSource
- (NSUInteger)numberOfSegments;
- (CGFloat)sizeOfSegmentAtIndex:(NSUInteger)segmentIndex;
@optional
- (NSString *)titleForSegmentAtIndex:(NSUInteger)segmentIndex;
@end
If a method in a protocol is marked as optional, you must check whether an object implements that method before attempting to call it.
1
2
3
4
NSString *thisSegmentTitle;
if ([self.dataSource respondsToSelector:@selector(titleForSegmentAtIndex:)]) {
thisSegmentTitle = [self.dataSource titleForSegmentAtIndex:index];
}
The respondsToSelector:
method uses a selector, which refers to the identifier for a method after compilation. You can provide the correct identifier by using the @selector()
directive and specifying the name of the method.
Protocols Inherit from Other Protocols
In the same way that an Objective-C class can inherit from a superclass, you can also specify that one protocol conforms to another.
As an example, it’s best practice to define your protocols to conform to the NSObject
protocol
1
2
3
@protocol MyProtocol <NSObject>
...
@end
Conforming to Protocols
The syntax to indicate that a class adopts a protocol again uses angle brackets.
1
2
3
@interface MyClass : NSObject <MyProtocol, AnotherProtocol, YetAnotherProtocol>
...
@end
Values and Collections
1
2
3
4
5
6
7
8
9
10
11
@implementation XYZCalculator
- (void)increment {
self.currentValue++;
}
- (void)decrement {
self.currentValue--;
}
- (void)multiplyBy:(double)factor {
self.currentValue *= factor;
}
@end
Dot syntax is purely a syntactic wrapper around accessor method calls, so each of the operations in this example is equivalent to first using the get accessor method to get the value, then performing the operation, then using the set accessor method to set the value to the result.
The BOOL
scalar type is defined in Objective-C to hold a Boolean value, which is either YES
or NO
. As you might expect, YES
is logically equivalent to true
and 1
, while NO
is equivalent to false
and 0
.
These types, like NSInteger
and NSUInteger
, are defined differently depending on the target architecture.
It’s best practice to use these platform-specific types if you might be passing values across API boundaries (both internal and exported APIs), such as arguments or return values in method or function calls between your application code and a framework.
For local variables, such as a counter in a loop, it’s fine to use the basic C types if you know that the value is within the standard limits.
The NSNumber
class is used to represent any of the basic C scalar types, including char
, double
, float
, int
, long
, short
, and the unsigned
variants of each, as well as the Objective-C Boolean type, BOOL
.
It’s also possible to create NSNumber
instances using Objective-C literal syntax:
1
2
3
4
5
6
7
8
9
10
NSNumber *magicNumber = @42;
NSNumber *unsignedNumber = @42u;
NSNumber *longNumber = @42l;
NSNumber *boolNumber = @YES;
NSNumber *simpleFloat = @3.14f;
NSNumber *betterDouble = @3.1415926535;
NSNumber *someChar = @'T';
Represent Other Values Using Instances of the NSValue Class
The NSNumber
class is itself a subclass of the basic NSValue
class, which provides an object wrapper around a single value or data item. In addition to the basic C scalar types, NSValue
can also be used to represent pointers and structures.
you can create an NSValue
instance by providing a pointer to the structure as well as an encoded Objective-C type. The @encode()
compiler directive is used to create the correct Objective-C type, like this:
1
2
3
4
5
6
7
8
9
10
11
typedef struct {
int i;
float f;
} MyIntegerFloatStruct;
struct MyIntegerFloatStruct aStruct;
aStruct.i = 42;
aStruct.f = 3.14;
NSValue *structValue = [NSValue value:&aStruct
withObjCType:@encode(MyIntegerFloatStruct)];
most collections in Objective-C code are instances of one of the Cocoa and Cocoa Touch collection classes, like NSArray
, NSSet
and NSDictionary
.
The basic NSArray
, NSSet
and NSDictionary
classes are immutable, which means their contents are set at creation. Each also has a mutable subclass to allow you to add or remove objects at will.
An NSArray
is used to represent an ordered collection of objects. The only requirement is that each item is an Objective-C object— there’s no requirement for each object to be an instance of the same class.
1
NSArray *someArray = @[firstObject, secondObject, thirdObject];
1
2
[someArray containsObject:someString]
[someArray objectAtIndex:0] // someArray[0] // alternative
If you need to be able to add or remove objects from an array after initial creation, you’ll need to use NSMutableArray
, which adds a variety of methods to add , remove or replace one or more objects:
1
2
3
NSMutableArray *mutableArray = [NSMutableArray array];
[mutableArray addObject:@"gamma"];
[mutableArray replaceObjectAtIndex:0 withObject:@"epsilon"];
An NSSet
is similar to an array, but maintains an unordered group of distinct objects.
1
NSSet *simpleSet = [NSSet setWithObjects:@"Hello, World!", @42, aValue, anObject, nil];
Rather than simply maintaining an ordered or unordered collection of objects, an NSDictionary
stores objects against given keys, which can then be used for retrieval.
1
2
3
4
5
6
NSDictionary *dictionary = @{
@"anObject" : someObject,
@"helloString" : @"Hello, World!",
@"magicNumber" : @42,
@"aValue" : someValue
};
There’s also a subscript syntax alternative to using objectForKey:
, which looks like this:
1
NSNumber *storedNumber = dictionary[@"magicNumber"];
If you need to add or remove objects from a dictionary after creation, you need to use the NSMutableDictionary
subclass.
Many collection classes conform to the NSFastEnumeration
protocol, including NSArray
, NSSet
and NSDictionary
. This means that you can use fast enumeration, an Objective-C language-level feature.
The fast enumeration syntax to enumerate the contents of an array or set looks like this:
1
2
3
for (<Type> <variable> in <collection>) {
...
}
Block
The syntax to define a block literal uses the caret symbol (^
), like this:
1
2
3
^{
NSLog(@"This is a block");
}
In the same way that you can use a function pointer to refer to a C function, you can declare a variable to keep track of a block, like this:
1
void (^simpleBlock)(void);
1
2
3
void (^simpleBlock)(void) = ^{
NSLog(@"This is a block");
};
Once you’ve declared and assigned a block variable, you can use it to invoke the block:
1
simpleBlock();
Blocks can also take arguments and return values just like methods and functions.
1
2
3
4
5
6
double (^multiplyTwoValues)(double, double) =
^(double firstValue, double secondValue) {
return firstValue * secondValue;
};
double result = multiplyTwoValues(2,4);