ObjC高级语法
# 03.ObjC高级语法
# 目录介绍
- 3.1 枚举的设计和使用
- 3.1.1 枚举的定义
- 3.1.2 枚举的使用
- 3.1.3 枚举的场景
- 3.1.4 枚举定义值
- 3.2 协议与委托
- 3.2.1 协议的作用
- 3.2.2 协议的声明
- 3.2.3 协议使用步骤
- 3.2.4 协议实际案例
- 3.2.5 协议使用场景
- 3.2.6 协议的判断
- 3.3 理解动态类型设计
- 3.3.1 理解动态类型
- 3.3.2 理解id类型
- 3.3.3 动态类型检查
- 3.3.4 动态类型转化
- 3.3.5 动态类型调用
- 3.3.6 动态类型弊端
- 3.4 类别如何使用
- 3.4.1 理解类别机制
- 3.4.2 类别使用场景
- 3.4.3 类别使用案例
- 3.5 Blocks设计
- 3.5.1 Block是什么
- 3.5.2 Block使用
- 3.6 KVO(键值观察)
- 3.6.1 添加观察者
- 3.6.2 回调
- 3.7 多线程使用
- 3.7.1 线程基本概念
- 3.7.2 使用NSThread
- 3.7.3 NSOperation
- 3.7.4 GCD多线程
- 3.7.5 线程安全
# 3.1 枚举的设计和使用
# 3.1.1 枚举的定义
定义枚举类型:使用typedef enum关键字来定义枚举类型。在定义时,可以列出枚举的所有可能取值。
typedef enum {
OptionA,
OptionB,
OptionC
} MyEnum;
2
3
4
5
枚举常量的默认值:如果没有为枚举常量指定具体的值,它们将按照顺序从0开始自动分配值。第一个常量的默认值为0,后续常量的值依次递增。
# 3.1.2 枚举的使用
使用枚举常量:定义枚举后,可以使用枚举常量来表示不同的取值。枚举常量的命名通常使用大写字母开头,以便与其他常量区分。
MyEnum myValue = OptionB;
一篇文章学会typedef enum、NS_ENUM、NS_OPTIONS和移位1 << 0:https://www.jianshu.com/p/dc26d719cc03
# 3.1.3 枚举的场景
应该用枚举来表示状态机的状态、传递给方法的选项以及状态码等值,给这些值起个易懂的名字。
用NS_ENUM与NS_OPRIONS宏来定义枚举类型, 并指明其底层数据类型.这样做可以确保枚举是用开发者所选的底层数据类型实现出来的,而不会采用编译器所选的类型。
# 3.1.4 枚举定义值
在 Objective-C 中,枚举(Enum)可以用来定义一组相关的命名常量。每个枚举常量都有一个与之相关联的整数值。
// 定义一个枚举类型
typedef NS_ENUM(NSInteger, Weekday) {
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6,
Sunday = 7
};
2
3
4
5
6
7
8
9
10
# 3.2 协议设计和使用
# 3.2.1 协议的作用
协议(Protocol)是一种定义接口的机制,用于声明一组方法、属性或其他成员,以便其他类遵循(adopt)并实现(implement)。
协议的作用是定义了一套规范,使得不同的类可以遵循相同的接口,从而实现代码的重用和解耦。
# 3.2.2 协议的声明
//@protocol 是用于声明协议(Protocol)的关键字。协议定义了一组方法,用于描述类或对象应该遵循的行为和功能。
@protocol MyProtocol
//协议中的方法声明与类中的方法声明类似,使用以下语法:- (返回类型)方法名;
- (void) doSomething;
- (NSString *) getName;
//@required
@optional
- (NSString *) getAge;
@end
2
3
4
5
6
7
8
9
10
11
12
13
@required与@optional
- @required修饰的方法的声明,就必须实现,编译器会警告,默认属性
- @optional修饰的方法的声明,可以实现或者不实现,不实现编译器也不会警告
# 3.2.3 协议使用步骤
在实践中,可以按照以下步骤使用协议:
- 定义协议:使用@protocol关键字定义协议,并在其中声明需要的方法、属性等。
- 遵循协议:在类的声明中使用
来遵循特定的协议。 - 实现协议方法:在类的实现中,根据协议的要求实现相应的方法。
- 使用协议:通过协议类型的指针或引用,可以调用协议中定义的方法,而不需要关心具体的类。
# 3.2.4 协议实际案例
// 遵循协议的类
@interface MyClass : NSObject <MyProtocol>
@end
@implementation MyClass
- (void)doSomething {
NSLog(@"Doing something...");
}
@end
// 使用协议
MyClass *myObject = [[MyClass alloc] init];
id<MyProtocol> objectWithProtocol = myObject;
[objectWithProtocol doSomething];
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3.2.5 协议使用场景
协议(Protocol)具有广泛的使用场景,以下是一些常见的协议使用场景:
- 委托模式(Delegate Pattern):协议常用于实现委托模式,其中一个类(委托方)将某些任务委托给另一个类(委托对象)来处理。
- 数据源模式(Data Source Pattern):协议可以用于实现数据源模式,其中一个类(数据源)提供数据给另一个类(数据使用者)。
- 视图控制器之间的通信:协议可以用于视图控制器之间的通信,例如一个视图控制器需要将数据传递给另一个视图控制器。
- 网络请求回调:协议可以用于处理网络请求的回调,例如在网络请求完成后通知调用方。
# 3.2.6 协议的判断
如何判断类是否实现了某个协议?要判断一个类是否实现了某个协议,可以使用conformsToProtocol:方法。这个方法可以检查一个类是否遵循了指定的协议。
// 判断类是否实现了某个协议
if ([MyClass conformsToProtocol:@protocol(MyProtocol)]) {
NSLog(@"MyClass 实现了 MyProtocol 协议");
} else {
NSLog(@"MyClass 没有实现 MyProtocol 协议");
}
2
3
4
5
6
如果你想检查一个对象是否实现了某个协议,可以使用NSObject类的conformsToProtocol:方法。示例如下:
// 判断对象是否实现了某个协议
if ([myObject conformsToProtocol:@protocol(MyProtocol)]) {
NSLog(@"myObject 实现了 MyProtocol 协议");
} else {
NSLog(@"myObject 没有实现 MyProtocol 协议");
}
2
3
4
5
6
# 3.3 理解动态类型设计
# 3.3.1 理解动态类型
动态类型是指在运行时确定对象的类型,而不是在编译时确定。Objective-C是一种动态类型语言,它允许在运行时进行类型检查和类型转换。
# 3.3.2 理解id类型
id类型:id是Objective-C中的一种特殊类型,表示一个未知类型的对象。
可以将任何对象赋值给id类型的变量,而不需要进行编译时的类型检查。在运行时,可以使用动态类型检查和消息传递来操作id类型的对象。
//1.未知类型的对象:当你不确定一个对象的具体类型时,可以使用id类型来存储该对象的引用。
id unknownObject = someObject;
//2.动态类型检查:通过将对象赋值给id类型的变量,可以使用动态类型检查方法来判断对象的具体类型。
if ([unknownObject isKindOfClass:[NSString class]]) {
// 对象是NSString类型
} else if ([unknownObject isKindOfClass:[NSNumber class]]) {
// 对象是NSNumber类型
}
//3.动态方法调用:使用id类型可以在运行时动态调用对象的方法,而不需要在编译时知道方法的具体实现。
id someObject = [SomeClass new];
[someObject performSelector:@selector(someMethod)];
//泛型集合:在Objective-C中,可以使用id类型来实现泛型集合,以存储不同类型的对象。
NSArray<id> *genericArray = @[object1, object2, object3];
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
要注意的是,由于id类型是一种未知类型,编译器无法提供类型检查和自动补全等功能。因此,在使用id类型时,需要确保在运行时进行适当的类型检查,以避免潜在的类型错误。
id 和 void * 的区别?id 表示一个指向 Objective-C 对象的指针,而 void * 可以表示为任何指针。
另外,使用 id 声明对象时编辑器不会报错,只有在运行时才会提示错误,所以,推荐使用 NSObject * 而不是直接使用 id 创建一个代表任何类的对象。
# 3.3.3 动态类型检查
动态类型检查:Objective-C提供了一些方法来进行动态类型检查,例如isKindOfClass:和isMemberOfClass:。
这些方法可以在运行时检查对象的类型,判断对象是否属于特定的类或其子类。
//该方法用于检查一个对象是否是指定类或其子类的实例。它会遍历整个继承链,包括父类和祖先类。
if ([someObject isKindOfClass:[NSString class]]) {
// 对象是NSString类型或其子类的实例
}
//该方法用于检查一个对象是否是指定类的实例,而不包括其子类。
if ([someObject isMemberOfClass:[NSString class]]) {
// 对象是NSString类型的实例,不包括其子类
}
2
3
4
5
6
7
8
9
如果需要检查对象是否遵循特定的协议,可以使用conformsToProtocol:方法。
if ([someObject conformsToProtocol:@protocol(MyProtocol)]) {
// 对象遵循MyProtocol协议
}
2
3
# 3.3.4 动态类型转化
动态类型转换:Objective-C提供了一些方法来进行动态类型转换,例如isKindOfClass:和respondsToSelector:。
这些方法可以在运行时检查对象的类型,并根据需要将对象转换为其他类型。
//可以使用respondsToSelector:方法来检查对象是否实现了特定的方法。
if ([someObject respondsToSelector:@selector(someMethod)]) {
// 对象实现了someMethod方法
}
2
3
4
# 3.3.5 动态类型调用
1.performSelector:动态调用无参数方法。
[obj performSelector:@selector(doSomething)];
2.performSelector:withObject:动态调用带一个参数的方法。
[obj performSelector:@selector(doSomethingWith:) withObject:arg];
3.performSelector:withObject:withObject:动态调用带两个参数的方法。
[obj performSelector:@selector(doSomethingWith:with:) withObject:arg1 withObject:arg2];
4.NSInvocation,用于动态调用任意参数的方法。
SEL selector = @selector(doSomethingWith:with:);
NSMethodSignature *signature = [obj methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:obj];
[invocation setSelector:selector];
[invocation setArgument:&arg1 atIndex:2];
[invocation setArgument:&arg2 atIndex:3];
[invocation invoke];
2
3
4
5
6
7
8
动态类型调用的注意事项
- 性能开销:动态类型调用比静态类型调用慢,因为需要在运行时查找方法。
- 安全性:动态调用可能导致运行时错误(如方法不存在),需要做好检查。
- 可读性:过度使用动态类型调用会降低代码的可读性和可维护性。
# 3.3.6 动态类型弊端
动态类型的特性使得Objective-C具有灵活性和可扩展性。它允许在运行时根据实际情况进行类型检查和类型转换,以适应不同的需求和场景。
但需要注意,过度使用动态类型可能会导致代码的可读性和可维护性下降,因此需要谨慎使用。
# 3.4 类别如何使用
# 3.4.1 理解类别机制
类别(Category)是一种机制,用于在不修改原始类的情况下扩展类的功能。类别允许开发者向现有类添加新的方法,以及修改或重写现有方法的实现。
# 3.4.2 类别使用场景
1.扩展类的功能:类别允许在不创建子类或修改原始类的情况下,向现有类添加新的方法。这对于向系统类或第三方库的类添加自定义功能非常有用。
2.方法重写:类别可以重写原始类中的方法实现。当类别中的方法与原始类中的方法同名时,类别中的方法会覆盖原始类中的方法。这使得可以通过类别来修改现有方法的行为。
3.避免命名冲突:在使用类别时,需要注意避免与其他类别或原始类中的方法产生命名冲突。最好使用特定的前缀或命名约定来命名类别中的方法,以确保命名的唯一性。
# 3.4.3 类别使用案例
下面是一个简单的示例,演示了如何使用类别来扩展NSString类的功能:
// 假设有一个名为 NSString+CustomMethods 的类别文件
@interface NSString (CustomMethods)
//反转字符串
- (NSString *)reversedString;
@end
@implementation NSString (CustomMethods)
- (NSString *)reversedString {
NSMutableString *reversedString = [NSMutableString string];
for (NSInteger i = self.length - 1; i >= 0; i--) {
[reversedString appendString:[NSString stringWithFormat:@"%C", [self characterAtIndex:i]]];
}
return reversedString;
}
@end
int main(int argc, char * argv[]) {
@autoreleasepool {
NSString *originalString = @"Hello, World!";
NSString *reversedString = [originalString reversedString];
NSLog(@"%@", reversedString); // 输出:!dlroW ,olleH
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 3.5 Blocks设计
# 3.5.1 Block是什么
Blocks 是 Objective-C 中一种特殊的对象,它可以直接执行一段代码,类似其它语言中的 lambda 表达式。
# 3.5.2 Block使用
Block 字面量,我们可以使用 ^ 符创建一个 block 字面量:
^{
NSLog(@"This is a block");
}
2
3
类似与 C 中的方法指针,我们可以使用下面的语法去引用一个 block:
void (^simpleBlock)(void);
以上可以理解为我们创建了一个 simpleBlock 变量来表示一个参数为 void,返回值也为 void 的方法。之后,我们可以创建方法体:
simpleBlock = ^ {
NSLog(@"This is a block");
}
2
3
当然,我们也可以将上面两个步骤合二为一:
void (^simpleBlock)(void) = ^ {
NSLog(@"This is a block");
}
2
3
另外,block 也可以包含参数和返回值:
double (^addBlock)(double, double) =
^(double firstValue, double secondValue) {
return firstValue * secondValue;
};
2
3
4
# 3.6 KVO(键值观察)
# 3.6.1 添加观察者
// 添加观察者
[person addObserver:self
forKeyPath:@"age"
options:NSKeyValueObservingOptionNew
context:nil];
2
3
4
5
# 3.6.2 回调
// 回调
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqualToString:@"age"]) {
NSLog(@"New age: %@", change[NSKeyValueChangeNewKey]);
}
}
2
3
4
5
6
7
8
9
# 3.7 多线程使用
# 3.7.1 线程基本概念
- 线程:线程是程序执行的最小单位,一个进程可以包含多个线程。
- 主线程:也称为 UI 线程,负责更新 UI 和处理用户交互。
- 后台线程:用于执行耗时操作,避免阻塞主线程。
# 3.7.2 使用NSThread
NSThread 是 Objective-C 中最基础的线程管理类,可以显式创建和管理线程。
1.创建线程
// 方式 1:使用类方法
[NSThread detachNewThreadSelector:@selector(threadMethod:) toTarget:self withObject:nil];
// 方式 2:使用实例方法
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadMethod:) object:nil];
[thread start];
2
3
4
5
6
2.线程方法
- (void)threadMethod:(id)object {
NSLog(@"Thread is running");
// 执行耗时操作
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"Thread finished");
}
2
3
4
5
6
3.线程同步
使用 @synchronized 实现线程同步。
@synchronized(self) {
// 线程安全的代码块
}
2
3
# 3.7.3 NSOperation
NSOperation 和 NSOperationQueue 是基于 GCD 的高级抽象,提供了更强大的任务管理功能。
1.创建 NSOperation
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"Operation is running");
// 执行耗时操作
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"Operation finished");
}];
2
3
4
5
6
2.创建 NSOperationQueue
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 3; // 设置最大并发数
[queue addOperation:operation];
2
3
3.依赖关系
可以设置 NSOperation 之间的依赖关系。
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"Operation 1");
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"Operation 2");
}];
[operation2 addDependency:operation1]; // operation2 依赖于 operation1
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation1];
[queue addOperation:operation2];
2
3
4
5
6
7
8
9
10
# 3.7.4 GCD多线程
Grand Central Dispatch (GCD),GCD 是 Apple 提供的多线程编程解决方案,基于 C 语言 API,性能高效且易于使用。
1.创建队列
- 主队列:串行队列,用于更新 UI。
- 全局队列:并发队列,用于执行后台任务。
- 自定义队列:可以是串行或并发队列。
// 主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 全局队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 自定义队列
dispatch_queue_t customQueue = dispatch_queue_create("com.example.customQueue", DISPATCH_QUEUE_SERIAL);
2
3
4
5
6
7
8
2.执行任务
// 异步执行
dispatch_async(globalQueue, ^{
NSLog(@"Background task is running");
// 执行耗时操作
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"Background task finished");
// 回到主线程更新 UI
dispatch_async(mainQueue, ^{
NSLog(@"Update UI");
});
});
// 同步执行(会阻塞当前线程)
dispatch_sync(customQueue, ^{
NSLog(@"Sync task is running");
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
3.延迟执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"Delayed task");
});
2
3
4.任务组
使用 dispatch_group 管理一组任务。
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"Task 1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"Task 2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"All tasks finished");
});
2
3
4
5
6
7
8
9
10
11
12
13
# 3.7.5 线程安全
1.使用 @synchronized
@synchronized(self) {
// 线程安全的代码块
}
2
3
2.使用 NSLock
NSLock *lock = [[NSLock alloc] init];
[lock lock];
// 线程安全的代码块
[lock unlock];
2
3
4
3.使用 dispatch_semaphore
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 线程安全的代码块
dispatch_semaphore_signal(semaphore);
2
3
4