①语言Swift

修饰词的作用

个人觉得是为了对各种对象进行内存管理而产生的。

Objective-C 中的 assign、strong、weak、copy 等修饰词(属性关键字)主要是为了解决 iOS 内存管理(Memory Management)

1、内存的存储位置(堆、栈、全局区、常量区)

1.1、所有 alloc, new, copy, mutableCopy 创建的对象都会存储在堆上

1
2
3
4
UIViewController *vc = [[UIViewController alloc] init];
• vc 是栈上的指针,指向堆上的对象。
• 这个 UIViewController 实例存储在堆上,并且受**引用计数(ARC/手动管理)**控制。
• 只有当 vc 及其所有强引用都释放时,该对象才会被销毁。

1.2、基本数据类型(如 int、float、char、BOOL、double)通常存储在栈上

1
2
3
4
5
6
7
8
9
void testFunction() {
int a = 10; // a 存在栈上
int b = 20; // b 存在栈上
} // 函数执行完毕后,a 和 b 自动销毁
• a 和 b 直接存储在栈上,因为它们的大小固定,生命周期由作用域决定。
• 当函数执行完毕后,栈上的变量会自动释放。

int globalVar = 100; // 存在全局区
static int staticVar = 200; // 存在全局区

但如果基本数据类型是作为对象的属性,且对象在堆上,那么它们也在堆上

1
2
3
4
5
6
7
8
@interface MyObject : NSObject
@property (nonatomic, assign) int age;
@end

MyObject *obj = [[MyObject alloc] init];
obj.age = 25;
• obj 是栈上的指针,指向堆上的 MyObject 实例。
• age 是 obj 的成员变量,它作为 MyObject 的一部分,也存储在堆上。

1.3、普通 NSString 在常量区

1
2
3
4
NSString *str = @"Hello, Objective-C!";
• str 是一个指针变量,如果它是局部变量,那么它存储在栈上。
@"Hello, Objective-C!" 这个字符串是Objective-C 的字符串字面量,存储在常量区(只读数据段),不会存储在堆上。
• str 只是一个指向 @"Hello, Objective-C!" 的指针,不影响该字符串的存储位置。

堆栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)testBlock {
void (^myBlock1)(void) = ^{
NSLog(@"Hello, World!");
};
self.myBlock = myBlock1;

UIViewController *vc = [[UIViewController alloc] init];
}

• myBlock1 是存储在栈上
• vc 是栈上的局部变量。
• vc 指向堆上的 UIViewController 实例。

myBlock1 和 vc 都会在方法结束时候释放掉,因为他们是栈上的,内存由栈管理,而不是引用计数。
所以为了避免 block 的销毁,所以我们需要使用 copy 将 block 从栈上拷贝一份到堆上。( • alloc, new, copy 创建的对象:这些对象通常是 分配到堆上 的。)

2、内存的管理

Strong: 创建的时候引用计数即为1,后续该对象本其他对象持有的话则引用计数会再增加1,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@property (nonatomic, strong) Person *person1;
@property (nonatomic, strong) Person *person2;

- (void)checkRetainCount {
Person *person = [[Person alloc] init]; // (1) person 的引用计数 = 1
Person *temp1 = person; // (2) temp1 仅仅是指针赋值,不增加引用计数
self.person1 = person; // (3) self.person1 strong 持有 person,引用计数 +1,变为 2
self.person2 = person; // (4) self.person2 strong 持有 person,引用计数 +1,变为 3
} // (5) 方法结束,局部变量 person 和 temp1 作用域结束,ARC 自动释放 person,引用计数 -1,变为 2

// 问:person 、temp1 、 person1 、 person2 这几个的引用计数是分别是多少。
// 答:它们都指向 person 这个对象,所以最后的引用计数都是一样的,都是那个对象的引用计数,在方法执行最后前是3,放完执行完后事2

// 问:
// 答:退出页面的时候,路由栈等不持有self的时候,会

delloc/引用计数怎么变为0的?

页面退出(pop、dismiss)的时候,路由栈不再持有页面self。从而页面self的retainCount==0,继而触发self的delloc(请记住是这个因果关系)。
delloc是self的引用计数为0而触发的,而不是self被销毁而触发的或者说delloc的时候self还未被真正销毁,所以你可以在delloc中调用self。那self的真正销毁是self的delloc执行完之后(在其内部调用super delloc后)。

1、self触发delloc的时候,会依次对持有的每个数据(person1、person2)进行relase操作。如果release后person对象的引用计数为0,则person也会被释放。如果person还被其他引用,如UserManager则只是引用计数减去1而已。(题外话:self的释放关注的是self自己被不被其他对象持有,而不是self持有的对象是否全都被释放)

2、因为self执行delloc的时候self并未被真正销毁,所以当页面退出的时候,如果timer的block中又未持有self的时候(一般用weakSelf),你可以在delloc中执行 [timer invalidate] 来将 timer 从Runloop中移除。

1
2
3
4
- (void)delloc {
[self.timer invalidate];
self.timer = nil; // 不做此步的话,timer这个指针仍然指向之前的内存地址。在ARC下不会有问题。但如果是MRC则做完[self.timer release]后,不做self.timer = nil 的操作,继续调用self.timer 的其他方法会导致野指针问题。即指针访问了一块已经释放的内存。指针内容请看 《2内存-①基础.md》 未初始化的指针不是空指针,而是野指针。
}

delloc流程代码示例见: https://github.com/dvlproad/CJOptimizeProject/blob/main/CJOptimizeProject/TSDemo_Optimize/Leak/TSDellocViewController.m

为什么weak能避免循环引用?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@interface ViewController : UIViewController
@property (nonatomic, strong) ChildViewController *childVC;
@end

@interface ChildViewController : UIViewController
@property (nonatomic, strong) ViewController *delegate; // ⚠️ strong delegate
@end

- (void)viewDidLoad {
[super viewDidLoad];

self.childVC = [[ChildViewController alloc] init]; // self 持有 childVC,childVC 的 retainCount = 1
self.childVC.delegate = self; // 如果delegate使用strong,则childVC 强制持有 self,则self 的 retainCount = 2,而退出页面的时候路由栈只对self的retainCount-1,剩下的还有1,导致self无法释放,除非你在disappear中再对self==nil,但会有其他问题。
// 而如果delegate使用 weak , 则执行self.childVC.delegate = self;时候不会改变self的引用计数,其仍为1,也就能在页面退出时候变为0,从而触发delloc。
}

使用修饰符来解决内存问题,只使用weak和strong不够吗?为什么还要assign和copy等?

非对象类型(如 int、float)根本没有引用计数的概念,它们是值类型,不是引用类型。所以在设计上增加

新的修饰符来处理更合适。而其他的weak、strong、copy等都有引用计数的概念,只是会不会改变引用计数而已。

另外在 Objective-C 中:

​ • 对象类型(NSObject 及其子类)是存储在堆(Heap)上的指针,需要 ARC 进行引用计数管理。

​ • 非对象类型(int、float、BOOL 等)是存储在栈(Stack)或者全局数据区,不需要 ARC 进行管理。

而copy修饰符主要是为了功能的考虑,让其拷贝对象,保证内容不变,即让属性存储的是原始值的拷贝,而不是原来的引用。防止可变对象被修改。不然操作的时候可能不小心操作。

关键字 适用场景 是否增加 retainCount 备注
assign 基本数据类型、struct、C 指针 ❌ 不会 适用于 int、float、BOOL、struct 等
strong 默认对象引用 ✅ 会 适用于普通对象
weak 避免循环引用 ❌ 不会 对象释放后自动置 nil,适用于 delegate、IBOutlet 等
copy 不希望对象内容被修改 ✅ 会 适用于 NSString、NSArray、NSDictionary 等

🚨 问题:多个线程对不同属性进行读写

✅ atomic 只能保证单个属性的 getter/setter 是线程安全的。⚠️ 但 atomic 并不是真正的多线程安全!

多个属性的操作(如 firstName 和 lastName 一起更新)不会自动是原子性的,会导致数据不一致。

🛠 解决方案:使用 @synchronized、GCD dispatch_barrier_async 等手段确保完整性。

🔍 线程 A & 线程 B 的并发访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@interface Person : NSObject
@property (atomic, strong) NSString *firstName;
@property (atomic, strong) NSString *lastName;
@end



Person *person = [[Person alloc] init];
// 线程 A 修改 firstName 和 lastName
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
person.firstName = @"Alice";
person.lastName = @"Smith";
});

// 线程 B 读取 firstName 和 lastName
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"%@ %@", person.firstName, person.lastName);
});

⚠️ 可能出现的问题

​ • 线程 B 读取 firstName 时,线程 A 可能只完成了 firstName = @”Alice”,但 lastName 还未更新,此时打印可能会是:

1
Alice (null)

🛠 解决方案:将整个过程加锁,使得整个赋值操作是线程安全的,不会出现部分属性更新的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 方法1: 通过 @synchronized 使多个属性的操作成为原子性**
- (void)setFullNameWithFirstName:(NSString *)firstName lastName:(NSString *)lastName {
@synchronized (self) {
_firstName = firstName;
_lastName = lastName;
}
}

// 方法2:使用 GCD 的 dispatch_barrier_async 保证在并发队列中,多个属性的操作作为一个整体执行,不会被其他线程打断。
dispatch_queue_t queue = dispatch_queue_create("com.example.personQueue", DISPATCH_QUEUE_CONCURRENT);
- (void)setFullNameWithFirstName:(NSString *)firstName lastName:(NSString *)lastName {
dispatch_barrier_async(queue, ^{
_firstName = firstName;
_lastName = lastName;
});
}

一、Copy

修饰符 作用 返回类型
copy 复制为 不可变对象 NSString、NSArray、NSDictionary
mutableCopy 复制为 可变对象 NSMutableString、NSMutableArray、NSMutableDictionary
  1. 如何让自己的类用copy修饰符

    若想令自己所写的对象具有拷贝功能,则需实现NSCopying协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现NSCopyingNSMutableCopying协议。
    具体步骤:
    1.需声明该类遵从NSCopying协议
    2.实现NSCopying协议的方法,具体区别戳这里

  • NSCopying协议方法为:
1
2
3
4
5
- (id)copyWithZone:(NSZone *)zone {
MyObject *copy = [[[self class] allocWithZone: zone] init];
copy.username = self.username;
return copy;
}

1、浅拷贝和深拷贝的区别

浅层复制:只复制指向对象的指针,而不复制引用对象本身。
深层复制:复制引用对象本身。

意思就是说我有个A对象,复制一份后得到A_copy对象后,

对于浅复制来说,A和A_copy指向的是同一个内存资源,复制的只不过是是一个指针,对象本身资源还是只有一份,那如果我们对A_copy执行了修改操作,那么发现A引用的对象同样被修改,这其实违背了我们复制拷贝的一个思想。
而对于深复制就好理解了,内存中存在了两份独立对象本身。

通俗的话将就是:
浅复制好比你和你的影子,你完蛋,你的影子也完蛋;
深复制好比你和你的克隆人,你完蛋,你的克隆人还活着。

2、 iOS - Copy 与 MutableCopy

iOS - Copy 与 MutableCopy
①copy:因为copy默认返回的是不可变的,所以当我们对一个不可变的字符串进行copy的时候,我们只是拷贝了它的指针(浅拷贝)。当我们对一个可变的字符串进行拷贝的时候,因为类型转变了,我们需对其进行深拷贝。
mutableCopy:默认返回的是一个可变的对象,适用于可变的对象,例如NSMutableString,NSMutableArray,NSMutableDictionary、etc。 无论对于可变的字符串还是不可变的字符串进行mutableCopy,系统都默认进行深拷贝。

其他参考

(1)、copy到底是深拷贝还是浅拷贝?

当我们对一个不可变对象(NSString类型)使用copy关键字的时候,系统是不会产生一个新对象,因为原来的对象已经是不能修改的,拷贝出来的对象也是不能修改的,那么既然两个都不可以修改,所以这两个对象永远也不会影响到另一个对象(符合我们说的“修改新(旧)对象,不影响旧(新)对象”原则),系统为了节省内存,所以就不会产生一个新的对象了。那么问题来了,copy到底是深拷贝还是浅拷贝?答:是否是深浅拷贝,是否创建新的对象,是由程序运行的环境所造成的,并不是一概而论。

(2)、这个写法会出什么问题@property (nonatomic, copy) NSMutableArray *mutableArray;

添加,删除,修改数组内元素的时候,程序会因为找不到对应的方法而崩溃。原因:copy修饰符代表赋值结果是要返回一个不可变的值,即是NSArry类。

self.mutableArray = xxx;在copy的修饰下执行的是 self.mutableArray = [xxx copy];进行了浅拷贝,得到的是一个xxx的副本,且该副本是一个不可变的数组。导致在运行的时候,其实你的mutableArray已经是NSArray类了。从而在添加,删除,修改数组内元素的时候,程序会因为找不到对应的方法而崩溃。

3、为什么NSArray用copy修饰、NSMutableArray用strong修饰

把NSArray用strong修饰,会导致NSArray可能会被修改。

把NSMutableArray用copy修饰有时就会crash,因为copy后的数组变成了不可变数组NSArray.当你对不可变数组NSArray进行增删改操作的时候就会crash,
举例如下:

1
2
3
4
5
6
7
①NSMutableArray用copy属性造成的crash:
@property (nonatomic, copy) NSMutableArray *mutableArray1; // 会崩溃
@property (nonatomic, strong) NSMutableArray *mutableArray2; // 正确

NSMutableArray *array = [NSMutableArray arrayWithArray:@[[Model1 new], [Model1 new]]];
self.mutableArray1 = array; // copy: mutableArray1是array的副本,且通过打印验证是浅拷贝。且此时aArray的值mArray的一个副本,该副本是通过[mArray copy]进行的浅拷贝得到的一个不可变新对象,即类型在执行时候变为了 NSArray了。所以,如果对归属为NSArray了的aArray执行NSMutableArray才有的如removeAllObjects的方法时,就会崩溃。
self.mutableArray2 = array; // strong:mutableArray1是array自身

当修饰可变类型的属性时,如NSMutableArray、NSMutableDictionary、NSMutableString,用strong。

当修饰不可变类型的属性时,如NSArray、NSDictionary、NSString,用copy。

4、模型数组深拷贝

通常需要实现对模型的拷贝都需要先实现NSCopying、 NSMutableCopying协议。注意:如果是数组使用拷贝操作是不会对数组内实现copy协议的对象进行深拷贝的。

参考文章:iOS 模型数组深拷贝

1、最笨的方法就是通过遍历逐个拷贝元素

1
2
3
4
NSMutableArray *array = [NSMutableArray array];
for (Person *person in dataSourceAry) {
[array addObject:[person copy]];
}

2、也有人使用归档解档实现数组内部元素拷贝

3、这么好用的一个方法现在才发现(推荐)

1
- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag 
1
2
3
4
5
6
7
8
NSArray <Person *>*deepCopyAry = [[NSArray alloc]initWithArray:dataSourceAry copyItems:YES];
NSLog(@"<dataSourceAry: %@>", dataSourceAry);
NSLog(@"<deepCopyAry: %@>", deepCopyAry);

[deepCopyAry enumerateObjectsUsingBlock:^(Person *obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.name = @"弗兰克";
obj.dog.name = @"弗兰克的dog";
}];

4、分析NSString、NSMutableString等类的copy、mutableCopy

在语言文章中,我们已经说明对于语句NSString *obj = [[NSData alloc] init]; obj在编译时是NSString的类型;运行时是NSData类型的对象。

由此得出的结论是,不要被编译时的类型蒙蔽,还要看实际运行时的类型。

(1)、分别对NSString、NSMutableString进行copy、mutableCopy生成的类型是什么?

你可以片面理解为

copy是[NSString alloc],所以生成的都是不可变的;
mutableCopy是[NSMutableString alloc],所以生成的都是可变的;

所以以下代码的结果,即为代码中的注释一样

1
2
3
4
5
6
7
8
9
>NSString *str1 = @"test001";

>NSMutableString *str2 = [str1 copy];
>//编译时,str2是NSMutableString类型。因为是把str2声明为可变字符串,所以str2即为声明的可变字符串
>//运行时,str2是NSString类型。因为是copy,所以不管str1是可变不可变,str2都是不可变字符串

>NSMutableString *str3 = [str1 mutableCopy];
>//编译时,str3是NSMutableString类型。因为是把str3声明为可变字符串,所以str3即为声明的可变字符串
>//运行时,str3是NSMutableString类型。因为是mutableCopy,所以不管str1是可变不可变,str3都是可变字符串
(2)、分别对NSString、NSMutableString进行copy、mutableCopy操作,是否会开辟新地址,即是属于深拷贝还是浅拷贝?

copy:对[string copy]是浅拷贝,即不会开辟新地址,而对[mutableString copy]是深拷贝,会开辟新地址。

mutableCopy:不管是对[string mutableCopy],还是对[mutableString mutableCopy],都是深拷贝,都会开辟新地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)testCopy {
NSString *testStr = [NSString stringWithFormat:@"123"];
id copyedStr = [testStr copy];
id mutableCopyStr = [testStr mutableCopy];
NSLog(@"=== 对NSString变量进行copy、mutableCopy得到的地址和类型分别是 ===");
NSLog(@"testStr = %p, class: %@", testStr, NSStringFromClass([testStr class]));
NSLog(@"copyedStr = %p, class: %@", copyedStr, NSStringFromClass([copyedStr class]));
NSLog(@"mutableCopyStr = %p, class: %@", mutableCopyStr, NSStringFromClass([mutableCopyStr class]));

NSLog(@"\n");

NSMutableString *testMutableStr = [NSMutableString stringWithFormat:@"mutable_456"];
id copyedMutableStr = [testMutableStr copy];
id mutableCopyMutableStr = [testMutableStr mutableCopy];
NSLog(@"=== 对NSMutableString变量进行copy、mutableCopy得到的地址和类型分别是 ===");
NSLog(@"testMutableStr = %p, class: %@", testMutableStr, NSStringFromClass([testMutableStr class]));
NSLog(@"copyedMutableStr = %p, class: %@", copyedMutableStr, NSStringFromClass([copyedMutableStr class]));
NSLog(@"mutableCopyMutableStr = %p, class: %@", mutableCopyMutableStr, NSStringFromClass([mutableCopyMutableStr class]));
}

对NSString、NSMutableString变量进行copy、mutableCopy得到的地址和类型分别是

结论:分别对NSString、NSMutableString进行copy、mutableCopy操作,只有NSString copy是浅拷贝

(3)、将NSString、NSMutableString变量赋值给用copy、strong修饰NSString属性的时候,是否会开辟新地址,即是属于深拷贝还是浅拷贝?(附:NSString用copy修饰是为什么)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@property (nonatomic, copy) NSString *copyedStr;
@property (nonatomic, strong) NSString *strongStr;

@property (nonatomic, copy) NSString *copyedMutableStr;
@property (nonatomic, strong) NSString *strongMutableStr;

///将NSString、NSMutableString变量赋值给用copy、strong修饰的NSString属性的时候
- (void)testCopy {
NSString *testStr = [NSString stringWithFormat:@"123"];
self.copyedStr = testStr;
self.strongStr = testStr;
NSLog(@"=== 将NSString变量赋值给用copy、strong修饰的NSString属性的时候 ===");
NSLog(@"testStr = %p", testStr);
NSLog(@"copyedStr = %p", self.copyedStr);
NSLog(@"strongStr = %p", self.strongStr);

NSLog(@"\n");

NSMutableString *testMutableStr = [NSMutableString stringWithFormat:@"mutable_456"];
self.copyedMutableStr = testMutableStr;
self.strongMutableStr = testMutableStr;
NSLog(@"=== 将NSMutableString变量赋值给用copy、strong修饰的NSString属性的时候 ===");
NSLog(@"testMutableStr = %p", testMutableStr);
NSLog(@"copyedMutableStr = %p", self.copyedMutableStr);
NSLog(@"strongMutableStr = %p", self.strongMutableStr);
}

将NSString、NSMutableString赋值给用copy、strong修饰的NSString变量

可以看出,

①将NSString变量赋值给用copy、strong修饰的NSString属性的时候,不管是strong还是copy属性的对象,其指向的地址都是同一个,即为string指向的地址。

1
2
3
4
5
6
@property (nonatomic, copy) NSString *copyedStr;
@property (nonatomic, strong) NSString *strongStr;

NSString *testStr = [NSString stringWithFormat:@"123"];
self.copyedStr = testStr;
self.strongStr = testStr;

②将NSMutableString变量赋值给用copy、strong修饰的NSString属性的时候,

此时copy属性字符串copyedMutableStr

1
2
3
4
@property (nonatomic, copy) NSString *copyedMutableStr;

NSMutableString *testMutableStr = [NSMutableString stringWithFormat:@"mutable_456"];
self.copyedMutableStr = testMutableStr;

深拷贝了testMutableStr字符串,并让copyedMutableStr对象指向这个字符串(即copyedMutableStr和testMutableStr只是对象值一样,但不是同一个了)。所以此时,我们如果去修改testMutableStr字符串的话,可以看到,我们用@property (nonatomic, copy) NSString *copyedMutableStr;修饰的copyedMutableStr能够不会因为其赋值源testMutableStr的改变而改变,也就保证了安全性。

而strong属性字符串strongMutableStr

1
2
3
4
@property (nonatomic, strong) NSString *strongMutableStr;

NSMutableString *testMutableStr = [NSMutableString stringWithFormat:@"mutable_456"];
self.strongMutableStr = testMutableStr;

因为strongMutableStr与testMutableStr是指向同一对象,所以strongMutableStr的值也会跟随着改变;

综上:所以,在声明NSString属性时,到底是选择strong还是copy,可以根据实际情况来定。不过,一般我们将对象声明为NSString时,都不希望它改变(包括不希望赋值后,其他原来的值的改变会改变到它),所以大多数情况下,我们建议用copy,以免因可变字符串的修改导致的一些非预期问题。使用copy来修饰无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本,这样更安全。

(4)、将NSString、NSMutableString变量赋值给用copy、strong修饰的NSMutableString属性的时候,是否会开辟新地址,即是属于深拷贝还是浅拷贝?
1
2
3
4
5
6
7
8
9
10
11
12
13
@property (nonatomic, copy) NSMutableString *copyedStr;
@property (nonatomic, strong) NSMutableString *strongStr;

NSString *testStr = [NSString stringWithFormat:@"123"];
self.copyedStr = testStr;
self.strongStr = testStr;



@property (nonatomic, copy) NSMutableString *copyedMutableStr;

NSMutableString *testMutableStr = [NSMutableString stringWithFormat:@"mutable_456"];
self.copyedMutableStr = testMutableStr;

2.自定义类如何让它具有copy功能?
遵守NScoping协议,实现copywithzone方法即可.

(5)、自己代码实现copy修饰符,应该怎么写????

4、NSCoding和NSCopy

NSCoding和NSCopy

(1)、NSCoding的作用

iOS通过NSCoding保存实体对象

很多时候我们都需要将对象序列化,比如将一个对象存入到NSUserDefault 里面去的时候,由于NSUserDefault支持存入的类型有限制,所以很多时候我们需要将NSObject类型的对象转换成NSData再存入进去。

(2)、NSCopy

当你要进行对象拷贝的时候需要遵循NSCopy协议

1
2
3
4
5
6
7
8
9
- (id)copyWithZone:(NSZone *)zone {
id copy = [[[self class] alloc] init];
if (copy) {
[copy setId:[self.id copyWithZone:zone]];
[copy setNickName:[self.nickName copyWithZone:zone]];
}

return copy;
}

5、@synthesize和@dynamic区别

@synthesize和@dynamic区别

在声明property属性后,有2种实现选择

  • @synthesize

编译器期间,让编译器自动生成getter/setter方法。当有自定义的存或取方法时,自定义会屏蔽自动生成该方法

  • @dynamic

告诉编译器,不自动生成getter/setter方法,避免编译期间产生警告,然后由自己实现存取方法,或存取方法在运行时动态创建绑定:主要使用在CoreData的实现NSManagedObject子类时使用,由Core Data框架在程序运行的时动态生成子类属性