成员变量与属性

865 阅读6分钟

1、成员变量

先上示例:

 @ interface Person : NSObject
 {
 	NSInteger _age;
    Child *son;
 }
 @property (nonatomic, copy) NSString *name;
 @end

上述示例中: _age 即为成员变量。

son 是一个实例变量,实例变量也是成员变量的一种特殊形式。

** 注意:**

OC 里面的成员变量默认 @protected 修饰的。

成员变量不能用点语法调用,因为没有set和get方法,只能使用->调用。

2、属性

2.1 属性的声明

上述示例中,name 即为我们声明了一个属性。

@property 在 Xcode 5之前,只能生成 setter 和 getter 方法的声明。 @property 在 Xcode 5之后,不光可以生成setter 和 getter 方法,还默认实现 setter 方法和 getter 方法,还会默认生成一个 _propertyName 的成员变量,即:

@property 作用 默认利用 @synthesize propertyName = _property 生成成员变量
声明并实现 setPropertyName: 方法
声明并实现 getPeopertyName 方法

现在的LLVM编译器中,我们不用再在声明属性之后声明成员变量了。

LLVM工作过程是在我们声明过属性之后,去为我们自动生成一个带下划线“_”的 同名成员变量,也会为我们自动生成相关属性的 settergetter方法。

我们在 .m 文件 中调用 self.name 和调用 _name 是一样的。

点方法调用:如果点方法调用在 = 号右边,是调用相关的 getter方法;如果点方法调用在 = 号左边,是调用相关的 setter 方法。在OC 中 点方法调用就是 调用setter 方法和 getter方法的快捷方式。

** 注意:**


属性的默认修饰是@protected。
属性会自动生成set和get方法。
属性可以用点语法调用,点语法实际上调用的是 setter 和 getter 方法。

2.2 @synthesize —— 为属性指定成员变量

iOS 6 之后 LLVM 编译器引入 property autosynthesis —— 属性自动合成,就是编译器会为每个 @property 添加 @synthesize ,相当于编译器默认帮我们加入了:@synthesize propertyName = _propertyName;

之前我们在声明属性之后,都会在 .m 文件中使用 @synthesize 来指定与属性对应的成员变量。

比如如果用 @synthesize name = myName, 那我们在调用 self.name 实际上操作的就是 myName 这个成员变量了。此时,系统并没有生成也不会生成 _name 这个成员变量了。

总之: 如果有 @synthesize xxx 声明,就不会生成默认的成员变量,调用时会调用的是 xxx,如果没有 @synthesize xxx 的声明,那么编译器就会为属性生成一个默认的带下划线的同名成员变量。

2.3 注意!有一个小状况

我 Person 类里面有一个 NSString *name 属性,我重写了 name 的setter 和 getter 方法。

but!!!

报错???


Use of undeclared identifier '_name'; did you mean 'name'?

Use of undeclared identifier '_name'

找不到 "_name" ? 我明明声明了属性为什么找不到 _name? 上面的都是骗人的吗?

答案:

如果对一个属性同时重写 setter 和 getter 方法,实现了完全的自定义实现,那么系统将不再生成相应的成员变量,无法对应到默认的变量_propertyName,这时的解决办法是要么手动添加成员变量,或者利用 @synthesize 来指定一个成员变量来绑定到这个属性上。

①
{
	NSString *_name;          
}

或

②

@synthesize name = _name;

属性 name 原本是对应于_name变量的,这里通过@synthesize改变了属性、gettersetter对应的变量。

2.4 @dynamic -- 禁止属性自动合成

@dynamic 作用在于告诉编译器:属性的 setter 与 getter 方法由用户手动自己实现,编译器不要不自动生成。


#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (nonatomic,strong)NSString *name;
@property (nonatomic,strong)NSString *sex;
@end

//  Man.m
#import "Person.h"

@implementation Person
@dynamic name, sex;
@end

上述例子中,@dynamic name, sex 告诉了编译器不要自动为其生成属性 name 和 sex 的 setter 和 getter 方法,程序员会自己手动添加好的。但是,如果我们不手动添加,程序编译时也不会报错,但是当我们在运行时对 name 和 sex 进行set 或者 get 时,就会 carsh,因为无法找到对应的存取方法。

2.5 如何修改 readonly 属性的值

在上面 Person 类里面添加一个手机号属性

@property(nonatomic, copy,readonly) NSString *phoneNo;

如果直接调用 person.phoneNo = @"18600000000" 会出现错误提示这个是 readonly 的属性不支持 修改:

Assignment to readonly property

那么怎样修改一个 readonly 的属性呢?

2.5.1 用 KVC 修改 readonly 属性

如果我们调用 [person setValue:@"18668007692" forKey:@"phoneNo"]; 即可修改person的 readon-phoneNo 的值。具体详见 KVC。

2.5.2 创建子类-->新增属性-->@synthesize 修饰

创建一个继承于 Person 的子类 ChildPerson,新增一个属性 phoneNo,这样应该可以了吧~~~ 调用一下:

ChildPerson *childPerson = [[ChildPerson alloc] init];
childPerson.phoneNo = @"18600000000";

crash!!! what??? -[ChildPerson setPhoneNo:]: unrecognized selector sent to instance 0x281149ec0 找不到方法?

输出一下 ChildPerson 的方法列表,发现确实没有 setPhoneNo: 方法,怎么办?

加呗!加方法! 怎么加?

@synthesize phoneNo = _phoneNo !!!

利用它可以给我们声明出来 setter 和 getter方法。

@synthesize 用法见上面👆

2.5.3 创建分类--> 新增属性 --> 手动添加 setter 和 getter

这个办法说到底也还是增加 setter 方法,因为崩错也就是因为找不到 setter 方法罢了。

2.6 protocol 中声明属性

在protocol 中声明并实现属性时,协议中声明的属性 不会自动生成setter和getter

需要使用 @synthesize 生成setter和getter。

例如 [UIApplicationDelegate window]

3、 给 Category 添加属性

我为 Person 类创建了一个 Category文件 —— Person+AddCategory,为此文件添加了一个属性 @property(nonatomic, copy) NSString *sex。 此时,.m 文件中会有一串⚠️:

Property 'sex' requires method 'setSex:' to be defined - use @dynamic or provide a method implementation in this category

此刻我选择性看不见这个警告,愉快的在某个控制器里写下了下列方法并调用:

- (void)objcCategoryAddProperty {
    Person *person = [[Person alloc] init];
    person.sex = @"Male";
    NSLog(@"性别:%@",person.sex);
}

嘿嘿?crash 了!

reason: '-[Person setSex:]: unrecognized selector sent to instance 0x283ac6be0'

那就是打开的方式不对呗

正确的打开方式:

① 在 Person+AddCategory 文件中导入 #import <objc/runtime.h>

② 在.m文件为我们的 sex 属性添加一个key

static NSString * const ksexKey = @"sexKey";

③ 重写 setter 和 getter 方法

- (void)setSex:(NSString *)sex {
    objc_setAssociatedObject(self, &ksexKey, sex, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)sex {
   return objc_getAssociatedObject(self, &ksexKey);
}

④ 大功告成

** Tips **

setSex : 里面使用了一个objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)方法,这个方法有四个参数,分别是:

参数含义
object源对象(self)
key关联时的用来标记是哪一个属性的key
value关联的对象(sex)
policy一个关联策略(OBJC_ASSOCIATION_COPY_NONATOMIC)

关联策略:

策略参数同义于
OBJC_ASSOCIATION_ASSIGN@property(assign)
OBJC_ASSOCIATION_RETAIN_NONATOMIC@property(strong, nonatomic)
OBJC_ASSOCIATION_COPY_NONATOMIC@property(copy, nonatomic)
OBJC_ASSOCIATION_RETAIN@property(strong,atomic)
OBJC_ASSOCIATION_COPY@property(copy, atomic)