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

Objective-C属性声明语法与语义特性

2021-10-243.9k 阅读

一、属性声明的基本语法

在Objective - C中,属性声明是一种简洁地声明实例变量以及与之相关的存取方法的方式。其基本语法如下:

@property (attributes) type variableName;
  • @property:这是声明属性的关键字。
  • attributes:属性的特性,以逗号分隔的列表形式出现,用于指定属性的行为,如内存管理、原子性等。
  • type:属性的数据类型,可以是Objective - C对象类型(如NSString *)、基本数据类型(如intfloat)或自定义类型。
  • variableName:属性的名称,遵循变量命名规范。

例如,声明一个NSString类型的属性name

@property (nonatomic, strong) NSString *name;

二、内存管理相关特性

  1. assign
    • 语义assign是默认的内存管理特性(对于基本数据类型)。它用于简单地赋值,不涉及引用计数操作。对于对象类型,使用assign时要小心,因为它不会增加对象的引用计数,当对象被释放后,指向该对象的指针成为悬空指针,可能导致程序崩溃。一般用于基本数据类型,如intfloatBOOL等。
    • 示例
@property (assign) int age;
@property (assign) BOOL isEnabled;
  1. retain(ARC之前)和strong(ARC之后)
    • retain(ARC之前)
      • 语义retain特性会使属性在赋值时增加被赋值对象的引用计数。当属性被释放时,会自动释放其引用的对象(通过调用release方法)。在手动引用计数(MRC)环境下,这确保了对象在被属性引用期间不会被意外释放。
      • 示例
// 在MRC环境下
@property (retain) NSString *retainedString;
  • strong(ARC之后)
    • 语义strong在ARC(自动引用计数)环境下与MRC中的retain类似。它表示属性对对象有强引用,会增加对象的引用计数。只要有一个强引用指向对象,对象就不会被释放。
    • 示例
// 在ARC环境下
@property (nonatomic, strong) NSString *strongString;
  1. weak
    • 语义weak特性表示弱引用。弱引用不会增加对象的引用计数。当对象的最后一个强引用被释放后,对象会被销毁,并且所有指向该对象的弱引用会自动被设置为nil。这可以有效地防止循环引用,在处理视图控制器之间的父子关系等场景中非常有用。
    • 示例
@property (nonatomic, weak) UIViewController *weakViewController;
  1. unsafe_unretained
    • 语义unsafe_unretained类似于assign用于对象类型。它不会增加对象的引用计数,与weak不同的是,当对象被释放后,指向该对象的指针不会被自动设置为nil,从而成为悬空指针。使用unsafe_unretained需要特别小心,一般仅在性能敏感且能确保对象生命周期的情况下使用。
    • 示例
@property (nonatomic, unsafe_unretained) id someObject;

三、原子性相关特性

  1. atomic
    • 语义atomic是默认的原子性特性。它保证了属性的存取方法是线程安全的,即同一时间只有一个线程可以访问属性。在多线程环境下,atomic属性的存取操作会通过锁机制来确保数据的一致性。然而,这会带来一定的性能开销,因为每次访问属性都需要进行锁的操作。
    • 示例
@property (atomic, strong) NSString *atomicString;
  1. nonatomic
    • 语义nonatomic表示非原子性。属性的存取方法不是线程安全的,在多线程环境下可能会出现数据竞争问题。但是,由于没有锁的开销,nonatomic属性的访问速度更快,适用于单线程环境或对性能要求较高且能自行处理线程安全的场景。
    • 示例
@property (nonatomic, strong) NSString *nonatomicString;

四、读写权限相关特性

  1. readwrite
    • 语义readwrite是默认的读写权限特性。它表示属性同时拥有读方法(通常是getter方法)和写方法(通常是setter方法)。编译器会自动生成这两个方法的实现,除非开发者自己手动实现。
    • 示例
@property (nonatomic, strong, readwrite) NSString *readwriteString;
  1. readonly
    • 语义readonly表示属性是只读的。只会生成读方法(getter方法),不会生成写方法(setter方法)。这对于一些不应该被外部修改的属性非常有用,如一些计算属性或在对象初始化后就不应该改变的值。
    • 示例
@property (nonatomic, strong, readonly) NSString *readonlyString;

五、自定义存取方法

  1. 自定义getter方法
    • 语法:要自定义getter方法,只需在类的实现文件中实现一个与属性名相同(或者根据getter特性指定的名称)的方法。
    • 示例
// 声明属性
@property (nonatomic, strong) NSString *formattedName;

// 自定义getter方法
- (NSString *)formattedName {
    if (!_formattedName) {
        // 假设存在firstName和lastName属性
        _formattedName = [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
    }
    return _formattedName;
}
  1. 自定义setter方法
    • 语法:自定义setter方法时,方法名由set加上属性名首字母大写组成,并且有一个参数,参数类型与属性类型相同。
    • 示例
// 声明属性
@property (nonatomic, strong) NSString *name;

// 自定义setter方法
- (void)setName:(NSString *)newName {
    if (![newName isEqualToString:_name]) {
        _name = [newName copy];
        // 可以在这里添加其他逻辑,如通知等
    }
}

六、属性与实例变量的关系

  1. 自动合成实例变量
    • 在ARC环境下,当声明一个属性时,编译器会自动为该属性合成一个实例变量。默认情况下,实例变量的名称是下划线_加上属性名。例如,对于属性name,编译器会自动合成实例变量_name
    • 示例
@property (nonatomic, strong) NSString *name;
// 可以直接访问合成的实例变量_name
- (void)printName {
    NSLog(@"Name: %@", _name);
}
  1. 手动声明实例变量
    • 开发者也可以手动声明实例变量,并使用属性来访问它。在这种情况下,需要注意避免命名冲突。手动声明实例变量在一些需要更精细控制内存管理或访问权限的场景中有用。
    • 示例
@interface MyClass : NSObject {
    NSString *_customName;
}
@property (nonatomic, strong) NSString *name;
@end

@implementation MyClass
- (NSString *)name {
    return _customName;
}
- (void)setName:(NSString *)newName {
    if (![newName isEqualToString:_customName]) {
        _customName = [newName copy];
    }
}
@end

七、属性声明在类继承中的表现

  1. 属性继承
    • 子类会继承父类声明的属性。这意味着子类可以像访问自己声明的属性一样访问父类的属性。属性的特性(如内存管理、原子性等)也会一并继承。
    • 示例
@interface ParentClass : NSObject
@property (nonatomic, strong) NSString *parentProperty;
@end

@interface ChildClass : ParentClass
@end

@implementation ChildClass
- (void)accessParentProperty {
    self.parentProperty = @"Accessed from child";
    NSLog(@"Parent property in child: %@", self.parentProperty);
}
@end
  1. 属性重写
    • 子类可以重写父类的属性。在重写时,子类可以改变属性的特性,但需要遵循一定的规则。例如,不能将父类的readonly属性重写为readwrite属性。
    • 示例
@interface ParentClass : NSObject
@property (nonatomic, strong, readonly) NSString *parentReadOnlyProperty;
@end

@interface ChildClass : ParentClass
@property (nonatomic, strong) NSString *parentReadOnlyProperty; // 错误,不能将readonly改为readwrite
@end

八、属性在协议中的使用

  1. 协议中的属性声明
    • 协议可以声明属性,就像类中声明属性一样。协议中的属性声明只是定义了遵循该协议的类应该提供的存取方法的接口。
    • 示例
@protocol MyProtocol <NSObject>
@property (nonatomic, strong) NSString *protocolProperty;
@end
  1. 类遵循协议的属性实现
    • 当一个类遵循包含属性声明的协议时,该类必须提供协议中属性的实现,包括合适的存取方法。实现的属性特性可以与协议声明的特性不完全一致,但需要满足协议的基本要求。
    • 示例
@interface MyClass : NSObject <MyProtocol>
@property (nonatomic, strong) NSString *protocolProperty;
@end

@implementation MyClass
// 编译器会自动合成存取方法以满足协议要求
@end

九、属性声明的一些高级用法和注意事项

  1. 属性与KVO(Key - Value Observing)
    • 属性天生支持KVO。通过使用@property声明的属性,可以方便地注册为KVO的观察对象。当属性值发生变化时,观察者会收到通知。
    • 示例
// 注册观察
[self addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];

// 观察回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"name"]) {
        NSString *newName = change[NSKeyValueChangeNewKey];
        NSLog(@"Name changed to: %@", newName);
    }
}
  1. 属性与Core Data
    • 在Core Data中,实体的属性可以通过@property声明。Core Data会根据属性的声明来管理数据的持久化。例如,对于一个NSManagedObject子类,属性的声明会决定数据如何存储在数据库中。
    • 示例
@interface Person : NSManagedObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
  1. 注意循环引用问题
    • 在使用属性时,特别是涉及对象之间的相互引用,要注意避免循环引用。例如,两个对象都使用strong属性相互引用,会导致对象无法释放,造成内存泄漏。使用weak属性可以有效地解决这种问题。
    • 示例
@interface ClassA : NSObject
@property (nonatomic, strong) ClassB *classB;
@end

@interface ClassB : NSObject
@property (nonatomic, strong) ClassA *classA;
@end
// 这里会形成循环引用
ClassA *a = [[ClassA alloc] init];
ClassB *b = [[ClassB alloc] init];
a.classB = b;
b.classA = a;
  • 修正循环引用:
@interface ClassA : NSObject
@property (nonatomic, strong) ClassB *classB;
@end

@interface ClassB : NSObject
@property (nonatomic, weak) ClassA *classA;
@end

通过深入理解Objective - C属性声明的语法和语义特性,开发者能够更高效、更安全地编写代码,充分利用Objective - C语言的特性来构建稳定、高性能的应用程序。无论是在内存管理、多线程编程还是对象关系处理方面,属性声明都扮演着至关重要的角色。