MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C中的@符号特殊用法全集

2024-05-073.8k 阅读

1. @interface 和 @implementation

在Objective - C中,@interface用于声明一个类,定义类的属性和方法。它就像是一个蓝图,告诉编译器这个类长什么样,有哪些成员。而@implementation则是对@interface中声明的方法进行具体实现。

// 定义一个简单的类
@interface Person : NSObject {
    NSString *name;
    NSInteger age;
}

@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;

- (void)sayHello;

@end

@implementation Person

@synthesize name;
@synthesize age;

- (void)sayHello {
    NSLog(@"Hello, my name is %@ and I'm %ld years old.", name, (long)age);
}

@end

在上述代码中,@interface部分声明了一个Person类,它继承自NSObject,有两个实例变量nameage,同时使用@property声明了两个属性,还声明了一个实例方法sayHello@implementation部分使用@synthesize为属性生成了存取方法,并实现了sayHello方法。

2. @property

@property是Objective - C中用于定义属性的关键字。它简化了实例变量的存取方法的创建。通过@property,可以指定属性的特性,如内存管理策略、原子性等。

2.1 内存管理特性

  • nonatomic:指定该属性的存取方法不是线程安全的。在多线程环境下,可能会出现数据竞争问题,但由于它不进行线程同步,所以性能较高。
  • atomic:默认值,属性的存取方法是线程安全的。通过锁机制保证同一时间只有一个线程能访问属性,但这会带来性能开销。

2.2 读写特性

  • readwrite:默认值,属性同时拥有读方法(getter)和写方法(setter)。
  • readonly:属性只有读方法,没有写方法,适用于一些只需要读取的属性,如视图的边界等。

2.3 内存管理策略

  • assign:用于简单数据类型,如NSIntegerCGFloat等。它直接赋值,不涉及内存管理。
  • strong:用于对象类型,会增加对象的引用计数。当一个对象被赋值给一个strong类型的属性时,该对象的引用计数加1。
  • weak:同样用于对象类型,但不会增加对象的引用计数。当对象的引用计数变为0并被销毁时,指向该对象的weak属性会自动被设置为nil,从而避免野指针问题。
  • copy:用于对象类型,会创建对象的副本。常用于NSString等不可变类型,以防止属性值被意外修改。
@interface Car : NSObject

@property (nonatomic, assign) NSInteger wheelCount;
@property (nonatomic, strong) NSString *brand;
@property (nonatomic, weak) id<CarDelegate> delegate;
@property (nonatomic, copy) NSString *model;

@end

上述代码中,wheelCount使用assign,因为它是简单数据类型;brand使用strong来持有NSString对象;delegate使用weak以避免循环引用;model使用copy确保属性值的不可变性。

3. @synthesize 和 @dynamic

3.1 @synthesize

@synthesize用于告诉编译器为@property生成存取方法。在现代的Objective - C中,即使不写@synthesize,编译器也会自动为@property生成默认的存取方法。但在某些情况下,我们可能需要手动使用@synthesize来指定实例变量。

@interface Dog : NSObject

@property (nonatomic, strong) NSString *name;

@end

@implementation Dog

@synthesize name = _dogName;

@end

在上述代码中,通过@synthesize name = _dogName,指定了name属性对应的实例变量为_dogName。这样,编译器生成的存取方法会操作_dogName这个实例变量。

3.2 @dynamic

@dynamic@synthesize相反,它告诉编译器不要自动生成存取方法。这在一些情况下很有用,比如当存取方法是在运行时动态创建的,或者属性是通过其他方式(如KVO)来实现的。

@interface DynamicPropertyClass : NSObject

@property (nonatomic, strong) NSString *dynamicProperty;

@end

@implementation DynamicPropertyClass

@dynamic dynamicProperty;

@end

在上述代码中,使用@dynamic声明dynamicProperty,意味着开发者需要自己在运行时提供存取方法,否则访问该属性会导致运行时错误。

4. @protocol 和 @optional

4.1 @protocol

@protocol用于定义协议,它是一种特殊的接口,定义了一组方法声明,但不包含方法的实现。任何类都可以声明遵循某个协议,从而承诺实现协议中的方法。

@protocol AnimalProtocol <NSObject>

- (void)eat;
- (void)move;

@end

@interface Cat : NSObject <AnimalProtocol>

@end

@implementation Cat

- (void)eat {
    NSLog(@"Cat is eating.");
}

- (void)move {
    NSLog(@"Cat is moving.");
}

@end

在上述代码中,@protocol AnimalProtocol定义了eatmove两个方法。Cat类声明遵循AnimalProtocol,并实现了协议中的方法。

4.2 @optional

在协议中,默认所有方法都是必须实现的。但通过@optional关键字,可以将协议中的某些方法标记为可选的,遵循该协议的类可以选择是否实现这些方法。

@protocol NewAnimalProtocol <NSObject>

- (void)eat;
@optional
- (void)fly;

@end

@interface Bird : NSObject <NewAnimalProtocol>

@end

@implementation Bird

- (void)eat {
    NSLog(@"Bird is eating.");
}

@end

在上述代码中,@protocol NewAnimalProtocol中的fly方法通过@optional标记为可选方法。Bird类遵循该协议,但只实现了eat方法,没有实现fly方法,这是允许的。

5. @selector

@selector用于获取一个方法的选择器。选择器是一个表示方法的唯一标识符,它是一个SEL类型的值。@selector在消息传递、目标 - 动作机制等方面有广泛应用。

@interface Calculator : NSObject

- (NSInteger)add:(NSInteger)a b:(NSInteger)b;

@end

@implementation Calculator

- (NSInteger)add:(NSInteger)a b:(NSInteger)b {
    return a + b;
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Calculator *calc = [[Calculator alloc] init];
        SEL addSelector = @selector(add:b:);
        NSInteger result = ((NSInteger (*)(id, SEL, NSInteger, NSInteger))objc_msgSend)(calc, addSelector, 3, 5);
        NSLog(@"The result is %ld", (long)result);
    }
    return 0;
}

在上述代码中,通过@selector(add:b:)获取了Calculator类中add:b:方法的选择器,然后使用objc_msgSend函数进行消息发送,实现了方法调用。

6. @try、@catch 和 @finally

Objective - C中的@try@catch@finally用于异常处理。@try块中包含可能会抛出异常的代码,@catch块用于捕获并处理异常,@finally块中的代码无论是否发生异常都会执行。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        @try {
            NSArray *array = @[@1, @2, @3];
            NSInteger value = [array objectAtIndex:5];
        } @catch (NSException *exception) {
            NSLog(@"Caught exception: %@", exception);
        } @finally {
            NSLog(@"This is finally block.");
        }
    }
    return 0;
}

在上述代码中,@try块中访问数组越界会抛出异常,@catch块捕获并打印异常信息,@finally块中的代码始终会执行。

7. @class

@class用于向前声明一个类。它告诉编译器某个类的存在,但不包含类的详细定义。这在减少编译依赖、避免循环引用等方面非常有用。

// ForwardDeclaration.h
@class AnotherClass;

@interface ForwardDeclaration : NSObject

@property (nonatomic, strong) AnotherClass *anotherObject;

- (void)doSomethingWithAnotherClass;

@end

// ForwardDeclaration.m
#import "ForwardDeclaration.h"
#import "AnotherClass.h"

@implementation ForwardDeclaration

- (void)doSomethingWithAnotherClass {
    AnotherClass *obj = [[AnotherClass alloc] init];
    [obj someMethod];
}

@end

在上述代码中,ForwardDeclaration.h中使用@class AnotherClass向前声明了AnotherClass,这样ForwardDeclaration.h就不需要导入AnotherClass.h,减少了编译依赖。在ForwardDeclaration.m中,由于需要使用AnotherClass的具体实现,所以导入了AnotherClass.h

8. @encode

@encode用于获取一个类型的编码字符串。这个编码字符串可以表示基本数据类型、对象类型、结构体等。它在运行时类型检查、消息传递等方面有重要应用。

NSString *intEncoding = [NSString stringWithUTF8String:@encode(int)];
NSString *objectEncoding = [NSString stringWithUTF8String:@encode(NSObject *)];
NSString *structEncoding = [NSString stringWithUTF8String:@encode(CGPoint)];

NSLog(@"Int encoding: %@", intEncoding);
NSLog(@"Object encoding: %@", objectEncoding);
NSLog(@"Struct encoding: %@", structEncoding);

上述代码中,@encode(int)获取int类型的编码字符串,@encode(NSObject *)获取NSObject指针类型的编码字符串,@encode(CGPoint)获取CGPoint结构体类型的编码字符串,并将其打印出来。

9. @synchronized

@synchronized用于实现简单的线程同步。它通过一个锁对象来保证在同一时间只有一个线程能执行被保护的代码块。

@interface SynchronizedClass : NSObject

@property (nonatomic, assign) NSInteger counter;

- (void)incrementCounter;

@end

@implementation SynchronizedClass

- (void)incrementCounter {
    @synchronized(self) {
        self.counter++;
    }
}

@end

在上述代码中,@synchronized(self)块中的代码在同一时间只有一个线程能执行,从而保证了counter属性的线程安全递增。

10. @defs

@defs是一个很少使用的关键字,它用于获取协议定义的方法列表。通常在运行时通过objc_protocol_list等函数结合@defs来获取协议的详细信息。

@protocol MyProtocol <NSObject>

- (void)protocolMethod;

@end

struct objc_protocol_list *protocolList = objc_copyProtocolList(&count);
for (NSUInteger i = 0; i < count; i++) {
    Protocol *protocol = protocolList[i];
    if (strcmp(protocol_getName(protocol), "MyProtocol") == 0) {
        struct objc_method_description *methodList = protocol_copyMethodDescriptionList(protocol, YES, YES, &methodCount);
        for (NSUInteger j = 0; j < methodCount; j++) {
            struct objc_method_description method = methodList[j];
            SEL selector = method.name;
            const char *types = method.types;
            NSLog(@"Method: %@, Types: %s", NSStringFromSelector(selector), types);
        }
        free(methodList);
    }
}
free(protocolList);

上述代码通过objc_copyProtocolList获取所有协议列表,然后查找MyProtocol协议,并使用protocol_copyMethodDescriptionList获取协议中方法的描述列表,包括方法选择器和类型编码。虽然@defs本身没有直接在代码中体现,但它在运行时获取协议方法列表的机制中起到了作用。

11. @end

@end用于结束@interface@implementation块,或者结束@protocol的定义。它明确标记了类、协议定义部分的结束。

@interface SampleClass : NSObject

// 类的声明部分

@end

@implementation SampleClass

// 类的实现部分

@end

@protocol SampleProtocol <NSObject>

// 协议的声明部分

@end

在上述代码中,@end分别结束了SampleClass的声明、实现以及SampleProtocol的定义。

12. @compatibility_alias

@compatibility_alias用于定义兼容性别名。它允许为一个类型定义一个别名,主要用于兼容旧版本的代码或提供更简洁的类型名称。

@compatibility_alias OldNSString NSString;

OldNSString *str = @"Hello, using alias";

在上述代码中,@compatibility_alias OldNSString NSStringNSString定义了一个别名OldNSString,后续代码中可以使用OldNSString来代替NSString

13. @selectorName

@selectorName@selector的一种变体,它用于获取方法选择器的字符串表示形式。这在一些需要将方法选择器以字符串形式传递或存储的场景中很有用。

@interface MethodSelectorClass : NSObject

- (void)exampleMethod;

@end

@implementation MethodSelectorClass

- (void)exampleMethod {
    NSLog(@"This is an example method.");
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MethodSelectorClass *obj = [[MethodSelectorClass alloc] init];
        SEL selector = @selector(exampleMethod);
        NSString *selectorName = NSStringFromSelector(selector);
        NSLog(@"Selector name: %@", selectorName);
    }
    return 0;
}

在上述代码中,通过NSStringFromSelector函数将@selector(exampleMethod)获取的选择器转换为字符串形式并打印出来。虽然不是直接使用@selectorName,但这展示了获取方法选择器字符串表示的常见用法。

14. @block

@block用于定义一个块(block)。块是Objective - C中的一种匿名函数,可以捕获其定义时的上下文变量。块在异步编程、回调等场景中广泛应用。

void (^simpleBlock)(void) = ^{
    NSLog(@"This is a simple block.");
};
simpleBlock();

int number = 10;
void (^captureBlock)(void) = ^{
    NSLog(@"The number is %d", number);
};
number = 20;
captureBlock();

在上述代码中,simpleBlock是一个简单的块,直接打印一条信息。captureBlock捕获了number变量,尽管在块定义后number的值发生了改变,但块内部捕获的是定义时number的值,所以打印的是10

15. @autoreleasepool

@autoreleasepool用于创建一个自动释放池。在Objective - C中,当对象发送autorelease消息时,对象会被放入最近的自动释放池中。当自动释放池被销毁时,池中的所有对象都会发送release消息。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        for (NSUInteger i = 0; i < 100000; i++) {
            NSString *string = [[NSString alloc] initWithFormat:@"Number %lu", (unsigned long)i];
            // 这里string发送autorelease消息后会放入当前的自动释放池
        }
    }
    // 自动释放池结束,其中的对象会被释放
    return 0;
}

在上述代码中,@autoreleasepool块内创建了大量的NSString对象,这些对象在发送autorelease消息后会被放入自动释放池。当@autoreleasepool块结束时,池中的对象会被释放,避免了内存峰值过高。

通过对Objective - C中@符号这些特殊用法的深入理解和掌握,开发者能够更高效、更灵活地编写Objective - C代码,充分发挥Objective - C语言的特性和优势,无论是在开发iOS应用、Mac应用还是其他基于Objective - C的项目中,都能应对各种复杂的编程场景。