Objective-C高级特性浅析与实践指南

OC的学习笔记(二)

文章目录

  • OC的学习笔记(二)
      • @property访问控制符
      • 点语法
    • 自定义`init`方法
    • 内存管理
      • retain 和 release
    • @class
    • 处理发生异常的方法
    • NSSrting的常用方法
      • 类方法
      • 对象方法
        • length
        • characterAtIndex
        • isEuqalString
        • compare
    • @autorelease和自动释放池
      • 自动释放池
    • Category类别与扩展
      • category的运用
        • NSNumber+jc.h:
        • NSNumber+jc.m:
        • main.m:
        • 整体流程:
    • Protocol
      • 特点:
      • 使用方式:
      • 关键字:
        • 1. `@protocol`:
        • 2. ``:
        • 3. `@optional` 和 `@required`:
        • 4. `conformsToProtocol:`:
      • 实际应用
        • OCButton.h:
        • OCButton.m:
        • OCListener.h 和 OCListener.m:
        • main.m:
        • 整体流程:
    • Block
      • 1. 基本语法:
      • 2. 声明和使用:
      • 3. Blocks 的捕获变量:
        • 1. 自动捕获(Automatic Capturing):
        • 2. 显式捕获(Explicit Capturing):
      • 4. Blocks 作为参数:
      • 5. Block 类型:
      • 6. 内存管理:
      • 7.Block的综合运用
        • Button.h:
        • Button.m:
        • main.m:
        • 整体流程:

存取的方法

@property访问控制符

在前面的学习中,我们不难发现对于每个类中的每一个成员变量,都有其实现的settergetter方法,为了避免繁琐的书写settergetter方法,我们可以让系统给我们自动合成对应的方法。

让系统自动生成对应的方法的方式如下:

  1. 在类接口部分使用@property 指令定义属性。使用@prop erty 定义属性时无须放在类接 又部分的花括号里,而是直接放在@interface、@end之间定义。@property 指令放在属性定义 的最前面。
  2. 此步是可选的。如果程序需要改变getter、setter 方法对应的成员变量的变量名,则可 在类实现部分使用@synthesize 指令
@property (attributes) type name;

编译器在遇到@property关键字的时候,会自动展开settergetter方法的声明。对于@synthesize则会自动展开settergetter方法的实现,且@synthesize 默认会找到与其同名的变量,如果没有与其同名变量则自动创建私有的同名变量。

由于我们在声明变量的时候会在变量名前加上下划线_,所以我们在使用@synthesize`,一般会这么写:

@synthesize window = _window;

上面代码用于告诉系统合成的 @property 对 应 的 成 员 变 量 为 _ window, 而 不 是window。 通 过 上面代码可以看出,使用 @synthesize的 语 法 格 式 如 下:

@synthesize property 名 [ = 成员变量名 ];

#import 
@interface Person : NSObject

@property int age;

@end
---------------------------------------------------------------------------------------------------------
  
@implementation Person
  
@synthesize age;

@end
---------------------------------------------------------------------------------------------------------
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 创建 Person 实例
        Person *a = [[Person alloc] init];
        
        // 使用点语法设置年龄属性
        [a setAge: 10];
        
        // 使用点语法获取年龄属性
        int age =[a age];
        NSLog(@"age is %d", age);
    }
    return 0;
}

点语法

在OC中我们也可以使点语法来,对类中的实例进行setter方法和getter,方法的调用,通过以下的例子,我们可以发现,a.age = 10;完全等价于[a age],在编译器编译的过程之中,会将a.age = 10 直接转化为[a setAge],其本质依然是函数调用。

由于点语法的存在,我们访问成员变量很容易与点语法中的getter方法淆,所以我们在声明成员变量的时候都会在变量名称前面加上一个下划线_

#import 
#import "Person.h"
@interface Person : NSObject
{
    int _age;
}

- (void)setAge:(int)newAge;
- (int)age;
@end


@implementation Person
- (void)setAge:(int)newAge {
    NSLog(@"调用了set方法");
    _age = newAge;
}

- (int)age {
    NSLog(@"调用了get方法");
    return _age;
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 创建 Person 实例
        Person *a = [[Person alloc] init];
        
        // 使用点语法设置年龄属性
        a.age = 10;
        
        // 使用点语法获取年龄属性
        int age = a.age;
        NSLog(@"age is %i", age);
    }
    return 0;
}

自定义init方法

init方法为静态方法。我们仍以上文的Person为例子,自定义一个初始化的函数

- (id)initWithAge:(int) age {
	if(self = [self super]) {
    self.age = age;
  }
  return self;
}

在自定义之后就可以进行方法的调用

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 创建 Person 实例
        Person *a = [[Person alloc] initWithAge: 10]; 
        NSLog(@"age is %i", a->-age);
    }
    return 0;
}

内存管理

retain 和 release

内存管理的范围:任何继承了NSObject的对象,对其他的c语言类型无效

原理:在OC之中每个对象都存在一个与之关联的整数,我们称之为引用计数器,当我们使用allocnew或者是copy创建一个对象的时候,该对象默认的引用计算器被设置为1。

  • 当我们给对象发送一条remain消息是时,它的计算器就会加一
  • 当我们给对象发送一条release消息时,可以使它的计数器值减一
  • 当一个对象的引用计算器值为0时,那么就将它销毁,它所占有的内存就被系统直接回收,OC也会自动向对象发送dealloc消息,一般我们会重写dealloc,在其中释放相关资源。
  • 我们也可以给对象发送remaiCcout消息获得当前计数引用器的值

当我们在init时,如果不给申请的对象指针,那么申请的内存会一直存在程序之中,不会被回收,直到程序终止。

当我们重复release,会发生野指针错误,释放一个不属于自己的内存。

- (void)dealloc {
  NSLog(@"%@被销毁", self);//标记
  
  [super dealloc];
}

dealloc的自定义方法

内存管理原则:谁创建,谁释放‘,在哪一个方法中创建的对象,就应该在那个程序内部将内存销毁,防止内存泄露。“谁retain,谁release。

setter方法的基本格式

-(void) setPerson : (Person*) preson {
  if(_person != person) {
    [_person release];
    _person = [person retain];//由于生成之后有一定会被release,在此处需要retain让其能够被返回
  }
}

如果对象中包含着其他对象,那么我们会在dealloc函数之中将,包含的副类我们需要将其release

- (void)dealloc {
  [self.对象名 release];
  NSLog(@"%@被销毁", self);//标记
  
  [super dealloc];
}

@class

在我们创建一个类的时候,我们想要在声明成员变量的时候,我们想要加入一个先前创建的类进入成员之中,我们就可以使用@class关键字 ,声明一个类,由于不需要import该类哦中的方法,仅仅声明一个类,在头文件中使用@class有利于我们提高性能。如果我们需要用到这个引用类的方法,那我们可以在.m文件中再进行import,即可解决问题。

​ 在OC中,@class 是一个关键字,用于在声明中提供一个类的前向声明。这是为了在不暴露类的具体实现的情况下,告诉编译器某个类的存在。@class 的主要作用是解决类之间的循环依赖问题。

​ 通常情况下,在 Objective-C 中,如果一个类 A 需要引用另一个类 B,而类 B 也需要引用类 A,就会出现循环依赖的问题。为了解决这个问题,可以在类 A 的头文件中使用 @class B; 声明类 B 的存在,而不用引入类 B 的头文件。

#import@class 是两种不同的 Objective-C 预处理指令,它们在编译时起到了不同的作用。

  1. #import 指令:

    • #import 指令用于在源文件中包含另一个文件的内容。它会确保被包含的文件只被引入一次,避免了重复引入的问题。
    • 通常用于引入头文件,以便在当前文件中使用头文件中声明的接口、类、函数等。
    #import "SomeClass.h" // 包含 SomeClass.h 文件的内容
    
    • #import 指令还会自动处理循环引用的情况,确保头文件不会被重复包含。
    • 当某个被#import的文件有改动,那么所有包含这个头文件的的文件都必须重新编译,效率就会低下。
  2. @class 关键字:

    • @class 关键字用于在声明中提前声明一个类的存在,而不包含其接口的细节。
    • 它告诉编译器:“这个类存在,但是我不需要它的具体实现,只需要知道它的存在”。
    • 主要用于解决类的循环依赖问题。当两个类互相引用对方的头文件时,可以使用 @class 来声明对方的存在,然后在实现文件中使用 #import 来包含对方的头文件
    @class SomeClass; // 提前声明 SomeClass 存在
    
    @interface AnotherClass : NSObject
    
    - (void)someMethodWithParameter:(SomeClass *)parameter;
    
    @end
    
    • 使用 @class 声明类时,只是告诉编译器这个类的存在,并不会包含其具体实现,因此可以提高编译速度和减少不必要的依赖。

总的来说,#import 主要用于包含文件的内容,直接包括了相关的方法,而 @class 主要用于声明类的存在,解决循环依赖的问题,而没有引用其中的方法。在使用时,需要根据具体的情况来选择合适的方式。

处理发生异常的方法

​ 在 Objective-C 中,@try 是一个异常处理机制的关键字,用于标记一段可能会抛出异常的代码块,并且提供了对这些异常的捕获和处理。

@try 块中的代码可能会抛出异常,如果抛出了异常,那么程序会跳转到 @catch 块,并执行相关的异常处理代码。如果没有抛出异常,程序会直接执行 @try 块后面的代码,而不会执行 @catch 块。

此外,@try 块后面还可以跟一个可选的 @finally 块,用于定义无论是否抛出异常都需要执行的代码。@finally 块中的代码会在 @try 块中的代码执行完成后无论是否抛出异常都会执行。

@try {
    // 可能会抛出异常的代码
    NSString *str = nil;
    [str length]; // 这里会抛出一个异常,因为 str 是 nil
}
@catch (NSException *exception) {
    // 捕获并处理异常
    NSLog(@"Caught an exception: %@", exception);
}
@finally {
    // 无论是否抛出异常,都会执行的代码
    NSLog(@"Finally block executed");
}

其中NSException *exception 是在 @catch 块中定义的参数。它表示捕获到的异常对象。

在 Objective-C 中,当代码中发生异常时,异常对象会被创建并抛出。NSException 是 Objective-C 中用于表示异常的类,它包含了关于异常的相关信息,比如异常的名称、原因等。

NSSrting的常用方法

类方法

  1. +(instancetype)stringWithUTF8String : (const char*)nullTerminatedCString
const char *cString = "Hello, world!";
NSString *objcString = [NSString stringWithUTF8String:cString];
NSLog(@"%@", objcString); // 输出:Hello, world!
  1. +(instancetype)stringWithFormat : (NSString*)format,...

用于根据格式化字符串创建一个新的字符串对象。

其中:

  • format 是一个格式化字符串,可以包含格式说明符(format specifiers),用来指定将要插入到字符串中的变量的格式。
  • ... 表示可变参数列表,用来传递给格式化字符串的变量。

示例:

NSString *name = @"John";
NSInteger age = 30;
NSString *greeting = [NSString stringWithFormat:@"Hello, my name is %@ and I'm %ld years old.", name, (long)age];
NSLog(@"%@", greeting); // 输出:Hello, my name is John and I'm 30 years old.

在这个示例中,我们使用 stringWithFormat: 方法创建了一个包含了变量 nameage 的字符串 greeting。在格式化字符串中,%@ 用于插入 name 的值,%ld 用于插入 age 的值。最后通过 NSLog 打印了这个字符串。

以上创建对象静态方法是不需要我们对其计数器进行释放的

对象方法

length

lengthNSString 类的一个属性,用于获取字符串的长度,即字符串中字符的个数。这个属性返回的是一个 NSUInteger 类型的值,表示字符串中字符的数量。

示例:

NSString *str = @"Hello";
NSUInteger len = str.length;
NSLog(@"Length of string: %lu", (unsigned long)len); // 输出:Length of string: 5

在这个示例中,我们定义了一个字符串 str,然后使用 length 属性获取了它的长度,并将长度值打印出来。注意,在打印时使用了 %lu 来格式化长度值,因为 length 返回的是 NSUInteger 类型的值。

characterAtIndex

characterAtIndex:NSString 类的一个方法,用于获取字符串中指定位置的字符。

该方法的定义如下:

- (unichar)characterAtIndex:(NSUInteger)index;

其中:

  • index 是要获取字符的位置,即字符串中的索引,从 0 开始计数。
  • 返回值 unichar 是表示 Unicode 字符的数据类型,是一个无符号的 16 位整数,用来表示一个字符的 UTF-16 编码。

示例:

NSString *str = @"Hello";
unichar firstChar = [str characterAtIndex:0];
NSLog(@"First character: %c", firstChar); // 输出:First character: H

在这个示例中,我们定义了一个字符串 str,然后使用 characterAtIndex: 方法获取了字符串中索引为 0 的字符,并将该字符打印出来。注意,在打印时使用了 %c 来格式化字符值。

如果我们要输出中文,则需要以下操作

NSString *str = @"你好,世界!";
unichar firstChar = [str characterAtIndex:0];
NSString *firstCharString = [NSString stringWithCharacters:&firstChar length:1];
NSLog(@"First character: %@", firstCharString); // 输出:First character: 你

isEuqalString
- (BOOL)isEqualToString : (NSString *)string;

isEqualToString: 是 NSString 类中的一个方法,用于比较两个字符串是否相等。它的基本用法是将一个字符串对象与另一个字符串对象进行比较,如果它们的内容相同,则返回 YES,否则返回 NO。例如:

NSString *str1 = @"Hello";
NSString *str2 = @"Hello";
NSString *str3 = @"World";

BOOL result1 = [str1 isEqualToString:str2]; // 返回 YES,因为 str1 和 str2 的内容相同
BOOL result2 = [str1 isEqualToString:str3]; // 返回 NO,因为 str1 和 str3 的内容不同

需要注意的是,isEqualToString: 方法是区分大小写的,即大写和小写字母视为不同。如果需要进行不区分大小写的比较,可以先将字符串转换为统一大小写后再进行比较。

compare

compare: 是 NSString 类中的一个方法,用于比较两个字符串的大小关系。它返回一个 NSComparisonResult 枚举值,指示两个字符串的大小关系。具体来说,返回值有三种可能:

  • NSOrderedAscending:表示接收者字符串的值在参数字符串之前。
  • NSOrderedSame:表示接收者字符串与参数字符串相等。
  • NSOrderedDescending:表示接收者字符串的值在参数字符串之后。

compare: 方法可以用于按照字典顺序比较字符串,也可以用于排序字符串数组。例如:

NSString *str1 = @"apple";
NSString *str2 = @"banana";
NSString *str3 = @"apple";

NSComparisonResult result1 = [str1 compare:str2]; // 返回 NSOrderedAscending,因为 "apple" 在 "banana" 之前
NSComparisonResult result2 = [str1 compare:str3]; // 返回 NSOrderedSame,因为 "apple" 与 "apple" 相等
NSComparisonResult result3 = [str2 compare:str1]; // 返回 NSOrderedDescending,因为 "banana" 在 "apple" 之后

需要注意的是,compare: 方法是按照字符串的 编码顺序进行比较的,因此可能会出现一些意想不到的结果。如果需要进行自定义的比较,可以使用其他方法或者自定义比较器。

@autorelease和自动释放池

不改变其计数器,只是将对象放入自动释放池,当自动释放池释放,池中元素的计数器就会自动减一

#import 

@interface MyClass : NSObject

// 类方法,用于创建并返回一个 MyClass 对象
+ (MyClass *)createMyClassWithParameter:(NSString *)parameter;

@end

@implementation MyClass

// 实现类方法
+ (MyClass *)createMyClassWithParameter:(NSString *)parameter {
    // 创建 MyClass 对象并设置属性
    MyClass *myClass = [[[MyClass alloc] init] autorelease];
    myClass.parameter = parameter;
    return myClass;
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 调用类方法创建对象
        MyClass *obj = [MyClass createMyClassWithParameter:@"Hello"];
        NSLog(@"%@", obj.parameter); // 输出对象属性值
    }
    return 0;
}

​ 以上就是用autorelease使用类方法(静态方法)来创建对象。类方法是属于类而不是对象的方法,因此可以直接通过类名调用。

在程序之中我们使用点语法进行变量的赋值,而不是直接的调用成员变量进行赋值,因为在静态方法之中我们无法直接访问变量

自动释放池

自动释放池(Autorelease Pool)是 Objective-C 中一种管理对象生命周期的机制。它允许我们将临时创建的对象添加到一个池子中,在池子被释放时,其中的对象也会被释放,从而避免内存泄漏。

在 Objective-C 中,当我们通过 alloccopymutableCopynew 等方法创建一个对象时,该对象的引用计数会增加。而在某些情况下,我们创建的对象并不需要长期保留,只需要在一段较短的代码执行期间内使用,使用完毕后即可释放。这时就可以使用自动释放池来管理这些临时对象。

自动释放池通过 @autoreleasepool 语句来创建。在 @autoreleasepool 块中创建的对象会被添加到自动释放池中,在块结束时,自动释放池会被释放,其中的对象也会被释放。

下面是一个示例代码,演示了如何使用自动释放池:

#import 

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 创建自动释放池
        // 在这个代码块中创建的对象会被添加到自动释放池中
        NSString *str1 = [[[NSString alloc] initWithFormat:@"Hello, %@!", @"World"] autorelease];
        NSLog(@"%@", str1); // 输出:Hello, World!

        // 创建自动释放池
        @autoreleasepool {
            // 在嵌套的自动释放池中创建的对象也会被添加到自动释放池中
            NSString *str2 = [[[NSString alloc] initWithFormat:@"Goodbye, %@!", @"World"] autorelease];
            NSLog(@"%@", str2); // 输出:Goodbye, World!
        } // 结束嵌套的自动释放池,其中的对象会被释放

        // 在主自动释放池中创建的对象会在主池结束时被释放
        NSDate *date = [[[NSDate alloc] init] autorelease];
        NSLog(@"%@", date); // 输出当前日期和时间
    } // 结束主自动释放池,其中的对象会被释放

    return 0;
}

在这个示例中,我们创建了一个主自动释放池,在其中创建了两个字符串对象 str1str2,以及一个日期对象 date。在主自动释放池结束时,其中的对象都会被释放,包括嵌套自动释放池中的对象。

值得注意的是,我们不应该把大量的循环操作放入自动释放池,由于创建的对象需要在循环中一直存在,直到释放池结束,容易占用内存

Category类别与扩展

Objective-C 的动态特征允许我们使用类别为现有的类添加新的方法,并且不需要创建子类,不需要访问原有类的源代码。

在Objective-C(OC)中,Category 是一种特殊的技术,允许你在不修改原始类代码的情况下为现有的类添加新的方法。通过 Category,你可以将类的实现分散到多个不同的文件中,这样可以更好地组织代码并避免单个类文件变得过于庞大。

具体来说,使用 Category,你可以:

  1. 为现有的类添加方法:你可以通过 Category 为系统类(如 NSString、NSArray 等)或者你自己创建的类添加新的方法,而不必修改原始类的实现。
  2. 将方法按照功能组织:你可以创建多个 Category 文件,每个文件包含一组相关的方法,这样可以更清晰地组织代码。
  3. 实现非正式协议:通过 Category,你可以使类遵循额外的协议,从而使它们能够执行额外的行为,而不必修改原始类的定义。

category的运用

//类别的接口部分
#import 

@interface NSNumber (jc)
-(NSNumber*) add : (double) num2;
-(NSNumber*) sub : (double) num2;
-(NSNumber*) mul : (double) num2;
-(NSNumber*) dev : (double) num2;
@end
—————————————————————————————————————————————————————————————————————————————————————————————————————————
//类别的实现部分
#import "NSNumber+jc.h"

@implementation NSNumber (jc)
-(NSNumber*) add : (double) num2 {
    return [NSNumber numberWithDouble:([self doubleValue] + num2)];
}
-(NSNumber*) sub : (double) num2{
    return [NSNumber numberWithDouble:([self doubleValue] - num2)];
}
-(NSNumber*) mul : (double) num2{
    return [NSNumber numberWithDouble:([self doubleValue] * num2)];
}
-(NSNumber*) dev : (double) num2{
    return [NSNumber numberWithDouble:([self doubleValue] / num2)];
}
@end
—————————————————————————————————————————————————————————————————————————————————————————————————————————
#import 
#import "NSNumber+jc.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSNumber* num1 = [NSNumber numberWithDouble:2.4];
        NSNumber* add = [num1 add:2.4];
        NSNumber* sub = [num1 sub:2.4];
        NSNumber* mul = [num1 mul:2.4];
        NSNumber* dev = [num1 dev:2.4];
        NSLog(@"%@ %@ %@ %@", add, sub, mul, dev);
    }
    return 0;
}

这段代码演示了如何使用类别(Category)扩展 NSNumber 类,添加一些数学运算方法。下面是代码的解释:

NSNumber+jc.h:
  • NSNumber+jc.h 中,定义了一个类别 jc,用于扩展 NSNumber 类。
  • 在类别接口中声明了四个方法 add:, sub:, mul:, dev:,用于执行加法、减法、乘法和除法运算。
NSNumber+jc.m:
  • NSNumber+jc.m 中,实现了类别接口中声明的四个方法。
  • 每个方法内部使用 [self doubleValue] 来获取当前 NSNumber 对象的数值,然后进行相应的数学运算,并返回一个新的 NSNumber 对象。
main.m:
  • main 函数中,创建了一个 NSNumber 对象 num1,并初始化其值为 2.4
  • 分别调用了 add:, sub:, mul:, dev: 方法来执行加法、减法、乘法和除法运算。
  • 使用 NSLog 打印了每个运算结果。
整体流程:
  1. main 函数中,创建了一个 NSNumber 对象 num1,并初始化其值为 2.4
  2. 分别调用了 add:, sub:, mul:, dev: 方法来执行加法、减法、乘法和除法运算,得到结果 addsubmuldev
  3. 使用 NSLog 打印了每个运算结果。

Protocol

协议(Protocol)是 Objective-C 中一种定义接口的方式,类似于其他编程语言中的接口或抽象类。协议定义了一组方法或属性的声明,但并不提供实现。类、分类或其他对象可以采用(adopt)协议,并实现其中定义的方法或属性,从而符合协议的要求。

下面是关于 Objective-C 中协议的一些重要特点和使用方式:

特点:

  1. 声明方法或属性:协议可以声明方法和属性,但不提供实现。它只是描述了一组方法或属性的接口。

  2. 多继承:Objective-C 中允许一个类采用多个协议,因此可以实现多继承的效果。一个类可以遵循多个协议,从而获得多个协议定义的方法和属性。

  3. 可选方法:协议中的方法可以是可选的,采用协议的对象可以选择性地实现这些方法。

  4. 协议的继承:一个协议可以继承自另一个协议,从而继承其方法和属性声明。

使用方式:

  1. 声明协议:使用 @protocol 关键字来声明协议,并在其中列出方法和属性的声明。例如:

    @protocol MyProtocol
    - (void)doSomething;
    @property(nonatomic, strong) NSString *name;
    @end
    
  2. 采用协议:使用 语法来指定一个类或对象采用某个协议。例如:

    @interface MyClass : NSObject
    @end
    
  3. 实现协议方法:在类的实现中实现协议中声明的方法。例如:

    @implementation MyClass
    - (void)doSomething {
        // 实现方法的具体逻辑
    }
    @end
    
  4. 遵循协议约定:采用协议的类必须实现协议中声明的所有必需方法。否则,在编译时会得到警告。可选方法可以选择性地实现。

    @implementation MyClass
    - (void)doSomething {
        // 实现方法的具体逻辑
    }
    @end
    
  5. 协议的组合:一个类可以遵循多个协议,即类可以有多个直接父协议,使用逗号分隔。例如:

    @interface MyClass : NSObject
    @end
    

通过使用协议,可以实现对象之间的松耦合,增加代码的可复用性和灵活性。协议使得在不同的类中实现相同的接口成为可能,同时也是实现委托模式(Delegate Pattern)的关键。

关键字:

1. @protocol

@protocol 是用于声明协议的关键字。通过 @protocol 关键字,可以定义一组方法和属性的接口,但不提供实现。例如:

@protocol MyProtocol
- (void)doSomething;
@property(nonatomic, strong) NSString *name;
@end
2.

语法用于指定一个类或对象采用某个协议。它在类的声明中使用,表示该类采用了指定的协议。例如:

@interface MyClass : NSObject
@end
3. @optional@required

@optional@required 是用于声明协议方法的可选性的关键字。在协议中,使用 @optional 关键字声明的方法是可选的,而未使用 @optional 关键字声明的方法是必须实现的。

例如:

@protocol MyProtocol
@required
- (void)requiredMethod;

@optional
- (void)optionalMethod;
@end
4. conformsToProtocol:

conformsToProtocol:NSObject 类的方法之一,用于检查一个对象是否遵循了指定的协议。它返回一个布尔值,表示对象是否遵循了指定的协议。例如:

if ([myObject conformsToProtocol:@protocol(MyProtocol)]) {
    // myObject 遵循 MyProtocol 协议
} else {
    // myObject 不遵循 MyProtocol 协议
}

这些关键字是在 Objective-C 中定义和使用协议时经常用到的,它们用于声明协议、指定采用协议的类、声明协议方法的可选性、检查对象是否遵循协议等。

实际应用

//OCButton.h的内容
#import 

@class OCButton;
@protocol ButtonDelegate 

-(void)OnClick : (OCButton *)button;

@end
@interface OCButton : NSObject
@property (nonatomic, retain) id delegate;
-(void)Click;
@end
—————————————————————————————————————————————————————————————————————————————————————————————————————————
//OCButton.m的内容
#import "OCButton.h"

@implementation OCButton

-(void) Click {
    if ([_delegate respondsToSelector:@selector(OnClick:)]){
        [_delegate OnClick : self];
    } else {
        NSLog(@"监听失效");
    }
   
}
@end—————————————————————————————————————————————————————————————————————————————————————————————————————————#import 
#import "OCButton.h"

@interface OCListener : NSObject 

@end
—————————————————————————————————————————————————————————————————————————————————————————————————————————
#import "OCListener.h"

@implementation OCListener
-(void) OnClick : (OCButton*) button{
    NSLog(@"%@be clicked on!", button);
}

@end
—————————————————————————————————————————————————————————————————————————————————————————————————————————
#import 
#import "OCButton.h"
#import "OCListener.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        OCButton* botton = [[OCButton alloc] init];
        OCListener* listener = [[OCListener alloc] init];
        botton.delegate = listener;
        [botton Click];
    }
    return 0;
}

这段代码演示了如何在 Objective-C 中使用委托模式(Delegate Pattern)实现按钮点击事件的监听。

OCButton.h:
  • OCButton.h 中,定义了一个协议 ButtonDelegate,其中声明了一个方法 OnClick:,用于处理按钮点击事件。
  • 定义了一个 OCButton 类,其中声明了一个 delegate 属性,用于存储委托对象。
  • 声明了一个方法 Click,用于模拟按钮点击事件的触发。
OCButton.m:
  • OCButton.m 中,实现了 Click 方法。该方法首先检查委托对象是否实现了 OnClick: 方法,如果实现了,则调用委托对象的 OnClick: 方法来处理按钮点击事件;否则输出监听失效的信息。
OCListener.h 和 OCListener.m:
  • OCListener.h 中,声明了一个 OCListener 类,并实现了 ButtonDelegate 协议。在 OCListener.m 中,实现了 OnClick: 方法,用于处理按钮点击事件。
main.m:
  • main 函数中,创建了一个 OCButton 对象和一个 OCListener 对象。
  • OCListener 对象设置为 OCButton 对象的委托对象。
  • 调用 OCButton 对象的 Click 方法,触发按钮点击事件。
整体流程:
  1. main 函数中,创建了一个按钮对象 botton 和一个监听器对象 listener
  2. 将监听器对象设置为按钮对象的委托对象。
  3. 调用按钮对象的 Click 方法。
  4. Click 方法内部检查委托对象是否实现了 OnClick: 方法,如果实现了,则调用委托对象的 OnClick: 方法来处理按钮点击事件。

Block

在 Objective-C 中,Block 是一种语言特性,与C语言之中的函数十分的相似,用于封装一段代码并在需要时执行。它们类似于匿名函数或闭包,在很多情况下可以替代传统的回调函数,使得代码更加简洁和易读。下面详细讨论一下 Objective-C 中的 Blocks:

1. 基本语法:

在 Objective-C 中,Blocks 以 ^ 符号开始定义,后跟一对大括号 {},其中包含需要执行的代码。语法如下:

^returnType (parameters) {
    // Block 代码
};

例如,一个接受两个整数参数并返回它们之和的 Block 可以如下定义:

int (^addBlock)(int, int) = ^(int a, int b) {
    return a + b;
};

2. 声明和使用:

Blocks 可以作为变量存储,也可以作为参数传递给方法或函数。它们可以通过调用来执行,就像函数一样。例如:

// 声明一个接受两个整数参数并返回它们之和的 Block
int (^addBlock)(int, int) = ^(int a, int b) {
    return a + b;
};

// 使用 Block 进行计算并打印结果
int result = addBlock(3, 5);
NSLog(@"Result: %d", result); // 输出: Result: 8

3. Blocks 的捕获变量:

Blocks 可以捕获其定义范围内的变量和常量,即使这些变量和常量在 Block 被执行时超出了其定义范围。捕获的变量在 Block 内部形成了闭包,可以在 Block 中被访问和修改。例如:

在 Objective-C 中,捕获变量有两种方式:自动捕获和显式捕获。

1. 自动捕获(Automatic Capturing):

当在 Blocks 内部引用外部变量时,如果这些变量是自动变量(在栈上分配),Blocks 会自动将它们捕获为 const 常量。这意味着在 Block 内部无法修改这些变量的值。例如:

int value = 10;
void (^printBlock)(void) = ^{
  //value = 19;  
    NSLog(@"Value: %d", value); // 自动捕获变量 value,输出10
};

在这个示例中,变量 value 是一个自动变量,被自动捕获为 const 常量,Block 内部可以读取它的值,但无法修改。如果加入被注释的内容就会导致编译器报错。

2. 显式捕获(Explicit Capturing):

如果需要在 Blocks 内部修改外部变量的值,就需要使用 __block 关键字显式地捕获这些变量。使用 __block 关键字修饰的外部变量可以在 Block 内部被修改,并且修改后的值会反映到 Block 外部的变量上。例如:

__block int count = 0;
void (^incrementBlock)(void) = ^{
    count++;
  	NSLog("%d", count);
};

在这个示例中,变量 count 被显式地使用 __block 关键字捕获,使得 Block 内部可以修改它的值。

4. Blocks 作为参数:

Blocks 可以作为方法或函数的参数传递,常用于异步编程、回调函数等场景。例如:

- (void)performOperationWithCompletion:(void (^)(void))completion {
    // 执行一些操作
    // ...

    // 执行完成后调用传入的 Block
    completion();
}

// 调用方法,并传入 Block 作为参数
[self performOperationWithCompletion:^{
    NSLog(@"Operation Completed!");
}];

5. Block 类型:

Blocks 可以有不同的类型,根据其参数和返回值的类型来区分。在 Objective-C 中,可以使用 typedef 来定义 Block 类型,以提高代码的可读性。例如:

typedef int (^OperationBlock)(int, int);

OperationBlock addBlock = ^(int a, int b) {
    return a + b;
};
NSLog(@"%d", addBlock(10, 10));

6. 内存管理:

在 Objective-C 中,Blocks 被创建时会在堆上分配内存。因此,在 ARC(Automatic Reference Counting)环境中,需要注意避免循环引用问题。可以使用 __weak__block 关键字来解决循环引用问题。

7.Block的综合运用

#import 

@class Button;
typedef void (^ButtonBlock) (Button* btn);
@interface Button : NSObject

@property (nonatomic, copy) ButtonBlock block;
-(void)click;
@end
—————————————————————————————————————————————————————————————————————————————————————————————————————————
#import "Button.h"

@implementation Button
-(void) click {
    _block(self);
}
@end
—————————————————————————————————————————————————————————————————————————————————————————————————————————
#import 
#import "Button.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Button* btn = [[Button alloc] init];
        btn.block = ^(Button* btn) {
            NSLog(@"%@ -- 被点击了",btn);
        };
        //回调实现
        [btn click];
    }
    return 0;
}

这段代码演示了如何在 Objective-C 中使用 Block 实现按钮点击事件的监听。下面是代码的解释:

Button.h:
  • Button.h 中,声明了一个 Button 类,并定义了一个 ButtonBlock 类型的 Block。
  • Button 类中声明了一个 block 属性,用于存储 Block 对象。
  • 声明了一个方法 click,用于模拟按钮点击事件的触发。
Button.m:
  • Button.m 中,实现了 click 方法。该方法内部调用了存储的 Block 对象,并将自身作为参数传递给 Block。
main.m:
  • main 函数中,创建了一个按钮对象 btn
  • 将一个 Block 对象赋值给按钮对象的 block 属性,Block 对象中实现了按钮点击后的操作。
  • 调用按钮对象的 click 方法,触发按钮点击事件。
整体流程:
  1. main 函数中,创建了一个按钮对象 btn
  2. 将一个 Block 对象赋值给按钮对象的 block 属性,Block 对象中实现了按钮点击后的操作。
  3. 调用按钮对象的 click 方法。
  4. click 方法内部调用存储的 Block 对象,触发按钮点击事件,并将按钮对象作为参数传递给 Block。
  5. 在 Block 中,打印出按钮对象的信息,表示按钮被点击了。

你可能感兴趣的:(objective-c,学习)