51、Objective-C编程深入解析

Objective-C编程深入解析

1. 实例方法

在面向对象编程中,核心是将责任转移到对象上。我们可以要求对象执行的任务就是它们的实例方法。最常见的实例方法用于直接访问和修改对象的实例变量,技术上称为访问器和修改器,Apple更常用的说法是setter和getter。

1.1 Getter和Setter的定义

  • Getter :在Objective - C中,getter的名称和返回类型与它访问的实例变量相同,并且不接受任何参数。例如:
- (NSString *)name;
{
    return name;
}
  • Setter :setter在名称前加上 set 前缀,接受一个与实例变量类型相同的参数,并且不返回任何值。由于内存管理的原因,setter稍微复杂一些,示例代码如下:
- (void)setName:(NSString *)aName;
{
    if (name == aName)
        return;
    [name release];
    name = [aName retain];
}

1.2 Setter参数命名注意事项

Setter的参数不能与实例变量同名,因为如果同名会导致判断条件 if (name == name) 这样的混淆情况。而在Java中,可以使用 this 关键字来区分,但Objective - C没有这个特性。

1.3 封装的重要性

虽然直接访问变量看起来更简单,但这违反了封装原则。封装是面向对象编程的重要部分,它要求隐藏实现细节,这样可以防止对象之间的纠缠,保护程序员免受实现更改的影响。例如,对于 age 的setter:

// 简单版本
- (void)setAge:(NSUInteger)anAge;
{
    age = anAge;
}
// 增加验证逻辑版本
- (void)setAge:(NSUInteger)anAge;
{
    if (anAge > 0 && anAge < 100)
        age = anAge;
}

如果遵循封装原则,只需修改setter方法;如果直接设置变量,则需要进行大量的实现工作。

2. 协议

Objective - C采用单继承模型,为了克服单继承的限制并增加灵活性,类可以通过遵循任意数量的协议来继承额外的接口。

2.1 协议的定义和使用

协议类似于类,但不包含变量或提供实现,只是定义一个接口,遵循该协议的类需要实现这些接口。例如,Foundation定义了 NSCopying 协议,如果 BMPerson 类实现了 copy 方法,可以在类声明中添加该协议:

@interface BMPerson : NSObject <NSCopying>

实际上,调用 - (id)copy 时,是调用 - (id)copyWithZone:(NSZone *)zone ,遵循 NSCopying 协议需要实现 copyWithZone: 方法。

2.2 自定义协议

可以自定义协议,例如定义一个提供唯一ID的通用接口:

@protocol BMIdentifying
- (NSString *)uniqueIDString;
@end

如果 BMPerson 实现了 uniqueIDString ,可以在协议列表中添加:

@interface BMPerson : NSObject <NSCopying, BMIdentifying>

还可以在方法签名中使用协议作为类型:

- (void)addIdentifyingObject:(id <BMIdentifying>)object;

调用该方法时,可以进行协议类型的转换:

[listOfIdentifyingObjects addIdentifyingObject:(id <BMIdentifying>)object];

2.3 Cocoa中协议的应用

Cocoa使用正式和非正式协议实现了多种与类无关的模式,如下表所示:
| 模式 | 说明 |
| ---- | ---- |
| 插件 | 通过创建插件架构,程序员可以将应用程序开放给第三方开发,使用协议为插件开发者提供设计自由 |
| 委托和数据源 | 对象的行为和内容需要可配置,委托和数据源模式将决策留给另一个对象,避免大量使用子类化 |
| 通知 | 在某些操作前后调用方法,程序员可以根据通知类的操作同步其他对象的操作 |
| 快速枚举 | Objective - C 2.0引入统一、简洁且高度优化的 for...in 循环,集合通过遵循 NSFastEnumeration 协议实现快速枚举 |
| 键值编码和键值观察 | 除了调用显式方法,还可以通过名称访问对象的属性,并观察属性的变化 |
| 脚本化 | 应用程序需要实现某些额外方法才能进行脚本化,还有一些可选方法处理对象使用和脚本使用之间的不匹配 |
| 可访问性 | 遵循可访问性协议,应用程序不仅可供残疾用户使用,还可参与用户界面脚本编写 |
| 动画 | Cocoa的Core Animation包装通过协议实现,减少对现有框架的更改,扩展开发者自定义类的可用性 |
| 拼写检查 | 通过实现拼写协议,开发者可以将系统级的拼写和语法检查功能集成到自己的应用中 |
| 锁定 | 锁定协议为线程安全类定义标准接口,用于锁定和解锁 |
| 粘贴板操作 | 正式和非正式协议定义了粘贴板操作的各个部分,包括提供数据、接收数据和数据格式 |

在Leopard之前,所有协议方法都是必需的,可选接口通过非正式协议(实际上是 NSObject 的类别)实现。Objective - C 2.0引入了 @optional @required 编译器指令,消除了对非正式协议的需求。

3. Objective - C动态运行时

Objective - C具有很强的动态性,多态性使得不同对象可以以不同方式响应相同的消息。与Java和C++等语言不同,Objective - C在运行时才真正发挥作用,大部分代码在执行前不会被锁定,这赋予了它强大的能力。

3.1 消息传递机制

Objective - C不寻常的消息传递语法是其动态性的核心。例如:

[myObject setColorWithRed:0.4 green:0.3 blue:0.0 alpha:1.0];

myObject 是接收者, setColorWithRed:0.4 green:0.3 blue:0.0 alpha:1.0 是消息。运行时会将消息拆分为方法签名和参数数组,每个方法签名会解析为一个选择器( SEL 类型),运行时动态地将选择器与方法实现( IMP 类型)配对。

3.2 方法名说明

方法的名称是方法声明,包括冒号,但不包括参数名称或类型。例如, setColorWithRed:0.4 green:0.3 blue:0.0 alpha:1.0 调用的方法名称是 setColorWithRed:green:blue:alpha:

下面是消息传递的流程mermaid图:

graph LR
    A[发送消息] --> B[运行时拆分消息]
    B --> C[解析方法签名为选择器]
    C --> D[动态配对选择器和方法实现]
    D --> E[执行方法]

4. 类别

在开发中,即使库的设计者考虑到了所有可能的方法,这也不是一个好主意,因为会导致类变得难以使用和性能下降。虽然可以通过子类化来扩展现有类,但有时这会带来很多问题。

4.1 子类化的问题

NSString 为例,它提供了 stringByAppendingString 方法,但没有 stringByPrependingString 方法。尝试子类化 NSString 创建 BMString 类:

#import <Cocoa/Cocoa.h>
@interface BMString : NSString
- (NSString *)stringByPrependingString:(NSString *)aString;
@end

@implementation BMString
- (NSString *)stringByPrependingString:(NSString *)aString;
{
    return [aString stringByAppendingString:self];
}
@end

但子类化 NSString 存在很多隐藏问题,如继承不完美, NSString 实际上是一个类簇的接口等,并且使用自定义子类时需要时刻记住使用它。

4.2 类别介绍

类别是在现有类上定义的额外方法,作为一种组织工具,它可以让代码的组织更符合开发者的需求。例如,可以将一个非常大的类拆分成多个文件,为不同功能区域使用单独的头文件。同时,也可以一次性为多个类添加功能,新功能的使用者只需导入新的头文件,旧类可以忽略它。

4.3 类别定义和使用

使用类别为 NSString 添加 stringByPrependingString 方法的示例如下:

#import <Cocoa/Cocoa.h>
@interface NSString (BMString)
- (NSString *)stringByPrependingString:(NSString *)aString;
@end

@implementation NSString (BMString)
- (NSString *)stringByPrependingString:(NSString *)aString;
{
    return [aString stringByAppendingString:self];
}
@end

使用时:

NSString *greeting = @", world!";
greeting = [greeting stringByPrependingString:@"Hello"];
NSLog(greeting);

与子类不同,类别可以访问 NSString 类的所有内容,包括私有成员。

类别和协议的对比列表如下:
| 对比项 | 类别 | 协议 |
| ---- | ---- | ---- |
| 功能 | 为现有类添加实现和方法 | 定义接口,让类实现 |
| 灵活性 | 可在已编译的类上添加功能 | 类遵循协议实现接口 |
| 与继承关系 | 不依赖继承 | 与继承共同提供扩展能力 |

5. 方法交换

虽然类别可以为类添加方法,但继承模型提供了类别没有的功能,即子类可以覆盖父类的实现。Objective - C允许我们交换任何类的实现,这就是方法交换(swizzling)。

5.1 方法交换示例

NSXMLNode description 方法为例,它输出的XML没有空格,难以阅读。我们可以创建一个类别,定义一个新方法 prettyDescription ,并将其实现与 description 方法的实现进行交换:

#import <Cocoa/Cocoa.h>
#import <objc/runtime.h>
@interface NSXMLNode (BMPrettyDescriptions)
- (NSString *)prettyDescription;
@end

@implementation NSXMLNode (BMPrettyDescriptions)
+ (void)load;
{
    Method oldMethod = class_getInstanceMethod(self, @selector(description));
    Method newMethod = class_getInstanceMethod(self, @selector(prettyDescription));
    method_exchangeImplementations(oldMethod, newMethod);
}

- (NSString *)prettyDescription;
{
    return [self XMLStringWithOptions:NSXMLNodePrettyPrint];
}
@end

使用交换后的方法:

NSXMLNode *xmlNode;
// ... load the XML document
NSLog(@"Current node: %@", xmlNode);

这样输出的XML就会有良好的格式。

方法交换的流程mermaid图如下:

graph LR
    A[定义新方法] --> B[获取旧方法和新方法]
    B --> C[交换方法实现]
    C --> D[调用原方法时执行新方法实现]

通过以上这些特性,Objective - C在编程中展现出了强大的灵活性和扩展性,开发者可以根据不同的需求选择合适的方式来实现功能。

6. 内存管理与实例方法

在Objective - C中,内存管理是一个重要的话题,特别是在处理实例方法时。前面提到的setter方法,由于涉及到内存管理,其实现会比getter方法复杂一些。

6.1 Setter内存管理分析

回顾之前的 setName 方法:

- (void)setName:(NSString *)aName;
{
    if (name == aName)
        return;
    [name release];
    name = [aName retain];
}

这里的内存管理步骤可以详细解释如下:
1. 检查是否为同一对象 if (name == aName) 检查新传入的对象和当前对象是否相同,如果相同则直接返回,避免不必要的内存操作。
2. 释放旧对象 [name release] 释放当前对象的引用,减少其引用计数。如果引用计数降为0,则对象会被销毁。
3. 保留新对象 name = [aName retain] 将新对象的引用赋值给实例变量,并增加新对象的引用计数,确保对象不会被意外销毁。

6.2 内存管理与封装的关系

内存管理和封装是紧密相关的。如果不使用setter和getter方法,直接访问实例变量,可能会导致内存管理混乱。例如,如果直接修改实例变量而不进行适当的内存管理,可能会导致内存泄漏或野指针问题。而使用setter和getter方法可以将内存管理的逻辑封装在方法内部,提高代码的安全性和可维护性。

下面是一个关于内存管理操作的步骤列表:
| 步骤 | 操作 | 说明 |
| ---- | ---- | ---- |
| 1 | 检查对象是否相同 | 避免不必要的内存操作 |
| 2 | 释放旧对象 | 减少旧对象的引用计数 |
| 3 | 保留新对象 | 增加新对象的引用计数 |

7. 协议的深入应用

协议在Objective - C中有着广泛的应用,除了前面提到的各种模式,还有一些实际的使用场景和注意事项。

7.1 协议的组合使用

在实际开发中,一个类可以遵循多个协议,以实现多种功能。例如, BMPerson 类可以同时遵循 NSCopying BMIdentifying 协议:

@interface BMPerson : NSObject <NSCopying, BMIdentifying>

这样, BMPerson 类就需要实现 copyWithZone: uniqueIDString 方法。

7.2 协议作为方法参数类型

使用协议作为方法参数类型可以提高代码的灵活性和可扩展性。例如:

- (void)addIdentifyingObject:(id <BMIdentifying>)object;

这个方法接受一个遵循 BMIdentifying 协议的对象作为参数,调用时可以传入任何实现了该协议的对象:

[listOfIdentifyingObjects addIdentifyingObject:(id <BMIdentifying>)object];

7.3 协议的可选方法

在Objective - C 2.0中引入了 @optional @required 编译器指令,使得协议方法可以分为可选和必需的。例如:

@protocol BMOptionalIdentifying
@required
- (NSString *)requiredIDString;
@optional
- (NSString *)optionalIDString;
@end

遵循该协议的类必须实现 requiredIDString 方法,而 optionalIDString 方法可以选择实现。

以下是一个协议使用的流程mermaid图:

graph LR
    A[定义协议] --> B[类遵循协议]
    B --> C[实现必需方法]
    C --> D{是否有可选方法}
    D -- 是 --> E[选择实现可选方法]
    D -- 否 --> F[完成协议实现]
    E --> F

8. 类别与代码组织

类别在代码组织方面有着独特的优势,它可以让代码更加模块化和易于维护。

8.1 大类别拆分

对于一个非常大的类,可以使用类别将其拆分成多个文件,每个文件负责不同的功能。例如,一个 LargeClass 可以拆分成以下几个类别:

// LargeClass+Feature1.h
@interface LargeClass (Feature1)
- (void)feature1Method;
@end

// LargeClass+Feature1.m
@implementation LargeClass (Feature1)
- (void)feature1Method {
    // 实现代码
}
@end

// LargeClass+Feature2.h
@interface LargeClass (Feature2)
- (void)feature2Method;
@end

// LargeClass+Feature2.m
@implementation LargeClass (Feature2)
- (void)feature2Method {
    // 实现代码
}
@end

这样,不同功能的代码可以分开管理,提高代码的可读性和可维护性。

8.2 多类功能添加

类别还可以一次性为多个类添加功能。例如,为 NSString NSArray 添加一个通用的日志方法:

// LoggingCategory.h
@interface NSString (Logging)
- (void)logString;
@end

@interface NSArray (Logging)
- (void)logArray;
@end

// LoggingCategory.m
@implementation NSString (Logging)
- (void)logString {
    NSLog(@"String: %@", self);
}
@end

@implementation NSArray (Logging)
- (void)logArray {
    NSLog(@"Array: %@", self);
}
@end

使用时,只需导入 LoggingCategory.h 头文件, NSString NSArray 对象就可以调用相应的日志方法。

以下是类别使用的好处列表:
| 好处 | 说明 |
| ---- | ---- |
| 代码模块化 | 将大类拆分成多个文件,便于管理 |
| 功能扩展 | 为现有类添加新功能 |
| 代码复用 | 一次性为多个类添加相同功能 |

9. 方法交换的注意事项

方法交换是一个强大的技术,但也需要谨慎使用,否则可能会导致一些难以调试的问题。

9.1 方法交换的副作用

方法交换会改变原方法的行为,可能会影响到其他依赖原方法的代码。例如,如果在一个广泛使用的类上进行方法交换,可能会导致其他部分的代码出现意外的结果。

9.2 线程安全问题

方法交换操作应该在合适的时机进行,通常在类加载时(如 + (void)load 方法)进行。如果在多线程环境下进行方法交换,可能会导致线程安全问题。

9.3 备份原方法

在进行方法交换时,建议备份原方法的实现,以便在需要时可以恢复。例如:

#import <Cocoa/Cocoa.h>
#import <objc/runtime.h>

@interface NSXMLNode (BMPrettyDescriptions)
- (NSString *)prettyDescription;
@end

@implementation NSXMLNode (BMPrettyDescriptions)
+ (void)load;
{
    Method oldMethod = class_getInstanceMethod(self, @selector(description));
    Method newMethod = class_getInstanceMethod(self, @selector(prettyDescription));
    IMP originalIMP = method_getImplementation(oldMethod);
    method_exchangeImplementations(oldMethod, newMethod);
    // 可以保存originalIMP用于恢复
}

- (NSString *)prettyDescription;
{
    return [self XMLStringWithOptions:NSXMLNodePrettyPrint];
}
@end

下面是一个方法交换注意事项的表格:
| 注意事项 | 说明 |
| ---- | ---- |
| 副作用 | 可能影响其他依赖原方法的代码 |
| 线程安全 | 在合适的时机进行交换,避免多线程问题 |
| 备份原方法 | 保存原方法实现,以便恢复 |

通过对Objective - C中实例方法、协议、动态运行时、类别和方法交换等特性的深入了解和合理应用,开发者可以更加灵活地编写代码,提高代码的质量和可维护性。同时,在使用这些特性时,需要注意相关的细节和潜在的问题,确保代码的稳定性和可靠性。

【无人机】基于改进粒子群算法的无人机路径规划研究[和遗传算法、粒子群算法进行比较](Matlab代码实现)内容概要:本文围绕基于改进粒子群算法的无人机路径规划展开研究,重点探讨了在复杂环境中利用改进粒子群算法(PSO)实现无人机三维路径规划的方法,并将其与遗传算法(GA)、标准粒子群算法等传统优化算法进行对比分析。研究内容涵盖路径规划的多目标优化、避障策略、航路点约束以及算法收敛性和寻优能力的评估,所有实验均通过Matlab代码实现,提供了完整的仿真验证流程。文章还提到了多种智能优化算法在无人机路径规划中的应用比较,突出了改进PSO在收敛速度和全局寻优方面的优势。; 适合人群:具备一定Matlab编程基础和优化算法知识的研究生、科研人员及从事无人机路径规划、智能优化算法研究的相关技术人员。; 使用场景及目标:①用于无人机在复杂地形或动态环境下的三维路径规划仿真研究;②比较不同智能优化算法(如PSO、GA、蚁群算法、RRT等)在路径规划中的性能差异;③为多目标优化问题提供算法选型和改进思路。; 阅读建议:建议读者结合文中提供的Matlab代码进行实践操作,重点关注算法的参数设置、适应度函数设计及路径约束处理方式,同时可参考文中提到的多种算法对比思路,拓展到其他智能优化算法的研究与改进中。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值