readWrite,readOnly
atomic,nonatomic
assign, strong,weak,copy,retain,unsafe_unretained
getter,setter
默认情况
iOS @property 默认属性
数据类型
atomic assign readwrite
对象类型
atomic strong readwrite
setter和getter
尽量多的使用属性(property)而不是实例变量(attribute)因为属性(property)相比实例变量有很多的好处:
1.自动合成getter和setter方法。当声明一个属性(property)的时候,编译器默认情况下会自动生成相关的getter和setter方法。
2.更好的声明一组方法。因为访问方法的命名约定,可以很清晰的看出getter和setter的用处。
3.属性(property)关键词能够传递出相关行为的额外信息。属性提供了一些可能会使用的特性来进行声明,包括assign(vs copy),weak,strong,atomic(vs nonatomic),readwrite,readonly等。
属性方法遵守一个简单的命名约定。getter的名字与属性名相同(如:属性名为date则getter的名字也为date),setter的名字则是属性名字加上set前缀并采用驼峰命名规则(如:属性名为date则setter的名字为setDate)。布尔类型的属性还可以定义一个以is开头的getter方法,如:
@property (readonly, getter=isBlue) BOOL blue;
如果按照上面的方法声明则以下所有访问方式都正确:
if (color.blue) {}
if (color.isBlue) {}
if ([color isBlue]) {}
当决定什么东西可以作为一个属性的时候,需要注意以下这些不属于属性:
init方法
copy和mutableCopy方法
类工厂方法
开启某项操作并返回一个BOOL结果的方法
明确的改变了一个getter的内部状态的副作用方法
除此之外,在你的代码中使用属性特性的时候请考虑以下规则:
- 一个可读写(read/write)的属性有两个访问方法。
setter方法是有一个参数的无返回值方法,
getter方法是没有参数的且有一个返回值的方法,返回值类型与属性声明的类型一致。
如果将这组方法转换成一个属性,就可以用readwrite关键字来标记它(默认即为readwrite可不写)。 - 一个只读(read-only)的属性只有一个访问方法。
即getter方法,它不接受任何参数,并且返回一个值。
如果将这个方法转换成一个属性,就可以用readonly关键字标记它。 - getter方法应当是幂等(idempotent)的(如果一个getter方法被调用两次,那么第二次调用时返回的结果应该和第一调用时返回的结果相同)。然而,如果一个getter方法每次调用时,是被用于计算结果,这是可以接受的。
如何适配
识别出一组可以被转换成一个属性的方法,如这些方法:
- (NSColor *)backgroundColor;
- (void)setBackgroundColor:(NSColor *)color;
用@property语法和适当的关键字将它们定义成一个属性:
@property (copy) NSColor *backgroundColor;
有关属性关键词和其他注意事项,可以阅读Encapsulating Data。
或者,你也可以使用Xcode中的modern Objective-C转换器来自动转换你的代码。参考Refactoring Your Code Using Xcode。
手动创建getter与setter
@interface Person : NSObject
{
NSString *_name;
NSUInteger _age;
}
- (void)setName:(NSString*)name;
- (NSString*)name;
- (void)setAge:(NSUInteger)age;
- (NSUInteger)age;
@end
@implementation Person
- (void)setName:(NSString*)name {
_name = [name copy];
}
- (NSString*)name {
return _name;
}
- (void)setAge:(NSUInteger)age {
_age = age;
}
- (NSUInteger)age {
return _age;
}
@end
上述代码就是手动创建变量的getter和setter的实现,getter和setter本质就是符合一定命名规范(前文Apple Official Property Introduction有讲解)的实例方法。
具体使用方法如下
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
//函数调用name的setter
[p setName:@"Jiaming Chen"];
//函数调用age的setter
[p setAge:22];
//函数调用name和age的getter,输出 Jiaming Chen 22
NSLog(@"%@ %ld", [p name], [p age]);
}
return 0;
}
通过调用方式可以看出,setter和getter本质就是实例方法,可以通过函数调用的方式来使用。
为了方便使用,Objective-C允许使用点语法来访问getter和setter。
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
//使用点语法访问name的setter
p.name = @"Jiaming Chen";
//使用点语法访问age的setter
p.age = 22;
//使用点语法访问name和age的getter,输出 Jiaming Chen 22
NSLog(@"%@ %ld", p.name, p.age);
}
return 0;
}
使用点语法访问的方式本质还是调用了我们手动创建的setter和getter。
当有很多变量需要设置时,这样手工创建setter和getter的方式难免很繁琐,因此合成存取方法就诞生了。
合成存取方法
@interface Person : NSObject
@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSUInteger age;
@end
@implementation Person
@synthesize name = _name;
@synthesize age = _age;
@end
在声明一个属性(property)的时候尽量使用Foundation框架的数据类型,如整型使用NSInteger或NSUInteger表示,时间间隔的浮点类型使用NSTimeInterval表示,这样代码数据类型更统一。
上面的代码使用@property声明两个属性name和age并为其设置了一些指示符(nonatomic,copy,assign等,下文会详细介绍)。
@synthesize表示为这两个属性自动生成名为_name和_age的底层实例变量,并自动生成相关的getter和setter也可以不写,编译器默认会自动生成'_属性名'的实例变量以及相关的getter和setter。
这里所说的编译器自动生成的实例变量就如同我们在上文中手动创建setter和getter时声明的变量_name和_age。也就是说编译器会在编译时会自动生成并使用_name和_age这两个变量来存储这两个属性,跟name和age没什么关系了,只是我们在上层使用这两个属性的时候可以用name和age的点语法来访问getter和setter。如果不想使用这两个名字用于底层的存储也可以任意命名,但最好按照官方的命名原则来命名。
也可以自定义getter和setter方法来覆盖编译器默认生成的方法,就如同手动创建getter和setter一样。
@interface Person : NSObject
@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSUInteger age;
@end
@implementation Person
//编译器会帮我们自动生成_name和_age这两个实例变量,下面代码就可以正常使用这两个变量了
@synthesize name = _name;
@synthesize age = _age;
- (void)setName:(NSString*)name {
//必须使用_name来赋值,使用self.name来设置值时编译器会自动转为调用该函数,会导致无限递归
//使用_name则是直接访问底层的存储属性,不会调用该方法来赋值
//这里使用copy是为了防止NSMutableString多态
_name = [name copy];
}
- (NSString*)name {
//必须使用_name来访问属性值,使用self.name来访问值时编译器会自动转为调用该函数,会造成无限递归
return _name;
}
@end
使用自定义的getter和setter一般是用来实现懒加载(lazy load),在很多情况下很常用,比如:创建一个比较大的而又不一定会使用的对象,可以按照如下方法编写。
@property (nonatomic, strong) CustomObject *customObject;
@synthesize customObject = _customObject;
- (CustomObject*) customObject {
if (_customObject == nil) {
//初始化操作,会调用setter方法
self.customObject = [[CustomObject alloc] init];
//如果按照如下方法编写不会调用setter方法,如果自定义setter方法需要完成一些事情建议使用self.customObject的方式来设置
//_customObject = [[CustomObject alloc] init];
}
return _customObject;
}
atomic和nonatomic
atomic和nonatomic用来决定编译器生成的getter和setter是否为原子操作。
atomic:
系统生成的 getter/setter(会进行加锁操作) 会保证 get、set 操作的完整性,不受其他线程影响。getter 还是能得到一个完好无损的对象(可以保证数据的完整性),但这个对象在多线程的情况下是不能确定的。
线程 A 调了 getter,
与此同时线程 B 、线程 C 都调了 setter
那最后线程 A get 到的值,有3种可能:
B、C set 之前原始的值、 B set 的值、C set 的值。
所以,atomic并不能保证对象
的线程安全。
也就是说:如果有多个线程同时调用setter的话,不会出现某一个线程执行完setter全部语句之前,另一个线程开始执行setter情况,相当于函数头尾加了锁一样,每次只能有一个线程调用对象的setter方法,所以可以保证数据的完整性。
atomic所说的线程安全(这个加锁的操作)只是保证了getter和setter存取方法的线程安全,并不能保证整个对象是线程安全的。因为线程安全还有读写之外的其他操作(比如:如果当一个线程正在get或set时,又有另一个线程同时在进行release操作,可能会直接crash)
因为getter/setter方法有加锁的缘故,所以在别的线程来读写这个属性之前,会先执行完当前操作
nonatomic:
(系统生成的getter/setter方法不会进行加锁操作),nonatomic返回你的对象可能就不是完整的value。当多个线程同时访问同一个属性,会出现无法预料的结果。因此,在多线程的环境下原子操作是非常必要的,否则有可能会引起错误的结果。但仅仅使用atomic并不会使得对象线程安全,我们还要为对象线程添加lock来确保线程的安全。
nonatomic VS atomic
nonatomic的速度要比atomic的快。atomic是Objc使用的一种线程保护技术
,这种机制是耗费系统资源
的,且速度要慢。所以在iPhone这种小型设备上,通常使用nonatomic,而对象的线程安全问题则由程序员代码控制。
atomic与nonatomic的本质区别其实也就是在setter方法上的操作不同
nonatomic对象、atomic对象setter和getter方法的实现:
/// nonatomic对象
- (void)setCurrentImage:(UIImage *)currentImage
{
if (_currentImage != currentImage) {
[_currentImage release];
_currentImage = [currentImage retain];
}
}
- (UIImage *)currentImage
{
return _currentImage;
}
/// atomic对象
- (void)setCurrentImage:(UIImage *)currentImage
{
@synchronized(self) {
if (_currentImage != currentImage) {
[_currentImage release];
_currentImage = [currentImage retain];
}
}
}
- (UIImage *)currentImage
{
@synchronized(self) {
return _currentImage;
}
}
@synchronized 结构所做的事情跟锁lock类似:它防止不同的线程同时执行同一段代码。
assign
assign表示对属性只进行简单的赋值操作,不更改所赋的新值的引用计数,也不改变旧值的引用计数,常用于标量类型
,如NSInteger,NSUInteger,CGFloat,NSTimeInterval等。
assign也可以修饰对象如NSString等类型对象,上面说过使用assign修饰不会更改所赋的新值的引用计数,也不改变旧值的引用计数,如果当所赋的新值引用计数为0对象被销毁时属性并不知道,编译器不会将该属性置为nil,指针仍旧指向之前被销毁的内存,这时访问该属性会产生野指针错误并崩溃,因此使用assign修饰的类型一定要为标量类型。
1.这个修饰词是直接赋值的意思 , 整型/浮点型等数据类型都用这个词修饰 .
2.如果没有使用 weak strong retain copy 修饰 , 那么默认就是使用 assign 了.
3.当然其实对象也可以用 assign 修饰 , 只是对象的计数器不会+1 . ( 与 strong 的区别 )
4.如果用来修饰对象属性 , 那么当对象被销毁后指针是不会指向 nil 的 . 所以会出现野指针错误 .
( 与weak的区别 )
1.在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如: delegate 代理属性。
2.自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong。
IBOutlet连出来的视图属性为什么可以被设置成weak?
因为父控件的subViews数组已经对它有一个强引用。
不同点:
assign 可以用非 OC 对象,而 weak 必须用于 OC 对象。
weak 表明该属性定义了一种“非拥有关系”。在属性所指的对象销毁时,属性值会自动清空 nil。
@interface Person : NSObject
@property (nonatomic, assign) NSString* name;
@property (nonatomic, assign) NSUInteger age;
@end
@implementation Person
@synthesize name = _name;
@synthesize age = _age;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
//这里使用NSMutableString而不使用NSString是因为NSString会缓存字符串,后面置空的时候实际没有被销毁
NSMutableString *s = [[NSMutableString alloc] initWithString:@"Jiaming Chen"];
//设置p.name不会增加s的引用计数,只是单纯将s指向的地址赋给p.name
p.name = s;
//输出两个变量的内存地址,可以看出是一致的
NSLog(@"%p %p", p.name, s);
//这里可以正常访问name
NSLog(@"%@ %ld", p.name, p.age);
//将上述字符串置空,引用计数为0,对象被销毁
s = nil;
//查看其地址时仍然可以访问到,表示其仍然指向那一块内存
NSLog(@"%p", p.name);
//访问内容时发生野指针错误,程序崩溃。因为对象已经被销毁
NSLog(@"%@ %ld", p.name, p.age);
}
return 0;
}
使用weak修饰的时候同样不会增加所赋的新值的引用计数,也不减少旧值的引用计数,但当该值被销毁时,weak修饰的属性会被自动赋值为nil,这样就可以避免野指针错误。
为了防止多态的影响,对NSString进行修饰时一般使用copy。
unsafe_unretained
使用unsafe_unretained修饰时效果与assign相同,不会增加新值的引用计数,也不会减少旧值的引用计数(unretained)当所赋的值被销毁时不会被置为nil可能会发生野指针错误(unsafe)。unsafe_unretained与assign的区别在于,unsafe_unretained只能修饰对象,不能修饰标量类型,而assign两者均可修饰。
weak
weak是弱引用,用weak描述修饰或者所引用对象的计数器不会加一,并且会在引用的对象被释放的时候自动被设置为nil,大大避免了野指针访问坏内存引起崩溃的情况,另外weak还可以用于解决循环引用。
weak原理概括
转载:__weak的底层实现原理:https://www.jianshu.com/p/48044cc54392
weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址数组。
weak的底层实现的原理是什么?
Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash表,Key是所指对象的地址,value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。
为什么value是数组?
因为一个对象可能被多个弱引用指针指向
weak原理实现步骤
1. 初始化时
runtime会调用objc_initWeak函数,objc_initWeak函数会初始化一个新的weak指针指向对象的地址。
示例代码:
NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj;
当我们初始化一个weak变量时,runtime会调用 NSObject.mm 中的objc_initWeak函数。
这个函数在Clang中的声明如下:
id objc_initWeak(id *object, id value);
而对于 objc_initWeak() 方法的实现如下:
id objc_initWeak(id *location, id newObj) {
// 查看对象实例是否有效,无效对象直接导致指针释放
if (!newObj) {
*location = nil;
return nil;
}
// 这里传递了三个 bool 数值
// 使用 template 进行常量参数传递是为了优化性能
return storeWeakfalse/*old*/, true/*new*/, true/*crash*/>
(location, (objc_object*)newObj);
}
这个函数仅仅是一个深层函数的调用入口,而一般的入口函数中,都会做一些简单的判断(例如 objc_msgSend 中的缓存判断),这里判断了其指针指向的类对象是否有效,无效直接释放,不再往深层调用函数。否则,object将被注册为一个指向value的__weak对象。而这事应该是objc_storeWeak函数干的。
注意:objc_initWeak函数有一个前提条件:就是object必须是一个没有被注册为__weak对象的有效指针。而value则可以是null,或者指向一个有效的对象。
- 添加引用时:
objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
objc_storeWeak的函数声明如下:
id objc_storeWeak(id *location, id value);
- 释放时:
调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
当weak引用指向的对象被释放时,又是如何去处理weak指针的呢?当释放对象时,其基本流程如下:
1.调用objc_release
2.因为对象的引用计数为0,所以执行dealloc
3.在dealloc中,调用了 _ objc _ rootDealloc函数
4.在 _ objc _ rootDealloc 中,调用了object _ dispose函数
5.调用objc_destructInstance
6.最后调用objc _ clear _ deallocating
objc _ clear _ deallocating该函数的动作如下:
从weak表中获取废弃对象的地址为键值的记录
将包含在记录中的所有附有 weak修饰符变量的地址,赋值为nil
将weak表中该记录删除
从引用计数表中删除废弃对象的地址为键值的记录
NSString *name = [[NSString alloc] initWithString: @"Jiaming Chen"];
__weak NSString *weakStr = name;
当为weakStr这一weak类型的对象赋值时,编译器会根据name的地址为key去查找weak哈希表,该表项的值为一个数组,将weakStr对象的地址加入到数组中,当name变量超出变量作用域或引用计数为0时,会执行dealloc函数,在执行该函数时,编译器会以name变量的地址去查找weak哈希表的值,并将数组里所有 weak对象全部赋值为nil。
strong
在ARC环境下,只要某一对象被一个strong指针指向,该对象就不会被销毁。如果对象没有被任何strong指针指向,那么就会被销毁。在默认情况下,所有的实例变量和局部变量都是strong类型的。可以说strong类型的指针在行为上跟非ARC下的retain是比较相似的
strong表示属性对所赋的值持有强引用表示一种“拥有关系”(owning relationship),会先保留新值即增加新值的引用计数,然后再释放旧值即减少旧值的引用计数。只能修饰对象。如果对一些对象需要保持强引用则使用strong。
weak表示对所赋的值对象持有弱引用表示一种“非拥有关系”(nonowning relationship),对新值不会增加引用计数,也不会减少旧值的引用计数。所赋的值在引用计数为0被销毁后,weak修饰的属性会被自动置为nil能够有效防止野指针错误。
weak常用在修饰delegate等防止循环引用的场景。
copy
预备知识
浅拷贝
只是将对象内存地址多了一个引用,也就是说,拷贝结束之后,两个对象的值不仅相同,而且对象所指的内存地址都是一样的。
深拷贝
拷贝一个对象的具体内容,拷贝结束之后,两个对象的值虽然是相同的,但是指向的内存地址是不同的。两个对象之间也互不影响,互不干扰。
这里讲的真的是太好了!!参考:https://www.jianshu.com/p/d01429a4b5c0
copy的作用
在非集合类对象中,对不可变对象进行copy操作,只仅仅是指针复制——浅复制,进行mutableCopy操作,是内容复制——深复制。
对于不可变的集合类对象进行copy操作,只是改变了指针,其内存地址并没有发生变化;进行mutableCopy操作,内存地址发生了变化,但是其中的元素内存地址并没有发生变化。
对于可变集合类对象,不管是进行copy操作还是mutableCopy操作,其内存地址都发生了变化,但是其中的元素内存地址都没有发生变化,属于单层深拷贝。
使用注意:
当将一个可变对象分别赋值给两个使用不同修饰词的属性后,改变可变对象的内容,使用strong修饰的会跟随着改变,但使用copy修饰的没有改变内容。
那么,是不是NSMutableString等这些可变对象是不是也需要copy来修饰呢?答案是千万不要这么干,我们可以测试一下:
@interface test()
@property (nonatomic, copy) NSMutableString *strCopy;
@end
/********************* test.m **********************/
NSMutableString *string = [NSMutableString stringWithFormat:@"abc"];
self.strCopy = string;
[self.strCopy appendString:@"def"]; // 在这一行会crash
因为copy是复制出一个不可变的对象,在不可变对象上运行可变对象的方法,就会找不到执行方法
copy修饰的属性会在内存里拷贝一份对象,两个指针指向不同的内存地址。
一般用来修饰有对应可变类型子类的对象。
如:NSString/NSMutableString,NSArray/NSMutableArray,NSDictionary/NSMutableDictionary等。
为确保这些不可变对象因为可变子类对象影响,需要copy一份备份,如果不使用copy修饰,使用strong或assign等修饰则会因为多态导致属性值被修改。
这里的copy还牵扯到NSCopying和NSMutableCopying协议,在下文会有简要介绍。
@interface Person : NSObject
//使用strong修饰NSString
@property (nonatomic, strong) NSString* name;
@property (nonatomic, assign) NSUInteger age;
@end
@implementation Person
@synthesize name = _name;
@synthesize age = _age;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
NSMutableString *s = [[NSMutableString alloc] initWithString:@"Jiaming Chen"];
//将可变字符串赋值给p.name
p.name = s;
//输出的地址和内容均一致
NSLog(@"%p %p %@ %@", p.name, s, p.name, s);
//修改可变字符串s
[s appendString:@" is a good guy"];
//再次输出p.name被影响
NSLog(@"%p %p %@ %@", p.name, s, p.name, s);
}
return 0;
}
copy还被用来修饰block,在ARC环境下编译器默认会用copy修饰, 一般情况下在block需要捕获外界数据时该block就会被分配在堆区,但在MRC环境下由于手动管理引用计数,block一般被分配在栈区,需要copy到堆区来防止野指针错误。
对于可变对象类型,如NSMutableString、NSMutableArray等则不可以使用copy修饰,因为Foundation框架提供的这些类都实现了NSCopying协议,使用copy方法返回的都是不可变对象,如果使用copy修饰符在对可变对象赋值时则会获取一个不可变对象,接下来如果对这个对象进行可变对象的操作则会产生异常,因为OC没有提供mutableCopy修饰符,对于可变对象使用strong修饰符即可。具体栗子如下:
@interface Person : NSObject
//使用copy修饰NSMutableString
@property (nonatomic, copy) NSMutableString* name;
@property (nonatomic, assign) NSUInteger age;
@end
@implementation Person
@synthesize name = _name;
@synthesize age = _age;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
NSMutableString *s = [[NSMutableString alloc] initWithString:@"Jiaming Chen"];
//将可变字符串赋值给p.name
p.name = s;
//输出的地址不一致,内容一致
NSLog(@"%p %p %@ %@", p.name, s, p.name, s);
//修改p.name,此时抛出异常
[p.name appendString:@" is a good guy."];
}
return 0;
}
上面的栗子使用copy修饰可变对象,在进行赋值的时候会通过copy方法获取一个不可变对象,因此p.name的地址和s的地址不同,而p.name运行时类型为NSString,调用appendString:方法会抛出异常。
所以,针对不可变对象使用copy修饰,针对可变对象使用strong修饰。
retain
在ARC环境下使用较少,在MRC下使用效果与strong一致。
copy的题外话
有时候我们需要copy一个对象,或是mutableCopy一个对象,这时需要遵守NSCopying和NSMutableCopying协议,来实现copyWithZone:和mutableCopyWithZone:两个方法,而不是重写copy和mutableCopy两个方法。
Foundation框架中的很多数据类型已经帮我们实现了上述两个方法,因此我们可以使用copy方法和mutableCopy方法来复制一个对象,两者的区别在于copy的返回值仍未不可变对象,mutableCopy的返回值为可变对象。
由上述表格可以看出,对于不可变类型,使用copy方法时是浅拷贝,只拷贝指针,因为内容是不会变化的。使用mutableCopy时由于返回可变对象因此需要一份拷贝,供其他对象使用。对于可变类型,不管是copy还是mutableCopy均会进行深拷贝,所指向指针不同。
前文介绍copy修饰符的时候讲过,在修饰NSString这样的不可变对象的时候使用copy修饰,但其实当给对象赋一个NSString时仍旧只复制了指针而不是拷贝内容,原因同上。
@interface Person : NSObject
//使用copy修饰
@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSUInteger age;
@end
@implementation Person
@synthesize name = _name;
@synthesize age = _age;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
NSString *s = @"Jiaming Chen";
p.name = s;
//p.name的地址与s地址相同,不可变对象copy是浅拷贝
NSLog(@"%p %p %@ %@", p.name, s, p.name, s);
}
return 0;
}
@property深入代码理解
@property = ivar + getter + setter
ivar就是实例变量,编译器会帮我们自动生成名字为'_属性名'这样的实例变量,同时也会自动生成getter和setter方法。
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, copy) NSString* cjmName;
@property (nonatomic, assign) NSUInteger cjmAge;
@end
@implementation Person
@synthesize cjmName = _cjmName;
@synthesize cjmAge = _cjmAge;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
p.cjmName = @"JIaming Chen";
p.cjmAge = 22;
}
return 0;
}
使用上述命令后生成的.cpp文件中可以查找到如下部分的代码
#ifndef _REWRITER_typedef_Person
#define _REWRITER_typedef_Person
typedef struct objc_object Person;
typedef struct {} _objc_exc_Person;
#endif
extern "C" unsigned long OBJC_IVAR_$_Person$_cjmName;
extern "C" unsigned long OBJC_IVAR_$_Person$_cjmAge;
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_cjmName;
NSUInteger _cjmAge;
};
// @property (nonatomic, copy) NSString* cjmName;
// @property (nonatomic, assign) NSUInteger cjmAge;
/* @end */
// @implementation Person
// @synthesize cjmName = _cjmName;
static NSString * _I_Person_cjmName(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_cjmName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_Person_setCjmName_(Person * self, SEL _cmd, NSString *cjmName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _cjmName), (id)cjmName, 0, 1); }
// @synthesize cjmAge = _cjmAge;
static NSUInteger _I_Person_cjmAge(Person * self, SEL _cmd) { return (*(NSUInteger *)((char *)self + OBJC_IVAR_$_Person$_cjmAge)); }
static void _I_Person_setCjmAge_(Person * self, SEL _cmd, NSUInteger cjmAge) { (*(NSUInteger *)((char *)self + OBJC_IVAR_$_Person$_cjmAge)) = cjmAge; }
// @end
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)p, sel_registerName("setCjmName:"), (NSString *)&__NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_4b2631_mi_0);
((void (*)(id, SEL, NSUInteger))(void *)objc_msgSend)((id)p, sel_registerName("setCjmAge:"), (NSUInteger)22);
}
return 0;
}
以上代码就是编译器为我们生成的C代码,现在一一讲解几个比较重要的部分。
typedef struct objc_object Person;
编译器将struct objc_object重命名为我们自定义的Person类,struct objc_object结构体只有一个类型为Class的isa指针变量
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
而这个Class就代表类对象。
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
/#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
这个Person就是我们创建的类对象,这个类对象包含了Person类所需的所有东西,包括属性、方法列表、版本号等一系列信息。
extern "C" unsigned long OBJC_IVAR_$_Person$_cjmName;
extern "C" unsigned long OBJC_IVAR_$_Person$_cjmAge;
定义了两个unsigned long类型的变量,这两个变量代表一个偏移量,值这两个实例变量在内存中存储的偏移量,通过这两个值就能够在内存中定位到这两个实例变量的位置。
这两个值是运行时计算出偏移量硬编码(hard code)写入的,这样的好处在于,如果你使用了一个库,这个库的类定义比较旧,而链接的代码使用的是版本较新的代码,增加了几个实例变量,你的程序运行时也不会报错,因为偏移量是通过运行时计算出来的,仍旧能够找到相应的位置。如果不使用合成存取方法定义实例变量而使用手工的方式创建,这个偏移量就是编译器计算出硬编码写到代码中的,如果类定义和链接库的版本不一致则可能发生指针错误,因此鼓励大家尽量都使用合成存取方法。
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_cjmName;
NSUInteger _cjmAge;
};
该结构体就是Person类实现,struct NSObject_IMPL结构体只有一个Class isa结构体指针变量,指向类对象,用于获取Person类的方法列表、实例变量列表、属性列表、版本等信息。可以看出,在底层代码中编译器帮我们自动生成了名为_cjmName和_cjmAge的两个实例变量,如果我们修改@synthesize cjmName = _cjmName为其他名称则这列会生成相应名称的实例变量。
static NSString * _I_Person_cjmName(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_cjmName)); }
这句代码就是属性cjmName的getter方法,可以看出,使用了OBJC_IVAR__cjmName偏移量来计算实例变量的存储位置并返回。
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_Person_setCjmName_(Person * self, SEL _cmd, NSString *cjmName) {
objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _cjmName), (id)cjmName, 0, 1);
}
这两句代码是属性cjmName的setter方法,使用OFFSETOFIVAR(TYPE, MEMBER)宏定义来计算偏移量,上文指的偏移量都是通过该宏定义计算而来,计算出偏移量后使用objc_setProperty来设置实例变量_cjmName的值。
将属性cjmName的修饰符改为strong后再次查看重写的setter代码:
static void _I_Person_setCjmName_(Person * self, SEL _cmd, NSString *cjmName) {
(*(NSString **)((char *)self + OBJC_IVAR_$_Person$_cjmName)) = cjmName;
}
与上文代码相比发现,没有声明objc_setProperty方法也没有使用该方法,而是直接计算出实例变量的偏移量后将指针赋给实例变量。由此就可以看出修饰符copy和strong底层代码的区别。
同样的可以将修饰符改为assign、unsafe_unretained、weak来查看生成的代码,结果都同Strong一致,这就解释了底层代码是如何copy实例变量的。
再来看看以下几个结构体:
struct _ivar_t {
unsigned long int *offset; // pointer to ivar offset location
const char *name;
const char *type;
unsigned int alignment;
unsigned int size;
};
static struct /*_ivar_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count;
struct _ivar_t ivar_list[2];
} _OBJC_$_INSTANCE_VARIABLES_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_ivar_t),
2,
{{(unsigned long int *)&OBJC_IVAR_$_Person$_cjmName, "_cjmName", "@\"NSString\"", 3, 8},
{(unsigned long int *)&OBJC_IVAR_$_Person$_cjmAge, "_cjmAge", "Q", 3, 8}}
};
struct _ivar_t结构体表示每一个实例变量,记录了偏移值、名称、类型、对齐方式和大小,用于描述每一个实例变量。
struct _ivar_list_t结构体表示类的实例变量列表,记录了实例变量的大小、个数、以及每一个实例变量描述。
我们每在类中加入一个属性,编译器都会在_ivar_list_t变量中加入一个_ivar_t的实例变量描述。
struct _objc_method {
struct objc_selector * _cmd;
const char *method_type;
void *_imp;
};
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[4];
} _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
4,
{{(struct objc_selector *)"cjmName", "@16@0:8", (void *)_I_Person_cjmName},
{(struct objc_selector *)"setCjmName:", "v24@0:8@16", (void *)_I_Person_setCjmName_},
{(struct objc_selector *)"cjmAge", "Q16@0:8", (void *)_I_Person_cjmAge},
{(struct objc_selector *)"setCjmAge:", "v24@0:8Q16", (void *)_I_Person_setCjmAge_}}
};
struct _objc_method结构体描述了每一个实例方法,包括一个SEL类型的指针、方法类型和方法实现。
struct _method_list_t结构体表示类的实例方法列表,记录了每一个实例方法的大小、实例方法个数以及具体的实例方法描述,每加入一个属性则会在_method_list_t中增加setter与getter方法的描述。
struct _prop_t {
const char *name;
const char *attributes;
};
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"cjmName","T@\"NSString\",C,N,V_cjmName"},
{"cjmAge","TQ,N,V_cjmAge"}}
};
struct _prop_t结构体描述了每一个属性,包括名称和属性值。
struct _prop_list_t结构体表示属性列表,记录了每一个属性的大小、属性个数以及具体的属性描述,每加入一个属性则会在_prop_list_t中增加_prop_t属性描述。
从结构体中的值不难看出,属性描述中的T@表示是类型对象后接类型名称,C表示copy,N表示nonatomic,V_cjmName表示实例变量。
clang -rewrite-objc main.m
这个命令用于clang重写.m文件为.cpp文件。
转载:
iOS @property探究(一): 基础详解 http://www.jianshu.com/p/646ae400fe7b
iOS @property探究(二): 深入理解 http://www.jianshu.com/p/44d12884e24e
iOS 底层解析weak的实现原理(包含weak对象的初始化,引用,释放的分析):http://www.cocoachina.com/ios/20170328/18962.html