ios声明属性时,在ARC环境下常用到的关键字,readonly、readwrite、nonatomic、atomic、strong、retain、assign、weak、copy、static、const、extern等,在平常使用中,大多时候只是会用,知其然而不知所以然,下面进行一下总结:
readonly和readwrite
- 这两个关键字其实从字面意思咱们就可以很好了解其作用,readonly只读与readwrite可读可写。readwrite是默认属性,同时产生setter\getter方法,而readonly只生成getter方法,没有setter方法。
@synthesize
编译器期间,让编译器自动生成getter/setter方法。当有自定义的存或取方法时,自定义会屏蔽自动生成该方法@property已经是默认自动生成setter 方法和 getter 方法。
@synthesize,可以自定义我们想要的属性名
@synthesize age = _age
这是默认的
@synthesize age = _haha
_haha就是我们自定义的属性名
@dynamic
告诉编译器,不自动生成getter/setter方法,避免编译期间产生警告然后由自己实现存取方法或存取方法在运行时动态创建绑定:主要使用在CoreData的实现NSManagedObject子类时使用,由Core Data框架在程序运行的时动态生成子类属性。动态绑定一个属性的实现,直白的说,就是告诉编译器这个属性不用你去做任何操作,完全由程序员自己来完成实现,包括getter,setter方法。
- 假如一个属性被声明为 @dynamic var,然后你没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到someVar = var时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
- @dynamic最常用的地方是类别里添加属性操作。都知道,在category里面不能添加属性。当我们有这个需求时,我们必须通过运行时runtime来完成,这时编译器不会自动给我们生成get和set方法,我们就需要自己来完成实现。这时我们就需要告诉编译器这个方法我自己去实现,这个时候需要给编译器一个标记@dynamic。然后我们再通过runtime的objc_setAssociatedObject/objc_getAssociatedObject来完成属性的get和set实现。
nonatomic
非原子性访问,对属性赋值的时候不加锁,多线程并发访问会提高性能。如果不加此属性,默认为是atomic 提供线程安全;
atomic
设置成员变量的@property属性时,默认为atomic,提供多线程安全,在多线程环境下,原子操作是必要的,否则有可能引起错误的结果(就是防止同时对数据修改或访问)。
- atomic的意思就是setter/getter函数是一个原语操作。如果有多个线程同时调用setter的话,不会出现某一个线程在执行setter语句还未完成,另一个线程开始执行setter的情况,相当于函数头尾加了锁一样,可以保证数据的完整性。nonatomic不保证setter/getter的原语性,所以你可能会取到不完整的东西。因此,在多线程的环境下原子操作是非常必要的,否则有可能会引起错误的结果。
- 在iOS开发中,几乎所有属性都声明为nonatomic。 一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全” ( thread safety),若要实现“线程安全”的操作,还需采用更为深层的锁定机制才行。例如,一个线程在连续多次读取某属性值的过程中有别的线程在同时改写该值,那么即便将属性声明为atomic,也还是会读到不同的属性值。因此,开发iOS程序时一般都会使用nonatomic属性。但是在开发 Mac OS X 程序时,使用atomic属性通常都不会有性能瓶颈。
strong与retain
它俩都是强引用,除了某些情况下不一样,其他的时候也是可以通用的。主要区别是在修饰block属性的时候,相信大家都知道要用copy吧,block在创建的时候它的内存是默认分配在栈(stack)上,而不是堆(heap)上,所以他的生命周期会随着函数的结束而结束,当你在该作用域外调用该block时, 程序就会crash,而copy之后会放在堆里面。strong在修饰block的时候就相当于copy,而retain修饰block的时候就相当于assign,这样block会出现提前被释放掉的危险。
简单测试了一下xcode 9.2 ios 9.0以上,除了使用assin会crash,retain会警告但是不会crash
@property(nonatomic,retain) void(^myBlcok)(void);//声明
self.myBlcok =^{//viewDidLoad中实现
NSLog(@"block内");
};
self.myBlcok(); //在别的方法调用
注意:在 ARC 情况下,其实编译器已经默认执行了copy操作。这问题在以后会详细说一下,先给个链接了解一下 Block内存管理实例分析
assign与weak
-
两者都是弱引用,assign通常用于础数据类型(NSInteger,CGFloat)和C数据类型(int, float, double, char等)使用,还有代理属性的修饰、xib、storyboard中拖出来属性,基本上来说两者是可以通用的。
-
只是weak比assign多了一个功能,后者会在引用的对象被释放的时候将该属性置为nil,而前者依然会指向原来的位置,这样就会变成野指针。在oc中你给你一个nil对象发送消息不会crash,但是给一个对象发送他不能解析的消息是会crash的,所以总的来说weak要比assign安全一些。像delegate属性建议用weak修饰而不是assign。
补充: 从storyboard或者xib上创建控件,在控件放在view上的时候,已经形成了如下的引用关系,以UIButton为例:
UIViewController->UIView->subView->UIButton
然后你为这个UIButton声明一个weak属性
@property(nonatomic,weak) IBOOutlet UIButton *btn;
相当于xib/sb对这个Button是强引用,你声明的属性对它是弱引用。
strong与weak
一个是强引用一个是弱引用,weak指针主要用于“父-子”关系,父类拥有一个子类的strong指针,因此父类是子类的所有者;但为了阻止所有权循环,子类需要使用weak指针指向父类。
典型例子是delegate模式,你的ViewController通过strong指针(self.view)拥有一个UITableView, UITableView的dataSource和delegate都是weak指针,指向你的ViewController。
strong与retain
声明属性时用strong或者retain效果是一样的(貌似更多开发者更倾向于用strong)。不过在声明Block时,使用strong和retain会有截然不同的效果。strong会等于copy,而retain竟然等于assign
Copy
copy分为深拷贝与浅拷贝:
1、浅拷贝,就是指只是将对象内存地址多了一个引用,也就是说,拷贝结束之后,两个对象的值不仅相同,而且对象所指的内存地址都是一样的。
2、深拷贝,拷贝内容,就是指拷贝一个对象的具体内容,拷贝结束之后,两个对象的值虽然是相同的,但是指向的内存地址是不同的。两个对象之间也互不影响,互不干扰。
使用注意
a. 在平时定义属性的时候,对于NSString、NSArray、NSDictionary类型的属性,我们最好设置为copy类型,这样别人使用我们定义的属性的时候,它不管怎么改动该属性的赋值,都不会影响我们给该属性赋的值。
b. 同样block作为属性是也用copy。
c. 不要将copy关键字用到NSMutableString、NSMutableArray、NSMutableDictionary等可变对象上,要用Strong关键字
想了解copy使用及原理:看《详解iOS的深浅拷贝》
Copy与Retain
copy与retain处理流程一样,先对旧值release,再copy出新的对象,而retain是指针拷贝,拷贝一份原来的指针,释放原来指针指向的对象的内容,再令指针指向新的对象内容,简单来说就是一个是内容拷贝,一个是指针拷贝。
主要区别:是在当给不可以的对象赋值时,如NSAarray、NSString,如果修改了原数据,那么使用的retain的属性的那个值也会跟着发生变化(跟strong一样),而copy不会因为它进行一个深拷贝。具体看上面链接
1、copy是创建一个新对象,retain是创建一个指针,引用对象计数加1。Copy属性表示两个对象内容相同,新的对象retain为1 ,与旧有对象的引用计数无关,旧有对象没有变化。
2、retain属性表示两个对象地址相同(建立一个指针,指针拷贝),内容当然相同,这个对象的retain值+1也就是说,retain 是指针拷贝,copy 是内容拷贝.
const与宏的区别
1.编译时刻不同,宏属于预编译 ,const属于编译时刻
2.宏能定义代码,const不能,使用宏定义过多的话,随着工程越来越大,编译速度会越来越慢,const只会编译一次,缩短编译时间。
3.宏不会检查错误,const会检查错误
《iOS日常工作之常用宏定义大全》
static:
-
修饰局部变量:
1.延长局部变量的生命周期,程序结束才会销毁。
2.局部变量只会生成一份内存,只会初始化一次。
3.改变局部变量的作用域。 -
修饰全局变量
1.只能在本文件中访问,修改全局变量的作用域,生命周期不会改
2.避免重复定义全局变量
开发中常用static修饰全局变量,只改变作用域
为什么要改变全局变量作用域,防止重复声明全局变量。
开发中声明的全局变量,有些不希望外界改动,只允许读取。
比如一个基本数据类型不希望别人改动
声明一个静态的全局只读常量,
如:static const int a = 20;
分类(category)和类扩展(extension)的区别
Category
-
用于给class及其subclass添加新的方法
-
有自己单独的 .h 和 .m 文件
-
用于添加新方法,而不能添加新属性(property)
-
是运行期决议的,这一点决定了它为什么不能为已有的类添加新的成员变量,实际上是允许添加属性的(这句话不能理解的请查看《成员变量、实例变量、属性变量 、全局变量、局部变量详解》),同样可以使用@property,但是不会生成_变量(带下划线的成员变量),也不会生成添加属性的getter和setter方法,所以,尽管添加了属性,也无法使用点语法调用getter和setter方法。
-
但是通过Runtime还是可以实现添加的 《分类Category添加属性》
Extension
- Extension常被称为是匿名的Category
- 用于给类添加新方法,但只作用于原始类,不作用于subclass
- 只能对有implementation源代码的类写Extension,对于没有implementation源代码的类,比如framework class,是不可以的
- Extension可以给原始类添加新方法,以及新属性
- 在编译器决议,是类的一部分,在编译器和头文件的@interface和实现文件里的@implement一起形成了一个完整的类。伴随着类的产生而产生,也随着类的消失而消失
分类只能扩展方法(属性仅仅是声明,并没真正实现),类扩展可以扩展属性、成员变量和方法
category作用:
分类,就是对一个类的功能进行扩展,让这个类能够适应不不同情况的需求.在一般的实际开发中,我们都会对系统的一些常用类进行扩展,比如NSString,Button,Label,UIColor等等,
简单来说类别是一种为现有的类添加新方法的方式。
利用Objective-C的动态运行时分配机制,Category提供了一种比继承(inheritance)更为简洁的方法来对类进行扩展,无需创建对象类的子类就能为现有的类添加新方法,可以为任何已经存在的类添加方法,包括那些没有源代码的类.如系统框架类Foundation,UIKit等等
extension 作用
(1)可以将类的实现分散到多个不同的文件或者不同的框架中,方便代码的管理。也可以对框架提供类的扩展(没有源码,不能修改)。
(2)创建对私有方法的前向引用:如果其他类中的方法未实现,在你访问其他类的私有方法时编译器报错这时使用类别,在类别中声明这些方法(不必提供方法实现),编译器就不会再产生警告
(3)向对象添加非正式协议:创建一个NSObject的类别称为“创建一个非正式协议”,因为可以作为任何类的委托对象使用。
__block和__weak修饰符的区别
__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,也可以修饰基本数据类型
__weak只能在ARC模式下使用,只能修饰对象(NSString),不能修饰基本数据类型
__block修饰的对象可以在block中被重新赋值被修改,__weak修饰的对象不可以修改
NSInteger与Int的区别
NSInteger是一个封装,它会识别当前操作系统的位数,自动返回最大的类型。
在苹果官方的API文档中,关于NSInteger有这样一段描述:
When building 32-bit applications, NSInteger is a 32-bit integer. A 64-bit application treats NSInteger as a 64-bit integer.
翻译过来就是:在32位App中,NSInteger是32位整型,在64位App中,NSInteger是64位整型.
-
32位与64位系统数据类型
不同的平台上对不同的数据类型分配的字节数是不同的,一般的,数据类型的字节数是由编辑器决定的(编译期间决定数据类型长度),64bit CPU拥有更大的寻址能力,最大支持到16GB内存,而32bit只支持4G内存。64位CPU一次可提取64位数据,比32位提高了一倍,理论上性能会提升1倍。
简单来说,平台就是CPU+OS+Compiler,cpu的位是指一次性可处理的数据量是多少,1字节=8位,32位处理器可以一次性处理4个字节的数据量,依次类推。32位操作系统针对的32位的CPU设计。64位操作系统针对的64位的CPU设计。所以平台是三者的组合,它们的字节长相同时,效率最高。
==、Equal 和 isEqualToString 的区别
-
isEqual: 判断两个对象的等同性,首先判断两个对象的地址是否相同,再判断类型是否一致, 然后再判断对象的具体内容是否一致,IsEqual 是 NSObject 的方法
-
isEqualToString 是 NSString 的方法,因此从继承关系角度来说isEqualToString 是 isEqual 的衍生方法,isEqualToString: 直接判断字符串内容是否相等。
-
==: 对于基本数据类型, ==运算符比较的是值; 对于对象类型, ==运算符比较的是对象的地址是否相同]。
首先贴个苹果官方重写isEqual 的demo
- (BOOL)isEqual:(id)other {
if (other == self)
return YES;
if (!other || ![other isKindOfClass:[self class]])
return NO;
return [self isEqualToWidget:other];
}
- (BOOL)isEqualToWidget:(MyWidget *)aWidget {
if (self == aWidget)
return YES;
if (![(id)[self name] isEqual:[aWidget name]])
return NO;
if (![[self data] isEqualToData:[aWidget data]])
return NO;
return YES;
}
简单说一下解释一下:
首先都会判断 指针是否相等 ,相等直接返回YES,不相等再判断是否是同类对象或非空,空或非同类对象直接返回NO,而后依次判断对象对应的属性是否相等,若均相等,返回YES
这样就不难理解 isEqualToString 的实现内部的了
v
网友评论