50、Objective-C 开发基础与实践

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];
  1. 将对象放入自动释放池 :通过调用对象的 autorelease 方法,将对象放入自动释放池。例如:
BMPerson *person = [[[BMPerson alloc] init] autorelease];
  1. 释放自动释放池 :在自动释放池的生命周期结束时,调用 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;

这个流程图清晰地展示了对象创建和初始化的步骤,以及在初始化过程中可能出现的情况。在实际编程中,我们需要严格按照这个流程来创建和初始化对象,确保对象的正常使用。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值