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工作过程是在我们声明过属性之后,去为我们自动生成一个带下划线“_”的 同名成员变量,也会为我们自动生成相关属性的 setter 和 getter方法。
我们在 .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改变了属性、getter、setter对应的变量。
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) |