@property的本质
@property = ivar(实例变量) + getter/setter(存取方法);
- @property有两个对应的词,一个是@synthesize,一个是@dynamic。如果@synthesize和@dynamic都没写,那么默认的就是@syntheszie var = _var;
- @synthesize的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法
- @dynamic告诉编译器:属性的setter与getter方法由用户自己实现,不自动生成。(当然对于readonly的属性只需提供getter即可)。假如一个属性被声明为@dynamic var,然后你没有提供setter方法和getter方法,编译的时候没问题,但是当程序运行到 instance.var = someVar, 由于缺setter方法会导致程序崩溃; 或者当运行到 someVar = var 时, 由于缺 getter 方法同样会导致崩溃. 编译时没问题, 运行时才执行相应的方法, 这就是所谓的动态绑定.
演示使用:
#import
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
NS_ASSUME_NONNULL_END
------------------------------------------------
#import "Person.h"
@implementation Person
//@synthesize name = _name;//默认
//@synthesize name = _name123;//生成_name123成员变量;在自己的.m中使用需要写_name123或者self.name,在其他类中调用只能写self.name,只两个是同一个东西
//@synthesize name;//生成name成员变量
//@dynamic name;//没有了_name这个成员变量
@end
(lldb) image lookup -t Person
0 match found in /Users/dengyazhouPro/Library/Developer/Xcode/DerivedData/DemoOC-aqhzxuxjddtuwnfvagfffmcnhzmb/Build/Products/Debug-iphoneos/DemoOC.app/DemoOC:
id = {0x100000002b}, name = "Person", byte-size = 16, decl = Person.h:14, compiler_type = "@interface Person : NSObject{
NSString * _name;
}
@property(nonatomic, copy, readwrite, getter = name, setter = setName:) NSString *name;
@end"
添加,@synthesize name = _name123;
,没有了_name
这个成员变量,而是把_name
换成了_name123
(lldb) image lookup -t Person
0 match found in /Users/dengyazhouPro/Library/Developer/Xcode/DerivedData/DemoOC-aqhzxuxjddtuwnfvagfffmcnhzmb/Build/Products/Debug-iphoneos/DemoOC.app/DemoOC:
id = {0x100000002b}, name = "Person", byte-size = 16, decl = Person.h:14, compiler_type = "@interface Person : NSObject{
NSString * _name123;
}
@property(nonatomic, copy, readwrite, getter = name, setter = setName:) NSString *name;
@end"
添加,@dynamic name;
,没有了_name
这个成员变量
(lldb) image lookup -t Person
0 match found in /Users/dengyazhouPro/Library/Developer/Xcode/DerivedData/DemoOC-aqhzxuxjddtuwnfvagfffmcnhzmb/Build/Products/Debug-iphoneos/DemoOC.app/DemoOC:
id = {0x100000001f}, name = "Person", byte-size = 8, decl = Person.h:14, compiler_type = "@interface Person : NSObject
@property(nonatomic, copy, readwrite, getter = name, setter = setName:) NSString *name;
@end"
例如:
#import
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
NS_ASSUME_NONNULL_END
------------------------------------------------
#import "Person.h"
@implementation Person
@dynamic name;
@end
当执行到:
Person *person = [[Person alloc] init];
person.name = @"dengyazhou";
程序就会crash,原因是“[People setName:]: unrecognized selector sent to instance”。
解决这种奔溃的方法有三种:
方法一:
最简单粗暴,但是还是挺管用,直接注释掉@dynamic name这行代码即可,由编译器自动添加。
但是如果我们不想让编译器自动添加,那么我们可以手动添加或则在运行时添加都可以。
方法二:
手动添加,由于@dynamic不能像@synthesize那样向实现文件(.m)提供实例变量,所以我们需要在类中显式提供实例变量。
#import
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
NS_ASSUME_NONNULL_END
------------------------------------------------
#import "Person.h"
@interface Person () {
NSString *_name;
}
@end
@implementation Person
@dynamic name;
- (void)setName:(NSString *)name {
_name = [name copy];
}
- (NSString *)name {
return _name;
}
@end
方法三:
通过runtime机制在运行时添加属性的存取方法。
在C函数中不能直接使用实例变量,需要将Objc对象self转成C中的结构体,因此在Person类同样需要显式声明实例变量而且访问级别是@public
#import
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
NS_ASSUME_NONNULL_END
------------------------------------------------
#import "Person.h"
#include
@interface Person () {
@public
NSString *_name;
}
@end
@implementation Person
@dynamic name;
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(setName:)) {
class_addMethod([self class], sel, (IMP)setName, "v@:@");
return YES;
} else if (sel == @selector(name)) {
class_addMethod([self class], sel, (IMP)getName, "@@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void setName(id self, SEL _cmd, NSString* name) {
if (((Person*)self)->_name != name) {
((Person*)self)->_name = [name copy];
}
}
NSString* getName(id self, SEL _cmd) {
return ((Person*)self)->_name;
}
@end