欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基础

程序员文章站 2022-03-03 16:27:36
...

1、父类实现深拷贝时,子类如何实现深度拷贝。父类没有实现深拷贝时,子类如何实现深度拷贝。

深拷贝同浅拷贝的区别:浅拷贝是指指针拷贝,对一个对象进行浅拷贝,相当于对指向对象的指针进行复制,产生一个新的指向这个对象的指针,那么就是有两个指针指向同一个对象,这个对象销毁后两个指针都应该置空。深拷贝是对一个对象进行拷贝,相当于对对象进行复制,产生一个新的对象,那么就有两个指针分别指向两个对象。当一个对象改变或者被销毁后拷贝出来的新的对象不受影响。
• 实现深拷贝需要实现NSCoying协议,实现- (id)copyWithZone:(NSZone *)zone 方法。当对一个property属性含有copy修饰符的时候,在进行赋值操作的时候实际上就是调用这个方法。
• 父类实现深拷贝之后,子类只要重写copyWithZone方法,在方法内部调用父类的copyWithZone方法,之后实现自己的属性的处理
• 父类没有实现深拷贝,子类除了需要对自己的属性进行处理,还要对父类的属性进行处理。

2、KVO,NSNotification,delegate及block的区别

  • KVO就是cocoa框架实现的观察者模式,一般同KVC搭配使用,通过KVO可以检测到一个值的变化,比如view的高度变化。是一对多的关系,一个值变化会通知所有观察者。
  • NSNotification是通知,也是一对多的场景。在某些情况下,KVO和NSNotification是一样的都是状态变化后告知对方。NSNotification的特点,就是需要被观察者主动发出通知,然后观察者注册监听后再来进行响应,比KVO多了发送通知的一步,但是其优点就是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,使用也灵活
  • delegate是代理,就是我不想做的事情交给别人做。比如狗需要吃饭,就是通过delegate通知主人,主人就会给它做饭,盛饭,倒水,这些狗不需要关心,只需要调用delegate就可以了,又其它类完成所需要的操作。所有delegate是一对一关系
  • block就是delegate的另一种形式,是函数式编程的一种形式。使用场景跟delegate一样,相比delegate更加灵活,而且代理的实现更直观
  • KVO的使用场景是数据,需求是数据变化,比如股票价格变化,我们一般使用KVO(观察者模式)。delegate一般使用场景是行为,需求是需要别人帮我做一件事情,比如买卖股票,我们一般使用delegate
  • Notification一般是进行全局通知,比如好消息一出,通知大家去买。delegate是强关联,就是委托和代理双方相互知道,你委托别人买股票你就需要知道经纪人,经纪人也不要知道自己的顾客。Notification是弱关联,好消息发出,你不需要知道是谁发的也可以做出相应反应,同理发消息的人也不需要知道接受的人也可以正常发消息

3、将一个函数在主线程执行的4种方法
• GCD方法,通过向主线程队列发送一个block块,使block里的方法可以在主线程中执行。

dispatch_async(dispatch_get_main_queue(),^{
    需要执行的方法
});
  • NSOperation方法
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; // 主队列
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    需要执行的方法
}];
[mainQueue addOperation:operation];
  • NSThread
[self performSelector:@selector(method) onThread [NSThread mainThread] withObject:nil waitUntilDone:YES modes:nil];
[self performSelectorOnMainThread:@selector(method) withObject:nil waitUntilDone:YES];
[[NSThread mainThread] performSelector:@selector(method) withObject:nil];
  • RunLoop方法
[[NSRunLoop mainRunLoop] performSelector:@selector(method) withObject:nil];

4、如何让计时器调用一个类方法
• 计时器只能调用实例方法,但是可以在这个实例方法里面调用静态方法。

• 使用计时器需要注意,计时器一定要加入RunLoop中,并且选好model才能运行。scheduledTimerWithTimeInterval方法创建一个计时器并加入到RunLoop中所以可以直接使用。

• 如果计时器的repeats选择YES说明这个计时器会重复执行,一定要在合适的时机调用计时器的invalid。不能在dealloc中调用,因为一旦设置为repeats 为yes,计时器会强持有self,导致dealloc永远不会被调用,这个类就永远无法被释放。比如可以在viewDidDisappear中调用,这样当类需要被回收的时候就可以正常进入dealloc中了。

[NSTimer scheduledTimerWithTimeInterval:1 target:self
selector:@selector(timerMethod) userInfo:nil repeats:YES];
-(void)timerMethod
{
    调用类方法
    [[self class] staticMethod];
}
-(void)invalid
{
    [timer invalid];
    timer =nil;
}

5、如何重写类方法

1、在子类中实现一个同基类名字一样的静态方法
2、在调用的时候不要使用类名调用,而是使用[self
class]的方式调用。原理,用类名调用是早绑定,在编译期绑定,用[self class]是晚绑定,在运行时决定调用哪个方法

6、iOS 核心框架
• CoreAnimation
• CoreGraphics
• CoreLocation
• AVFoundation
• Foundation

7、iOS本地数据存储都有几种方式?

①.NSkeyedArchiver:采用归档的形式来保存数据,该数据对象需要遵守NSCoding协议,并且该对象对应的类必须提供encodeWithCoder:和initWithCoder:方法.前一个方法告诉系统怎么对对象进行编码,而后一个方法则是告诉系统怎么对对象进行解码.
②.NSUserDefaults:用来保存应用程序设置和属性,用户保存的数据.用户再次打开程序或者开机后这些数据仍然存在.NSUserDefaults可以存储的数据类型包括:NSData,NSString,NSNumber,NSDate,NSArray.NSDictionary,其他类型的数据需要先行转换.
③.Write写入方式:永久保存在磁盘中.具体:a.获得文件保存的路径.b.生成该路径下的文件,c,往文件中写入数据.d.从文件中读出数据.
④.SQLite:采用SQLite数据库来存储数据,SQLite作为一种轻量级数据库.具体:a.添加SQLite相关的库以及头文件,b.使用数据库存数数据:打开数据库,编写数据库语句,执行,关闭数据库.另:写入数据库,字符串可以采用char方式,而从数据库中取出char类型,当char类型有表示中文字符时,会出现乱码,这是因为数据库默认使用ascII编码方式,所以想要正确从数据库中取出中文,需要使用NSString来接受从数据库取出的字符串.
⑤.CoreData:原理是对SQLite的封装,开发者不需要接触sql语句,就可以对数据库进行操作.

8、什么是安全释放?
先释放再置空.

9、什么是序列化和反序列化,可以用来做什么?如何在OC中实现复杂对象的存储.

序列化和反序列化:归档和反归档,进行本地化,进行数据存储.
CoreData:数据托管.有四种存储方式:xml,sqlite,二进制,内存.
遵循NSCoding协议之后,进行归档即可实现复杂对象的存储.

10、事件循环,是线程里面的一个组件.主线程的RunLoop是自动开启的.分为:计时源(timer source),事件源(输入源):input source.防止CPU中断(保证程序执行的线程不会被系统终止).

Runloop提供了一种异步执行代码的机制,并不能并行执行任务,是事件接收和分发机制的一个实现.每一个线程都有其对应的RunLoop,但是默认非主线程的RunLoop是没有运行的,需要为RunLoop添加至少一个事件源,然后run它.
一般情况下我们是没有必要去启动线程的RunLoop的,除非你在一个单独的线程中需要长时间的检测某个事件.
RunLoop,正如其名所示,是线程进入和被线程用来响应事件以及调用事件处理函数的地方.
input source传递异步事件,通常是来自其他线程和不同程序的消息。
timer source传递同步事件.
当有事件发生时,RunLoop会根据具体的事件类型通知应用程序作出响应.
当没有事件发生时,RunLoop会进入休眠状态,从而到达省电的目的.
当事件再次发生时,RunLoop会被重新唤醒,处理事件.
一般在开发中很少会主动创建RunLoop,而通常会把事件添加到RunLoop中.

11、ViewController的didReceiveMemoryWarning是在什么时候被调用的?

1.当应用程序的内存使用接近系统的最大内存使用时,应用会向系统发送内存警告,这时候系统会调用方法向所有ViewController发送内存警告.
2.打开系统相机.
3.加载高清图片.
默认操作:把里面没有用的对象进行释放.

12、事件响应者链

如果当前view是控制器的view,那么就传递给控制器
如果控制器不存在,则将其传递给它的父控件
在视图层次结构的最顶层视图也不能处理接收到的事件或消息,则将事件或消息传递给UIWindow对象进行处理
如果UIWindow对象也不处理,则将事件或消息传递给UIApplication对象
如果UIApplication也不能处理该事件或消息,则将其丢弃
补充:如何判断上一个响应者
如果当前这个view是控制器的view,那么控制器就是上一个响应者
如果当前这个view不是控制器的view,那么父控件就是上一个响应者

13、触摸事件的传递

触摸事件的传递是从父控件传递到子控件
如果父控件不能接收触摸事件,那么子控件就不可能接收到触摸事件
不能接受触摸事件的四种情况
不接收用户交互,即:userInteractionEnabled = NO
隐藏,即:hidden = YES
透明,即:alpha <= 0.01
未启用,即:enabled = NO
提示:UIImageView的userInteractionEnabled默认就是NO,因此UIImageView以及它的子控件默认是不能接收触摸事件的
如何找到最合适处理事件的控件:
首先,判断自己能否接收触摸事件
§ 可以通过重写hitTest:withEvent:方法验证
其次,判断触摸点是否在自己身上
§ 对应方法pointInside:withEvent:
从后往前(先遍历最后添加的子控件)遍历子控件,重复前面的两个步骤
如果没有符合条件的子控件,那么就自己处理

14、UIScrollView的contentSize能否在viewDidLoad中设置?

  • 因为UIScrollView的内容尺寸是根据其内部的内容来决定的,所以是可以在viewDidLoad中设置的
    补充:(这仅仅是一种特殊情况)
  • 前提,控制器B是控制器A的一个子控制器,且控制器B的内容只在控制器A的view的部分区域中显示
    假设控制器B的view中有一个UIScrollView这样一个子控件
  • 如果此时在控制器B的viewDidLoad中设置UIScrollView的contentSize的话会导致不准确的问题
  • 因为任何控制器的view在viewDidLoad的时候的尺寸都是不准确的,如果有子控件的尺寸依赖父控件的尺寸,在这个方法中设置会导致子控件的frame不准确,所以这时应该在下面的方法中设置子控件的尺寸

15、描述下SDWebImage里面给UIImageView加载图片的逻辑

SDWebImage 中为UIImageView 提供了一个分类UIImageView+WebCache.h, 这个分类中有一个最常用的接口sd_setImageWithURL:placeholderImage:,会在真实图片出现前会先显示占位图片,当真实图片被加载出来后在替换占位图片
• 加载图片的过程大致如下:
○ 首先会在SDWebImageCache 中寻找图片是否有对应的缓存, 它会以url
作为数据的索引先在内存中寻找是否有对应的缓存
○ 如果缓存未找到就会利用通过MD5处理过的key来继续在磁盘中查询对应的数据, 如果找到了, 就会把磁盘中的数据加载到内存中,并将图片显示出来
○ 如果在内存和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片
○ 下载后的图片会加入缓存中,并写入磁盘中
○ 整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来