Objective-C 开发基础与实践
1. 基础概念
在Objective-C中,对象的创建通常包括声明、分配和初始化三个步骤。示例代码如下:
NSObject *object; // Declaration
object = // Assignment
[NSObject alloc]; // Allocation
[object init]; // Initialization
需要注意的是,苹果建议不要将
alloc
和
init
分开在两行调用。
2. 标量类型
在C语言中,标量类型的宽度是特定的。而在Leopard系统中,引入了与宽度无关的标量类型,它们会解析为编译架构的原生宽度,具体如下表所示:
| Leopard | C | 描述 |
| ---- | ---- | ---- |
| NSInteger | int, long | 有符号整数 |
| NSUInteger | unsigned int, unsigned long | 无符号整数 |
| CGFloat | float, double | 浮点数 |
3. 日志记录
C语言的
printf
函数无法处理Objective-C对象,而
NSLog
函数不仅具备
printf
的所有标记和格式化功能,还增加了
%@
标记,用于调用对象的
description
方法。
4. 字符串
在C语言中,字符串是由
NULL
结尾的字符数组。而在Objective-C中,建议使用
NSString
类。
NSString
作为对象,提供了多个内置方法和可变子类,还能处理Unicode等。可以使用编译器指令
@"This is a string"
来声明
NSString
常量。
5. 数组
C数组只是内存块,使用方括号运算符和指针算术进行操作,无法直接得知数组的长度。在Objective-C中,建议使用
NSArray
,它和
NSString
一样,具有方便的方法和可变子类,并且会在幕后进行优化,以使用最适合长度和操作要求的数据结构。
6. 布尔类型
在C语言中,没有布尔类型,零被视为假,非零被视为真。Objective-C保留了这一约定,并添加了标量类型
BOOL
,其值为
YES
和
NO
。
7. 相等性比较
C和Objective-C都使用
=
进行赋值,使用
==
表示相等。但在Objective-C中,对象由指针表示,使用
==
比较两个对象时,只有当它们是同一个指针时才会返回
true
。为了解决这个问题,
NSObject
定义了
isEqual:
方法,用于比较对象的哈希值。不过,重写
isEqual:
并不容易,因为还需要重写
hash
方法,而且哈希计算比较复杂。许多类都有自己的相等性方法,如下表所示:
| 类 | 方法 |
| ---- | ---- |
| NSArray | isEqualToArray: |
| NSData | isEqualToData: |
| NSDate | isEqualToDate: |
| NSDictionary | isEqualToDictionary: |
| NSHashTable | isEqualToHashTable: |
| NSIndexSet | isEqualToIndexSet: |
| NSNumber | isEqualToNumber: |
| NSSet | isEqualToSet: |
| NSValue | isEqualToValue: |
在Xcode中,当输入
isEqual
时,按下
Esc
键可以打开自动完成列表,查看是否有更具体的补全选项。需要注意的是,不要使用
isEqualTo:
,它是AppleScript的一部分,不应直接使用。
8. 空类型
C语言使用零表示空,通常称为
NULL
。Objective-C除了
NULL
,还有
nil
、
Nil
和
NSNull
。
NULL
表示无,
nil
表示无对象,即
nil
的类型为
id
;大写的
Nil
表示无类,类型为
Class
。
NSNull
是一个单例对象,通过
[NSNull null]
获取,用于在不允许实际为空的集合(如
NSArray
)中作为空占位符。不同空类型的布尔运算结果如下表所示:
| 操作 | NULL/nil/Nil | NSNull |
| ---- | ---- | ---- |
| (BOOL)
_ | NO | YES |
| NULL / nil / Nil==
| YES | NO |
| NULL / nil / Nil isEqual:
_ | NO | NO |
| NSNull ==
| NO | YES |
| NSNull isEqual: ____ | NO | YES |
9. Objective-C内存管理
由于对象可能包含其他对象,多个对象可能包含同一个对象,因此存在释放他人仍在使用的对象的风险。当程序尝试访问不再拥有的内存时,会立即崩溃。为了降低这种风险,Objective-C使用引用计数系统。每个继承自
NSObject
的对象都有一个实例变量
retainCount
,对象通过
alloc
或
copy
创建时,
retainCount
初始化为1。对对象感兴趣的人发送
retain
消息,使
retainCount
加1;不再感兴趣时发送
release
消息。当
retainCount
达到0时,对象被释放。
然而,当创建一个对象并将其传递给他人时,会出现问题。为了解决这个问题,可以使用自动释放池。通过发送
autorelease
消息,对象会被放入自动释放池,自动释放池会在事件循环的底部发送
release
消息。如果在自动释放池释放对象之前,所有人都已经释放了该对象,那么最终结果是相同的。但如果在长循环中创建和释放大量对象,程序员可能需要手动创建内部自动释放池并进行排水操作。
10. 面向对象编程
与过程式语言(如C)相比,面向对象编程(如Objective-C)可以将代码组织成称为类的结构化单元。从C的角度来看,类就像结构体、类型定义和函数库的组合。对象是类的单个实例。
11. 声明接口
下面是一个简单的Objective-C类的头文件示例:
#import <Cocoa/Cocoa.h>
@interface BMPerson : NSObject {
NSString *name;
NSUInteger age;
}
+ (BMPerson *)personWithName:(NSString *)aName age:(NSUInteger)anAge;
- (NSString *)name;
- (void)setName:(NSString *)aName;
- (NSUInteger)age;
- (void)setAge:(NSUInteger)anAge;
@end
具体步骤如下:
1. 导入应用框架,为了方便,可以直接导入整个Cocoa框架:
#import <Cocoa/Cocoa.h>
。
#import
是
#include
的改进版本,只会在头文件未定义时添加。
2. 使用
@interface
声明接口,命名类为
BMPerson
,并使用
:
指定其父类为
NSObject
。
3. 在大括号内声明实例变量,如
NSString *name;
和
NSUInteger age;
。
4. 声明类方法和实例方法。类方法以
+
开头,实例方法以
-
开头。
5. 最后使用
@end
结束接口声明,并将文件保存为类名加
.h
扩展名的头文件。
12. 实现类
类的实现代码如下:
#import "BMPerson.h"
@implementation BMPerson
+ (BMPerson *)personWithName:(NSString *)aName age:(NSUInteger)anAge;
{
BMPerson *newPerson = [[self alloc] init];
[newPerson setName:aName];
[newPerson setAge:anAge];
return [newPerson autorelease];
}
- (id)init;
{
if (![super init])
return nil;
name = nil;
return self;
}
- (void)dealloc;
{
[name release];
name = nil;
[super dealloc];
}
- (NSString *)name;
{
return name;
}
- (void)setName:(NSString *)aName;
{
if (name == aName)
return;
[name release];
name = [aName retain];
}
- (NSUInteger)age;
{
return age;
}
- (void)setAge:(NSUInteger)anAge;
{
age = anAge;
}
@end
具体步骤如下:
1. 导入头文件:
#import "BMPerson.h"
。导入系统框架时使用尖括号,导入自己的头文件时使用引号。
2. 使用
@implementation
指令和类名打开实现部分。
3. 实现类方法和实例方法。类方法的声明与头文件中相同,实现时可以直接复制粘贴。在类方法中,使用
self
来引用类本身。
4.
init
方法用于对象的初始化,需要调用父类的
init
方法,并在失败时返回
nil
。
5.
dealloc
方法用于对象的释放,需要释放对象的实例变量,并将指针置为
nil
,以避免悬空指针。
13. 类方法
以
+ (BMPerson *)personWithName:(NSString *)aName age:(NSUInteger)anAge;
为例,其实现如下:
{
BMPerson *newPerson = [[self alloc] init];
[newPerson setName:aName];
[newPerson setAge:anAge];
return [newPerson autorelease];
}
在类方法中,使用
self
发送消息,因为每个方法在Objective-C中都会转换为函数,
self
是传递给函数的第一个“秘密”参数,指向调用该函数的对象。在类方法中,
self
指的是类本身。
工厂方法的优点是可以节省代码,尤其在频繁使用时,节省的代码量会很可观。例如,Foundation类(如
NSString
和
NSArray
)有很多工厂方法。
14. Init和Dealloc方法
init
方法是从父类
NSObject
继承而来的。在对象实例化时,先调用
alloc
方法分配内存,然后立即调用
init
方法进行初始化。如果子类重写了
init
方法,需要调用父类的
init
方法,并在失败时返回
nil
。
dealloc
方法用于在对象释放前清理资源。在
dealloc
方法中,需要释放对象的实例变量,并将指针置为
nil
,以避免悬空指针。例如:
- (void)dealloc;
{
[name release];
name = nil;
[super dealloc];
}
每个类通常有一个指定的初始化方法,所有其他初始化方法都应该引用该方法。子类应该调用指定的初始化方法,而不是直接调用
init
方法。不过,通常需要查看文档才能确定哪个是指定的初始化方法。
综上所述,Objective-C在内存管理、面向对象编程等方面有其独特的机制和方法。开发者需要深入理解这些概念,才能编写出高效、稳定的代码。
Objective-C 开发基础与实践
15. 方法调用机制与
self
和
super
的使用
在Objective-C中,方法调用是通过消息传递机制实现的。当发送一个消息给对象时,消息系统会查找相应的函数并调用它。在这个过程中,除了传递的参数外,还会传递一些“秘密”参数。其中,
self
就是这样一个参数,它指向调用该方法的对象。
在类方法中,
self
代表类本身。例如在
+ (BMPerson *)personWithName:(NSString *)aName age:(NSUInteger)anAge;
方法中,使用
[[self alloc] init]
来创建对象,这里的
self
指的是
BMPerson
类。而在实例方法中,
self
代表对象实例。比如在
- (NSString *)name;
方法中,
self
就是调用该方法的
BMPerson
对象实例。
super
则用于调用父类的方法。当子类重写了父类的方法,但又想在子类方法中调用父类的实现时,就可以使用
super
。例如在
- (id)init;
方法中,
if (![super init])
就是调用父类
NSObject
的
init
方法。
16. 工厂方法的优势与应用场景
工厂方法是Objective-C中一种常用的创建对象方式,它提供了一种便捷的对象创建和初始化途径。以
BMPerson
类的
+ (BMPerson *)personWithName:(NSString *)aName age:(NSUInteger)anAge;
方法为例,它封装了对象的创建和初始化过程。
工厂方法的优势主要体现在以下几个方面:
-
代码简洁
:减少了重复的对象创建和初始化代码。例如,如果需要多次创建
BMPerson
对象,使用工厂方法可以避免每次都写
[[BMPerson alloc] init]
、设置属性等代码。
-
提高可维护性
:如果对象的创建和初始化逻辑发生变化,只需要修改工厂方法的实现,而不需要在每个创建对象的地方进行修改。
-
支持单例优化
:在某些情况下,工厂方法可以实现单例模式,确保一个类只有一个实例。
工厂方法适用于以下场景:
-
频繁创建对象
:当需要频繁创建某个类的对象时,使用工厂方法可以节省大量代码。
-
对象创建逻辑复杂
:如果对象的创建和初始化过程比较复杂,使用工厂方法可以将这些逻辑封装起来,使代码更加清晰。
17. 自动释放池的使用与管理
自动释放池是Objective-C中用于管理对象生命周期的重要机制。在前面提到的内存管理中,当创建一个对象并将其传递给他人时,使用自动释放池可以解决对象释放的问题。
自动释放池的使用步骤如下:
1.
创建自动释放池
:可以使用
NSAutoreleasePool
类来创建自动释放池。例如:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-
将对象放入自动释放池
:通过调用对象的
autorelease方法,将对象放入自动释放池。例如:
BMPerson *person = [[[BMPerson alloc] init] autorelease];
-
释放自动释放池
:在自动释放池的生命周期结束时,调用
drain或release方法释放自动释放池。例如:
[pool drain];
在长循环中创建和释放大量对象时,手动创建内部自动释放池并进行排水操作是很有必要的。例如:
for (int i = 0; i < 10000; i++) {
NSAutoreleasePool *innerPool = [[NSAutoreleasePool alloc] init];
// 创建和使用对象
BMPerson *person = [[[BMPerson alloc] init] autorelease];
[innerPool drain];
}
18. 相等性比较的深入理解
在Objective-C中,对象的相等性比较是一个需要注意的问题。使用
==
比较两个对象时,比较的是对象的指针是否相同,而不是对象的内容是否相同。为了比较对象的内容,
NSObject
定义了
isEqual:
方法。
不同类有自己的相等性方法,如下表所示:
| 类 | 方法 |
| ---- | ---- |
| NSArray | isEqualToArray: |
| NSData | isEqualToData: |
| NSDate | isEqualToDate: |
| NSDictionary | isEqualToDictionary: |
| NSHashTable | isEqualToHashTable: |
| NSIndexSet | isEqualToIndexSet: |
| NSNumber | isEqualToNumber: |
| NSSet | isEqualToSet: |
| NSValue | isEqualToValue: |
重写
isEqual:
方法时,需要同时重写
hash
方法,因为
isEqual:
方法通常是基于对象的哈希值进行比较的。但是,哈希计算比较复杂,所以在很多情况下,通过比较对象的一些属性(如字符串的长度、数组的元素数量等)来快速判断对象是否不相等,比生成哈希值更加高效。
19. 空类型的区别与应用
Objective-C中有多种空类型,包括
NULL
、
nil
、
Nil
和
NSNull
,它们的含义和应用场景有所不同。
-
NULL
:在C语言中,
NULL
用于表示空指针,在Objective-C中也保留了这个概念,通常用于表示没有指向任何对象的指针。
-
nil
:表示无对象,其类型为
id
。在Objective-C中,如果向
nil
发送消息,不会引发崩溃,而是返回
nil
。例如:
NSString *str = nil;
NSString *result = [str uppercaseString]; // result 为 nil
-
Nil
:表示无类,其类型为
Class。通常用于表示没有指向任何类的指针。 -
NSNull
:是一个单例对象,用于在集合(如
NSArray、NSDictionary等)中表示空值。因为集合中不允许直接存储nil,所以可以使用NSNull作为占位符。例如:
NSArray *array = [NSArray arrayWithObjects:@"one", [NSNull null], @"three", nil];
不同空类型的布尔运算结果如下表所示:
| 操作 | NULL/nil/Nil | NSNull |
| ---- | ---- | ---- |
| (BOOL)
_ | NO | YES |
| NULL / nil / Nil==
| YES | NO |
| NULL / nil / Nil isEqual:
_ | NO | NO |
| NSNull ==
| NO | YES |
| NSNull isEqual: ____ | NO | YES |
20. 面向对象编程的优势与实践
面向对象编程(OOP)是Objective-C的核心编程范式,与过程式语言(如C)相比,它具有以下优势:
-
代码组织性强
:将代码组织成类和对象,每个类负责特定的功能,使代码结构更加清晰,易于理解和维护。
-
可重用性高
:可以通过继承和组合的方式重用已有的类和代码,减少代码重复。
-
可扩展性好
:可以通过继承和多态的方式扩展类的功能,而不需要修改现有的代码。
在实践中,使用Objective-C进行面向对象编程时,需要注意以下几点:
-
合理设计类
:类的设计应该遵循单一职责原则,即一个类只负责一个特定的功能。
-
正确使用继承和多态
:继承可以实现代码的重用,多态可以提高代码的灵活性。
-
注意内存管理
:在使用对象时,需要正确管理对象的生命周期,避免内存泄漏和悬空指针。
21. 总结与建议
通过对Objective-C的基础概念、内存管理、面向对象编程等方面的学习,我们可以看到Objective-C是一门功能强大、灵活的编程语言。在开发过程中,需要注意以下几点:
-
深入理解内存管理
:掌握引用计数和自动释放池的使用,避免内存泄漏和悬空指针。
-
合理使用面向对象编程
:设计合理的类和对象,充分发挥继承、多态等特性的优势。
-
注意代码规范
:遵循Objective-C的代码规范,提高代码的可读性和可维护性。
希望以上内容对大家学习和使用Objective-C有所帮助,在实际开发中不断实践和探索,提高自己的编程水平。
通过以上内容,我们对Objective-C的各个方面有了更深入的了解,从基础概念到具体的编程实践,每个环节都有其重要性。在实际开发中,我们需要综合运用这些知识,编写出高效、稳定的代码。
下面是一个简单的mermaid流程图,展示了对象创建和初始化的过程:
graph TD;
A[开始] --> B[发送alloc消息给类];
B --> C[返回未初始化的对象];
C --> D[发送init消息给对象];
D --> E{父类init是否成功};
E -- 是 --> F[初始化子类实例变量];
E -- 否 --> G[返回nil];
F --> H[返回初始化后的对象];
H --> I[结束];
G --> I;
这个流程图清晰地展示了对象创建和初始化的步骤,以及在初始化过程中可能出现的情况。在实际编程中,我们需要严格按照这个流程来创建和初始化对象,确保对象的正常使用。
超级会员免费看
2552

被折叠的 条评论
为什么被折叠?



