OC基础之属性关键字
# 属性关键字系列
# 讲讲你对atomic & noatomic的理解
atomic:默认是有该属性的,这个属性是为了保证程序在多线程情况下,编译器会自动生成一些互斥加锁代码,避免该变量的读写不同步问题。
nonatomic:如果该对象无需考虑多线程的情况,请加入这个属性,这样会让编译器少生成一些互斥加锁代码,可以提高效率。
在iOS10之后apple废弃了OSSpinLock自旋锁,使用os_unfair_lock来替代。 atomic的底层实现只是在 setter 和 getter 方法中加了一个锁, 这个锁表面上是自旋锁, 但是在最新的runtime版本上是一个os_unfair_lock(互斥锁)。
在iOS10之后apple已经不再建议使用OSSpinLock自旋锁了,它的替代方案是一个互斥锁,所以一般情况下我们使用互斥锁来解决线程同步的问题才是比较合理的。
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
为什么苹果不推荐使用atomic了? 个人总结为以下2点 ,更详细的原因和原理参考
系统用一个全局生效的字典保存属性值与atomic锁的对应关系, 系统最多提供8个atomic锁给atomic加锁解锁, 当一个项目中有几万个属性都是原子性的时候, 几万映射到8个锁上, 很多属性都会对应到同一把锁上, 那么这个属性就得等其他毫不相关的属性完成读写, 自己才能进行操作, 而且set/get是一个特别高频的操作.
atomic只针对特定场景保证线程安全, 存在局限性. 只能保证set/get的线程安全, 对于更大范围的线程安全是无法保证的. 举一个很简单的例子,假设定义属性 NSInteger i 是原子的,对i进行 i = i + 1; 这个操作就是不安全的。因为原子性只能保证读写安全,而该表达式需要三步操作:
- 先进行get, 读取i的值存入寄存器;
- 将寄存器的值加1;
- 使用寄存器修改后的值给i赋值;
atomic只能保证1和3是线程安全的, 如果在第1步完成的时候,i被其他线程修改了,那么表达式执行的结果就会与预期的不一样,也就是不安全的。所以要解决这样的线程安全问题, 只能对 整个表达式进行加锁, 单纯对i 设置atomic达不到预期的.
# 为什么说atomic没办法保证整个对象的线程安全,这里主要看一下网上主流的答案?
对于NSArray类型 @property(atomic)NSArray *array我们用atomic修饰,数组的添加和删除并不是线程安全的,这是因为数组比较特殊,我们要分成两部分考虑,一部分是&array也就是这个数组本身,另一部分是他所指向的内存部分。atomic限制的只是&array部分,对于它指向的对象没有任何限制。 atomic表示,我TM也很冤啊!!!!
.当线程A进行写操作,这时其他线程的读或者写操作会因为该操作而等待。当A线程的写操作结束后,B线程进行写操作,然后当A线程需要读操作时,却获得了在B线程中的值,这就破坏了线程安全,如果有线程C在A线程读操作前release了该属性,那么还会导致程序崩溃。所以仅仅使用atomic并不会使得线程安全,我们还要为线程添加lock来确保线程的安全
# 请说明并比较以下关键词:strong, weak, assign, copy。
strong表示指向并拥有该对象。其修饰的对象引用计数会增加1。该对象只要引用计数不为0则不会被销毁。当然强行将其设为nil可以销毁它。
weak表示指向但不拥有该对象。其修饰的对象引用计数不会增加。无需手动设置,该对象会自行在内存中销毁。
assign主要用于修饰基本数据类型,如NSInteger和CGFloat,这些数值主要存在于栈上。
weak 一般用来修饰对象,assign一般用来修饰基本数据类型。原因是assign修饰的对象被释放后,指针的地址依然存在,造成野指针,在堆上容易造成崩溃。而栈上的内存系统会自动处理,不会造成野指针。
copy与strong类似。不同之处是strong的复制是多个指针指向同一个地址,而copy的复制每次会在内存中拷贝一份对象,指针指向不同地址。copy一般用在修饰有可变对应类型的不可变对象上,如NSString, NSArray, NSDictionary。 Objective-C 中,基本数据类型的默认关键字是atomic, readwrite, assign;普通属性的默认关键字是atomic, readwrite, strong。
# #include、#import、@class的区别?
在C 语言中, 我们使用 #include 来引入头文件,如果需要防止重复导入需要使用#ifndef...#define...#endif
在OC语言中, 我们使用#import来引入头文件,可以防止重复引入头文件,可以避免出现头文件递归引入的现象。
@class仅用来告诉编译器,有这样一个类,编译代码时,不报错,不会拷贝头文件.如果需要使用该类或者内部方法需要使用 #import导入
# New 作用是什么?
向计算机(堆区)申请内存空间; 给实例变量初始化; 返回所申请空间的首地址;
# OC实例变量的修饰符? 及作用范围?
提示
@puplic
- 可以在其他类中访问被@public修饰的成员变量
- 也可以在本类中访问被@public修饰的成员变量
- 可以在子类中访问父类中被@public修饰的成员变量
@private
- 不可可以在其他类中访问被@private修饰的成员变量
- 也可以在本类中访问被@private修饰的成员变量
- 不可以在子类中访问父类中被@private修饰的成员变量
@protected (默认情况下所有的实例变量都是protected)
- 不可可以在其他类中访问被@protected修饰的成员变量
- 也可以在本类中访问被@protected修饰的成员变量
- 可以在子类中访问父类中被@protected修饰的成员变量
@package
介于public和private之间的,如果是在其他包中访问就是private,在当前代码中访问就是public.
# @proprety的作用
@property = ivar + getter + setter;
在.h文件中帮我们自动生成get和set方法声明 在.m文件中帮我们生成私有的实例变量(前提是没有在.h文件中没有手动生成) 在.m文件中帮我们是实现get和set方法的实现 注意: 在使用@property情况下,可以重写getter和setter方法.需要注意的是, 当把setter和getter方法都实现了之后,实例变量也需要手动添加.
# NSObject和id的区别?
NSObject和id都可以指向任何对象 NSObject对象会在编译时进行检查,需要强制类型转换 id类型不需要编译时检查,不需要强制类型转换
# id 和 instancetype的区别?
id可以作为方法的返回以及参数类型 也可以用来定义变量
instancetype 只能作为函数或者方法的返回值
instancetype对比id的好处就是: 能精确的限制返回值的具体类型
# id类型, nil , Nil ,NULL和NSNULL的区别?
id类型: 是一个独特的数据类型,可以转换为任何数据类型,id类型的变量可以存放任何数据类型的对象,在内部处理上,这种类型被定义为指向对象的指针,实际上是一个指向这种对象的实例变量的指针; id 声明的对象具有运行时特性,既可以指向任意类型的对象
nil 是一个实例对象值;如果我们要把一个对象设置为空的时候,就用nil
Nil 是一个类对象的值,如果我们要把一个class的对象设置为空的时候,就用Nil
NULL 指向基本数据类型的空指针(C语言的变量的指针为空)
NSNull 是一个对象,它用在不能使用nil的场合
# 什么情况使用 weak 关键字,相比 assign 有 什么不同?
在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决, 比如:delegate 代理属性, 自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用 strong,但是建议使用 weak
weak 和 assign 的不同点
weak 策略在属性所指的对象遭到摧毁时,系统会将 weak 修饰的属性对象的指针指 向 nil,在 OC 给 nil 发消息是不会有什么问题的; 如果使用 assign 策略在属性所指 的对象遭到摧毁时,属性对象指针还指向原来的对象,由于对象已经被销毁,这时候就产生了野指针,如果这时候在给此对象发送消息,很容造成程序奔溃 assigin 可以用于修饰非 OC 对象,而 weak 必须用于 OC 对象
代理使用 weak 还是 assign
建议使用 weak, 对于weak: 指明该对象并不负责保持delegate这个对象,delegate这个对象的销毁由外部控制。 可以使用 assign,也有weak的功效, 对于使用 assign 修饰delegate, 在对象释放前,需要将 delegate 指针设置为 nil,不然会产生野指针
# ARC 下,不显式指定任何属性关键字时,默认 的关键字都有哪些?
基本数据类型: atomic,readwrite,assign 普通的 OC 对象: atomic,readwrite,strong
# 怎么用 copy 关键字?
NSString、NSArray、NSDictionary 等等经常使用 copy 关键字,是因为他们有对应 的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,为确保 对象中的属性值不会无意间变动,应该在设置新属性值时拷贝一份,保护其封装性
block 也经常使用 copy 关键字,方法内部的 block 默认是 在栈区的,使用 copy 可以把它放到堆区.
对于 block 使用 copy 还是 strong 效果是一样的,但是 建议写上 copy,因为这样显示告知调用者“编译器会自动对 block 进行了 copy 操 作
# 如何让自定义类可以用 copy 修饰符?如何重写带 copy 关键字的 setter?
若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopyiog 与 NSMutableCopying 协议
// 实现不可变版本拷贝
- (id)copyWithZone:(NSZone *)zone;
// 实现可变版本拷贝
- (id)mutableCopyWithZone:(NSZone *)zone;
// 重写带 copy 关键字的 setter
- (void)setName:(NSString *)name {
_name = [name copy];
}
2
3
4
5
6
7
8
# weak 属性需要在 dealloc 中置 nil 么
在 ARC 环境无论是强指针还是弱指针都无需在 dealloc 设置为 nil , ARC 会自动帮我们处理
即便是编译器不帮我们做这些,weak 也不需要在 dealloc 中置 nil 在属性所指的对象遭到摧毁时,属性值也会清空
# 数组copy后里面的元素会复制一份新的吗
不会,数组里面存的是之前对象的地址,不会改变,可以自己测试一下
# 如下代码,会有什么问题吗?
@property (copy, nonatomic) NSMutableArray * array
使用 copy 修饰,会生成不可变数组,在添加删除数组元素时候会崩溃
# OC中的NSInteger 和int 有什么区别
在32位操作系统时候, NSInteger 等价于 int,即32位
在64位操作系统时候, NSInteger 等价于 long,即64位
# @synthesize 和 @dynamic 分别有什么作用
@property 有两个对应的词,一个是@synthesize,一个是@dynamic。
如果 @synthesize 和@dynamic都没写,那么默认的就是@syntheszie var = _var;
@synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器 会自动为你加上这两个方法
@dynamic 告诉编译器:属性的 setter 与 getter方法由用户自己实现,不自动生成(当然对于 readonly 的属性只需提供 getter 即可)
# NSMutableDictionary 中使用setValueForKey 和 setObjectForKey有什么区别?
根据官方文档说明: 一般情况下,如果给NSMutableDictionary 发送setValue 仍然是调用了 setObject方法, 如果参数 value 为 nil,则会调用removeObject 移除这个键值对;
setObjectForKey 是 NSMutableDictionary特有的, value 不能为 nil,否则会崩溃
setValueForKey 是KVC的,key 必须是字符串类型, setObject 的 key 可以是任意类型
# NSCache 和NSDictionary 区别?
NSCache可以提供自动删减缓存功能,而且保证线程安全,与字典不同,不会拷贝键。
NSCache可以设置缓存上限,限制对象个数和总缓存开销。定义了删除缓存对象的时机。这个机制只对NSCache起到指导作用,不会一定执行。
NSPurgeableData搭配NSCache使用,可以自动清除数据。 只有那种“重新计算很费劲”的数据才值得放入缓存。
# NSArray 和 NSSet区别
NSSet和NSArray功能性质一样,用于存储对象,属于集合。
NSSet属于 “无序集合”,在内存中存储方式是不连续
NSArray是 “有序集合” 它内存中存储位置是连续的。
NSSet,NSArray都是类,只能添加对象,如果需要加入基本数据类型(int,float,BOOL,double等),需要将数据封装成NSNumber类型。 由于NSSet是用hash实现的所以就造就了它查询速度比较快,但是我们不能把某某对象存在第几个元素后面之类的有关下标的操作。
NSSet的效率确实是比NSArray高的,因为它主要用的是hash算法,我的理解中,NSSet在集合中寻找一个元素的时候是一针见血,找到目标顺着就去了;而NSArray的话需要循环集合中所有的对象,来找到所需要的目标。所以,循环所有对象与直接去对象的位置获取,速度就显而易见了。
# 简要说明const,宏,static,extern区分以及使用?
# const
点击查看
const常量修饰符,经常使用的字符串常量,一般是抽成宏,但是苹果不推荐我们抽成宏,推荐我们使用const常量。
- const 作用:限制类型
- 使用const修饰基本变量, 两种写法效果一致 , b都是只读变量
const int b = 5;
int const b = 5;
- 使用const修饰指针变量的变量
第一种: const int *p = &a 和 int const *q = &a; 效果一致,*p 的值不能改,p 的指向可以改;
第二种: int * const p = &a; 表示 p 的指向不能改,*p 的值可以改
第三种:
const int * const p = &a; *p 值和 p 的指向都不能改
const 在*左边, 指向可变, 值不可变
const 在*的右边, 指向不可变, 值可变
const 在*的两边, 都不可变
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 宏
点击查看
- 基本概念:宏是一种批量处理的称谓。一般说来,宏是一种规则或模式,或称语法替换 ,用于说明某一特定输入(通常是字符串)如何根据预定义的规则转换成对应的输出(通常也是字符串)。这种替换在预编译时进行,称作宏展开。编译器会在编译前扫描代码,如果遇到我们已经定义好的宏那么就会进行代码替换,宏只会在内存中copy一份,然后全局替换,宏一般分为对象宏和函数宏。 宏的弊端:如果代码中大量的使用宏会使预编译时间变长。
const与宏的区别?
- 编译检查 宏没有编译检查,const有编译检查;
- 宏的好处 定义函数,方法 const不可以;
- 宏的坏处 大量使用宏,会导致预编译时间过长
# static
点击查看
- 修饰局部变量: 被static修饰局部变量,延长生命周期,跟整个应用程序有关,程序结束才会销毁,被 static 修饰局部变量,只会分配一次内存
- 修饰全局变量: 被static修饰全局变量,作用域会修改,也就是只能在当前文件下使用
# extern
点击查看
声明外部全局变量(只能用于声明,不能用于定义)
常用用法(.h结合extern联合使用) 如果在.h文件中声明了extern全局变量,那么在同一个类中的.m文件对全局变量的赋值必须是:数据类型+变量名(与声明一致)=XXXX结构。并且在调用的时候,必须导入.h文件。代码如下:
.h
@interface ExternModel : NSObject
extern NSString *lhString;
@end
.m
@implementation ExternModel
NSString *lhString=@"hello";
@end
调用的时候:例如:在viewController.m中调用,则可以引入:ExternModel.h,否则无法识别全局变量。当然也可以通过不导入头文件的方式进行调用(通过extern调用)。
# NSString为什么要用copy关键字,用strong会有什么问题?
对于strongMulStr,虽然定义的是NSMutableString属性,但是由于赋值时指向了str这个不可变对象而变成了NSString,而NSString里没有appendString:这个函数,所以报错;
对于copyedMulStr,虽然定义的是NSMutableString属性,但是用copy修饰后进行了深拷贝,变成了NSString类型,也不能再调用appendString:函数。
对于不可变属性,推荐使用copy,能防止不可变对象变成可变对象,从而防止值发生不可控变化。
对于可变属性,推荐使用strong,因为用copy修饰后,会变成不可变对象,再调用可变对象的函数时会crash。
mArray属性就成了NSArray,NSMutableArray只能用strong修饰,不存在有copy修饰的情况,写了就成NSArray了。
如果是strong,直接是赋值_mArray = mArray;右边是什么,左边就是什么,并且是强引用新值。这样就和strong的作用一样了。
所以:用copy为关键字的话,调用setter方法后。是对赋值对象进行深拷贝。并且拷贝的对象是copy的(不可变的),而不是mutableCopy的(可变的)。所以用copy修饰的mutableArray也被视为Array了,所以再用mutableArray的方法就会发生崩溃。