在不使用@property来封装数据使用,Objective-C对象通常会把其所需要的数据保存为各种实例变量,我们先来看实例变量的用法:
OC中实例变量的定义:
实例变量可以定义在:类接口,匿名扩展。
注意:不能在类别中定义实例变量
//类接口中实例变量的定义
@interface MyClass : NSObject {
@public
NSString *publicString;
@private
NSString *_privateString;
}
@end
//匿名扩展中实例变量的定义
@interface MyClass() {
NSString *_categoryString;
}
@end实例变量的访问:
- (instancetype)init {
if (self = [super init]) {
_categoryString = @"categoryString";
_privateString = @"privateString";
publicString = @"publicString";
}
return self;
}在外部只能访问到"@public"定义的实例变量:
MyClass *obj = [MyClass new];
obj->publicString = @"modify publicString";setter和getter方法
- (NSString *)implementString {
return _implementString;
}
- (void)setImplementString:(NSString *)string {
_implementString = [string copy];
}读取和写入变量:
[self implementString];
[self setImplementString:@"publicString"];属性
在实际的OC编码中我们很少像上面那样用实例变量,我们大多数时候用的是属性,之所以写上面的实例变量是为了更清楚的知道属性为我们做了什么。
引入《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法》中第6条:理解”属性“这一概念:
- 属性(property)是Objective-C的一项特性,用于封装对象中的数据。Objective-C对象通常会把其所需要的数据保存为实例变量
- 可以令编译器自动编写与实例变量相关的存取方法。
- 引入了一种新的"."语法,使开发者可以更容易的访问和存放数据。
把实例变量定义为属性后:
@interface MyClass : NSObject
@property (nonatomic, copy) NSString *publicString;
@end
@interface MyClass()
@property (nonatomic, copy) NSString *privateString;
@property (nonatomic, copy) NSString *categoryString;
@property (nonatomic, copy) NSString *implementString;
@end
@implementation MyClass
- (instancetype)init {
if (self = [super init]) {
self.categoryString = @"categoryString";
_privateString = @"privateString";
_implementString = @"_implementString";
}
return self;
}由上可以看出编译器自动向类中添加适当类型的实例变量,并且在属性名前面加"_",以此作为实例变量的名字。
我们也可以修改它创建的实例变量的名字,比如我想让属性“privateString”自动创建的实例变量名为"_myPrivateString",可以用以下方法实现:
@implementation MyClass
//更改实例变量名
@synthesize privateString = _myPrivateString;
- (instancetype)init {
if (self = [super init]) {
//可以用自定义的实例变量名来访问了
_myPrivateString = @"myPrivateString";
//但是不影响存取方法的名字
self.privateString = @"privateString";
_implementString = @"_implementString";
}
return self;
}
@end我们也可以使用@dynamic关键字,告诉编译器不要自动创建实例变量,也不要为其创建存取方法。而且在编译访问属性的代码时,即使编译器没有发现存储方法也不会报错,因为它相信这些方法能在运行时找到。示例:
@implementation MyClass
@synthesize privateString = _myPrivateString;
@dynamic publicString;
- (instancetype)init {
if (self = [super init]) {
_categoryString = @"categoryString";
_myPrivateString = @"privateString";
_implementString = @"_implementString";
self.publicString = @"publicString";
NSLog(@"%@", self.publicString);
}
return self;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString isEqualToString:@"setPublicString:"]) {
class_addMethod(self, sel, (IMP)setPublicString, "v@:@");
NSLog(@"add %@", selectorString);
return YES;
} else if([selectorString isEqualToString:@"publicString"]) {
class_addMethod(self, sel, (IMP)publicString, "@@:");
NSLog(@"add %@", selectorString);
return YES;
}
return [super resolveInstanceMethod:sel];
}
static void *publicStringKey = "publicStringKey";
void setPublicString(id self, SEL _cmd, NSString *publicString) {
MyClass *typedSelf = (MyClass *)self;
objc_setAssociatedObject(typedSelf, publicStringKey, publicString, OBJC_ASSOCIATION_COPY);
}
id publicString(id self, SEL _cmd) {
MyClass *typedSelf = (MyClass *)self;
return objc_getAssociatedObject(typedSelf, publicStringKey);
}
@end打印结果:
**2016-12-13 00:34:16.736 IOSAPILearn[41692:15613165] add setPublicString:**
**2016-12-13 00:34:16.739 IOSAPILearn[41692:15613165] add publicString**
**2016-12-13 00:34:16.740 IOSAPILearn[41692:15613165] publicString**可以看出"."存取,分别访问了setPublicString:和publicString方法。
属性特性
- 原子性:atomic或者nonatomic。在默认情况下, 由编译器所合成的方法会通过锁定机制确保其原子性。如果属性具有nonatomic(非原子)特性,则不使用同步锁。
- 读/写权限:readwrite或者readonly。由@synthesize实现(非@dynamic实现)的readwrite属性,编译器自动生成getter和setter方法。由@synthesize实现的readonly属性,编译器只为其合成setter方法,可以对外公开为readonly,然后在匿名类别中声明为readwrite。
- 内存管理语义:
| 属性声明的属性 | 所有权修饰符 |
|---|---|
| assign | __unsafe_unretained修饰符 |
| copy | __strong修饰符(但是赋值的是被复制的对象) |
| retain | __strong修饰符 |
| strong | __strong修饰符 |
| unsafe_unretained | __unsafe_unretained修饰符 |
| weak | __weak修饰符 |
- 方法名:getter=和setter=指定“获取方法(getter)”和“设置方法(setter)”的方法名。
示例:
@interface MyClass : NSObject
//设置getter和setter方法名,内存管理语义为copy,非原子特性
@property (nonatomic, copy, getter=mypublicString, setter=setMypublicString:) NSString *publicString;
//设置只读权限,非原子特性,内存管理语义为copy
@property (nonatomic, copy, readonly) NSString *implementString;
@end
@interface MyClass()
@property (nonatomic, copy, readonly) NSString *privateString;
@property (atomic, copy, readwrite) NSString *categoryString;
//在匿名类别中将该属性重新设置为可读写
@property (nonatomic, copy, readwrite) NSString *implementString;
@end
@implementation MyClass
@synthesize privateString = _myPrivateString;
@dynamic publicString;
- (instancetype)init {
if (self = [super init]) {
//此时mypublicString和publicString都可以访问,但调用的setter和getter方法是一样的,看下面的打印结果
self.mypublicString = @"mypublicString";
NSLog(@"%@", self.mypublicString);
self.publicString = @"publicString";
NSLog(@"%@", self.publicString);
}
return self;
}
//重写setter时,要遵循内存管理语义特性。
- (void)setImplementString:(NSString *)implementString {
_implementString = [implementString copy];
}
//@dynamic publicString 中的存取方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString isEqualToString:@"setMypublicString:"]) {
class_addMethod(self, sel, (IMP)setPublicString, "v@:@");
NSLog(@"add %@ method", selectorString);
return YES;
} else if([selectorString isEqualToString:@"mypublicString"]) {
class_addMethod(self, sel, (IMP)publicString, "@@:");
NSLog(@"add %@ method", selectorString);
return YES;
}
return [super resolveInstanceMethod:sel];
}
static void *publicStringKey = "publicStringKey";
void setPublicString(id self, SEL _cmd, NSString *publicString) {
MyClass *typedSelf = (MyClass *)self;
objc_setAssociatedObject(typedSelf, publicStringKey, publicString, OBJC_ASSOCIATION_COPY);
NSLog(@"use setter method: setMypublicString:");
}
id publicString(id self, SEL _cmd) {
MyClass *typedSelf = (MyClass *)self;
NSLog(@"use getter method: mypublicString");
return objc_getAssociatedObject(typedSelf, publicStringKey);
}
@end打印结果:
**2016-12-13 10:05:19.454 IOSAPILearn[43443:15723711] add setMypublicString: method**
**2016-12-13 10:05:19.460 IOSAPILearn[43443:15723711] use setter method: setMypublicString:**
**2016-12-13 10:05:19.461 IOSAPILearn[43443:15723711] add mypublicString method**
**2016-12-13 10:05:19.462 IOSAPILearn[43443:15723711] use getter method: mypublicString**
**2016-12-13 10:05:19.462 IOSAPILearn[43443:15723711] mypublicString**
**2016-12-13 10:05:19.464 IOSAPILearn[43443:15723711] use setter method: setMypublicString:**
**2016-12-13 10:05:19.465 IOSAPILearn[43443:15723711] use getter method: mypublicString**
**2016-12-13 10:05:19.465 IOSAPILearn[43443:15723711] publicString**混合用法
属性是对实例变量的一种封装,属性内部自动生成了于之对应的实例变量,然后通过设置属性特性来封装这个实例变量:
- 根据读写权限和getter/setter方法名自动生成相对应的存取方法
- 通过内存管理语义影响设置方法(setter)的实现(比如是否要retain,是否要copy等)
- 通过原子性来判断读写时,是否要加锁
这些属性自动封装的实现都是编译器做的。
其实我们可以把实例变量和属性搭配着一起使用:
比如我在匿名类别中定义了一个属性:
@interface MyClass() {
NSString *_categoryString;
}
@end在没有写所有权修饰符时,默认为__strong修饰符
然后我希望编译器为这个实例变量自动生成存取方法,并可以通过"."来访问。那我只需要定义一个同名属性:
@interface MyClass() {
NSString *_categoryString;
}
//属性会自动生成getter方法,通过copy语义生成setter方法
@property (copy) NSString *categoryString;
@end注意:变量与属性声明中的内存管理语义中的所有权修饰符必须一致,否则会引起编译错误,例如属性如果声明为:
@property (weak) NSString *categoryString,则会提示错误:Existing instance variable '_categoryString' for __weak property 'categoryString' must be __weak
在重写setter方法时,要遵循内存管理语义:
void setCategoryString:(NSString *)categoryString {
_categoryString = [categoryString copy];
}简书个人主页:www.jianshu.com/users/b92ab…