一、关于NSNull
先说说nil和NULL代表的是内存中的一块空地址,NSNull表示的是一个对象,指向一个nil地址。
文档上这样描述,NSNull一个单例,NSNull是一个用来表示不允许空值集合对象中的空值的。
其实NSNull存在的意义在于,OC中集合类型只能存储对象不能存储nil。存储nil会报NSInvalidArgumentException。可以用NSNull替代。
二、self和super
在方法调用方面self表示调用自己的方法,super表示调用父类方法。
self其实是类或者对象,super是预编译指令。
super调用的方法会转换成如下指令:
//以class方法为例
[super class]
//runtime层
struct objc_super1 {
__unsafe_unretained id receiver;
Class superClass;
};
struct objc_super1 obj_super = {self,class_getSuperclass(object_getClass(self))};
id obj3 = objc_msgSendSuper(&obj_super,@selector(class));
可以看出super调用方法的时候会传入当前对象
三、保证属性读写线程安全的方案
- 方案一:读写时加锁可以实现需求,但这样所有的属性读写都是原子的,我们的需求是针对同一属性线程安全,所以这样做效率较低。
- 方案二:可以创建一个串行队列,把读写操作放到这个串行队中同步执行,串行队列的性质可保证线程安全。优化的话,可以将写操作改为异步执行,读操作改为同步执行。这样做的好处在于写操作速度加快,不会阻塞主线程,但是究其根本所有的读写任务还是同步的,所以读写效率并不高。代码如下:
- (void)setSomeString:(NSString *)someString{
dispatch_async(self.serialQueue, ^{
_someString = someString;
});
}
- (NSString *)someString{
__block NSString * localString;
dispatch_sync(self.serialQueue, ^{
localString = _someString;
})
return localString;
}
- 方案三:前面提到,读取操作我们可以并行去做,因为每个属性互不干扰,只要不与读取操作同事执行就可以,栅栏方法
dispatch_barrier_async
会在队列中必须单独执行,一定不会与其他任务并发,利用这个特性,我可以更好的实现需求。代码如下:
- (void)setSomeString:(NSString *)someString{
dispatch_barrier_async(self.syncQueue, ^{
_someString = someString;
})
}
- (NSString *)someString{
__block NSString * localString;
dispatch_sync(self.syncQueue, ^{
localString = _someString;
})
return localString;
}
以上代码在不同线程读取属性的时候,可以并发读取。同时又保证了读取时的同步。
- 扩展,我们思考一下为什么不建议属性用atomic关键字,大家都知道,其实atomic就是通过自旋锁来保证读写线程安全的,这样同一对象所有的属性读取都是同步的,而且会阻塞主线程,所以如果必须保证属性的线程安全的话,推荐方案三。那么引申出来,如何保证两类操作之间的线程安全呢?方案三是最好的选择。
四、GCD与NSOperation
GCD是纯C的API,而NSOperation是面向对象的,GCD是用块这种轻量级的数据结构来表示任务的,而GCD则是用更加重量级的对象来表示的,NSOperation是基于GCD的那NSOperation的好处如下。
- NSOperation可以通过cancel方法轻易的取消队列中的任务,而GCD把块安排到队列中后是无法取消的。
- GCD可以轻易的设置依赖关系,比如一个网络操作需要之前的5个任务处理完毕之后才能进行,我们可以将请求任务依赖于那五个任务,这样简单的可以实现需求,用GCD我们要用dispatch_group或者信号量的同步方式。
- 通过键值观察可以了解到NSOperation的声明周期,比如属性isCancel、isFinished等等。NSOperation适合进行比GCD更加精密的操作。
- NSOperation可以执行操作的优先级,队列中优先级高的任务先执行,GCD则没有直接操作优先级的功能,只能操作队列的优先级。
- NSOperation对象内置了很多方便的子类,比如NSBlockOperation。
五、用NSCache的几点好处
- 低内存时自动删减缓存,LRU(最近最少使用)缓存淘汰。
- 不同于NSDictionary,NSCache并不会拷贝键,而是会保留键,一些不支持拷贝的类也可以作为键。
- 可以设置最大开销,和数量,但这并不是对cache的硬限制,只做参考。
- NSCache可以配合NSPurgeableData使用,当NSPurgeableData对象被系统释放的时候,缓存自动清除。
六、bounds和frame的区别?
- bounds:相对自身来说的,控件的内部尺寸,如果修改了Bounds,子控件的相对位置也会发生改变。
- Frame:相对于父控件来说的,是控件的外部尺寸。
举个例子:
let view = UIView(frame: CGRect.init(x: 100, y: 100, width: 100, height: 100));
view.backgroundColor = UIColor.red;
self.view.addSubview(view);
let subView = UIView(frame: CGRect.init(x: 20, y: 20, width: 60, height: 60));
subView.backgroundColor = UIColor.blue;
view.addSubview(subView);
print("viewFrame:\(view.frame) viewBounds:\(view.bounds) subViewFrame:\(subView.frame)");
view.transform = CGAffineTransform.init(scaleX: 2, y: 2);
print("viewFrame:\(view.frame) viewBounds:\(view.bounds) subViewFrame:\(subView.frame)");
//打印结果:
viewFrame:(100.0, 100.0, 100.0, 100.0) viewBounds:(0.0, 0.0, 100.0, 100.0) subViewFrame:(20.0, 20.0, 60.0, 60.0)
viewFrame:(50.0, 50.0, 200.0, 200.0) viewBounds:(0.0, 0.0, 100.0, 100.0) subViewFrame:(20.0, 20.0, 60.0, 60.0)
如上代码给一个视图放大而被,发现其frame改变了,因为锚点默认是中心,所以按中心放大,但是其bounds没有改变,subView的frame也没有改变。