面试
1.mvvm的开发模式 可以解释一个微博的cell就是一个典型的vm,为什么用viewmodel,(减少之前mvc模式里面c里面的代码量,管理代码更加的复合面向对象的思维)
viewmodel的理解(View绑定到ViewModel,然后执行一些命令在向它请求一个动作。而反过来,ViewModel跟Model通讯,告诉它更新来响应UI。这样便使得为应用构建UI非常的容易。)
2.我经常为一个类写代理方法@optional(这是选择性的实现的方法) 和required(必须要实现的)的方法,比如同一个cell可以有多个不同的应用场地,所以我们要使用代理方法。
3.单例,为什么用单例:
在我认为单例最大的优点就是:通过单例方式访问的实例,都是同一个。
这个优点也是我在蓝牙音乐盒项目中使用单例模式实现外设控制类和AVFoundation类的原因。你们想,假设我在a,b,c页面都会调用到_player,那么我再a页面点击播放、上一首、下一首等等操作后,怎么让b,c页面知道并同步呢?我一开始使用的是通知。可是我试了后发现,最好的还是单例。单例模式下的无论在那个类访问_player实例,它的状态都是最新的,也就是a点了播放,那么我在b,c页面访问_play实例,就可以知道它的状态是播放的,不需要通知。
单利的优点也是导致它节省内存的一个衍生优点。
解释 单例用处
根据单例模式的定义,我们知道一般两种情况下使用单例:
(1)系统中某种对象只能存在一个,多了就会出问题
(2)系统中某种对象实例只需要一个就够用了,多了占内存
对于第一种情况,我们必须使用单例,对于第二种情况,我们虽然可以不用单例,但是单例是更优的选择
下面我们来看看dispatch_once的原理:
dispatch_once主要是根据onceToken的值来决定怎么去执行代码。
当onceToken= 0时,线程执行dispatch_once的block中代码
当onceToken= -1时,线程跳过dispatch_once的block中代码不执行
当onceToken为其他值时,线程被线程被阻塞,等待onceToken值改变
当线程首先调用shareInstance,某一线程要执行block中的代码时,首先需要改变onceToken的值,再去执行block中的代码。这里onceToken的值变为了140734731430192。
这样当其他线程再获取onceToken的值时,值已经变为140734731430192。其他线程被阻塞。
当block线程执行完block之后。onceToken变为-1。其他线程不再阻塞,跳过block。
下次再调用shareInstance时,block已经为-1。直接跳过block,直接访问已经创建完成的那个内存地址的对象,也就是我们想要的单例。
4.通知,本地通知(基本的作用传值以及)和远程的通知(远程通知就是服务器发的推送消息)
键盘通知
对于键盘,我们经常需要注册与移除注册通知:
UIKeyboardWillShowNotification// 键盘即将显示
UIKeyboardDidShowNotification// 键盘显示完毕
UIKeyboardWillHideNotification// 键盘即将隐藏
UIKeyboardDidHideNotification// 键盘隐藏完毕
UIKeyboardWillChangeFrameNotification// 键盘的位置尺寸即将发生改变
UIKeyboardDidChangeFrameNotification// 键盘的位置尺寸改变完毕
5. kvc(Key-value coding) kvo(Key-value observe),
kvc最大的用处就是可以知道系统没有提供的一些类属性,我们通过kvc取到系统的这些属性,进行重新的自定义。平常就行用它取对象的值更加的方便,比如一个对象A里面包含着另外一个对象B,但是我们要的是对象B的属性X,我们就可以通过这个kvc直接取出来这个属性,而不需要去建立对象B。
kvo 如果你想对于一个对象的属性发生了变化,立即来相应其他的事件,最常见例子就是数据的变化影响图像的变化,通过监听的回调事件来控制你要执行代码
KVC(Key-value coding)键值编码,顾名思义,额,简单来说,是可以通过对象属性名称(Key)直接给属性值(value)编码(coding)“编码”可以理解为“赋值”。这样可以免去我们调用getter和setter方法,从而简化我们的代码,也可以用来修改系统控件内部属性(这个黑魔法且用且珍惜)。
setValuesForKeysWithDictionary这个方法直接是nsobject对象类型直接调用的,用来给对象类型进行赋值,转换成一个有内容的对象类型,
关于kvc的黑魔法:修改系统控件内部属性
系统的UIPageControl是圆点,但是我们自己结合runtime以及setValuesForKeysWithDictionary这个系统方法,可以打印出我们自己想要的属性,并且是系统自己没有在这个类里面提供的属性,便于我们自己进行自定义。
下面这句红色的还没有验证。
需要强调的是KVO的回调要被调用,属性必须是通过KVC的方法来修改的,如果是调用类的其他方法来修改属性,这个观察者是不会得到通知的。
这解释了为什么我们用kvo的地方很少,在实际开发中 KVO 使用的情景并不多,更多时候还是用 Delegate 或 NotificationCenter,kvo比较适合底层的研究
6,内存管理 写在了13条那里
7.runtime 运行时系统,写一个方法测试kvo检测出来的uipagecontrol的属性,
8.缓存的思想;runtime里面的objc_cache就是一个字典,不用每次都到method list里面找对应的oc里面的函数名字,我的点赞也是这样设计,不是点一次就发送一次网络请求,而是在你离开这个页面的时候发送网络请求。
但这种实现有个问题,效率低。但一个 class 往往只有 20% 的函数会被经常调用,可能占总调用次数的 80% 。每个消息都需要遍历一次 objc_method_list 并不合理。如果把经常被调用的函数缓存下来,那可以大大提高函数查询的效率。这也就是 objc_class 中另一个重要成员 objc_cache 做的事情 - 再找到 foo 之后,把 foo 的 method_name 作为 key ,method_imp 作为 value 给存起来。当再次收到 foo 消息的时候,可以直接在 cache 里找到,避免去遍历 objc_method_list.
9.kvo的黑色魔法的原理:打印出了objc_class结构体,这个结构体里面包含了很多的这个类的属性(可以看到运行时一个类还关联了它的超类指针,类名,成员变量,方法,缓存,还有附属的协议。 ),所以我们才可以通过kvo的方式来改变系统底层提供的不可见的属性,同样解释了Category不能添加属性的原因,因为属性是系统的,不可以自己修改,只能增加一些拓展方法。
10.afnetworking的get post的区别
GET请求, 将参数直接写在访问路径上. 操作简单, 不过容易让外界看到, 安全性不高, 地址最多 255 字节.
POST 请求, 将参数放到 body 里面, POST请求的操作相对复杂, 需要将参数和地址分开, 不过安全性高,参数放在body里面, 不容易被捕获.
平时直接用的afnet发送请求,这两个区别很小没有觉察
POST的安全性要比GET的安全性高。注意:这里所说的安全性和上面GET提到的“安全”不是同个概念。上面“安全”的含义仅仅是不作数据修改,而这里安全的含义是真正的Security的含义,比如:通过GET提交数据,用户名和密码将明文出现在URL上,因为(1)登录页面有可能被浏览器缓存, (2)其他人查看浏览器的历史纪录,那么别人就可以拿到你的账号和密码了,除此之外,使用GET提交数据还可能会造成Cross-site request forgery攻击 .
1. GET使用URL或Cookie传参。而POST将数据放在BODY中。
2. GET的URL会有长度上的限制,则POST的数据则可以非常大。
3. POST比GET安全,因为数据在地址栏上不可见。
11.MPMoviePlayerController的自定义
12.coredata的内部原理,
1.搭建上下文环境
创建两中类型的context,本质就是查询的线程(分为bgm线程,还有另外一个是最终确定将数据的改变存储到数据库的线程,数据库的设计其实有好几种方式,待会研究)
2.添加数据到数据库
1. // 传入上下文,创建一个Person实体对象
2. NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];
3. // 设置Person的简单属性
4. [person setValue:@"MJ" forKey:@"name"];
5. [person setValue:[NSNumber numberWithInt:27] forKey:@"age"];
6. // 传入上下文,创建一个Card实体对象
7. NSManagedObject *card = [NSEntityDescription insertNewObjectForEntityForName:@"Card" inManagedObjectContext:context];
8. [card setValue:@"4414241933432" forKey:@"no"];
9. // 设置Person和Card之间的关联关系
10. [person setValue:card forKey:@"card"];
11. // 利用上下文对象,将数据同步到持久化存储库
12. NSError *error = nil;
13. BOOL success = [context save:&error];
14. if (!success) {
15. [NSException raise:@"访问数据库错误" format:@"%@", [error localizedDescription]];
16. }
17. // 如果是想做更新操作:只要在更改了实体对象的属性后调用[context save:&error],就能将更改的数据同步到数据库
3.从数据库中查询数据
1. // 初始化一个查询请求
2. NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
3. // 设置要查询的实体
4. request.entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:context];
5. // 设置排序(按照age降序)
6. NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:NO];
7. request.sortDescriptors = [NSArray arrayWithObject:sort];
8. // 设置条件过滤(搜索name中包含字符串"Itcast-1"的记录,注意:设置条件过滤时,数据库SQL语句中的%要用*来代替,所以%Itcast-1%应该写成*Itcast-1*)
9. NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name like %@", @"*Itcast-1*"];
10. request.predicate = predicate;
11. // 执行请求
12. NSError *error = nil;
13. NSArray *objs = [context executeFetchRequest:request error:&error];
14. if (error) {
15. [NSException raise:@"查询错误" format:@"%@", [error localizedDescription]];
16. }
17. // 遍历数据
18. for (NSManagedObject *obj in objs) {
19. NSLog(@"name=%@", [obj valueForKey:@"name"]
20. }
需要注意的coredata的线程的安全问题
在实际的开发当中,我遇到了各种各样的问题,如果是多线程操作数据库的话,个人建议:
1: 最好一个线程对应一个NSManagedObjectContext。如果只有一个NSManagedObjectContext,并且多个线程对其进行操作,回出现许多不清不楚的问题。
2:在每一个线程对应一个NSManagedObjectContext的时候,尽量一个线程只读写与其对应的context。在其完成操作的时候通知另外的线程去修改其对应的context。在apple的api中有NSManagedObjectContextDidSaveNotification, 它可以帮助你通知修改其它的contexts。
上面的代码只是简单利用NSManagedObjectContextDidSaveNotification,当子线程修改了数据库以后,通知主线程去修改其对应的context。当然childThreadManagedObjectContext的创建是在创建子线程的时候进行的。
ios5以后,有了NSPrivateQueueConcurrencyType,会自动的创建一个新线程来存放NSManagedObjectContext而且它还会自动创建NSPersistentStoreCoordinator,所以就不用担心多线程的问题。
13.内存管理
首先是对象的arc的机制,计数器,当资源不用这个对象的时候计数器就会变为零,(至于什么时候变为零用runloop解释)创建的对象必须是strong的类型,因为只有是strong的类型才可以对这个对象进行技术,以便于arc在合适的时候进行释放,(什么时候释放,但因为ARC不一定会及时释放,所以程序有时候可能会占用内存较大,我们通过instrument这个工具找到内存增到的代码块,进行手动管理内存)
14,网络线程的布局,主要是在发送网络请求的时候尽量避免用主线程,应该用gcd建立子线程来发送网络请求
dispatch_group_t dispatchGroup = dispatch_group_create();
dispatch_group_enter(dispatchGroup);
[MALAFNManger getDataWithUrl:Url1 parameters:nil finish:^(RequestResult *result) {
NSLog(@"第一个请求完成");
dispatch_group_leave(dispatchGroup);
} des:@"第一个url"];
dispatch_group_enter(dispatchGroup);
[MALAFNManger getDataWithUrl:Url2 parameters:nil finish:^(RequestResult *result) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(10);//网络请求结束后回调是在主线程如果sleep放在外面会阻塞主线程
NSLog(@"第二个请求完成");
dispatch_group_leave(dispatchGroup);
});
} des:@"第二个url"];
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
NSLog(@"请求完成");
});
这就是使用dispatch_group来进行线程同步
网络超时的时候dispatch_group_notify的block里面直接写停止网络请求
当队列dispatch_queue_t queue上的所有任务执行完毕时会执行dispatch_group_notify里的dispatch_block_t block的代码
处理网络的并发的问题,主要通过以下几个量
dispatch_group_enter(dispatch_group_t)、dispatch_group_leave(dispatch_group_t),dispatch_group_notify(这个block是所有的线程执行完成之后的才走的方法,可以处理超时,以及顺利完成网络请求之后回到主线程的操作)
15,与NSURLConnection相比,NSURLSession最直接的改善就是提供了配置每个会话的缓存,协议,cookie和证书政策 (credential policies),甚至跨应用程序共享它们的能力。这使得框架的网络基础架构和部分应用程序独立工作,而不会互相干扰。每一个NSURLSession 对象都是根据一个NSURLSessionConfiguration初始化的,该NSURLSessionConfiguration指定了上面提到的 政策,以及一系列为了提高移动设备性能而专门添加的新选项。
NSURL *URL = [NSURL URLWithString:@"http://example.com"];
1.建立请求
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
2.建立会话
NSURLSession *session = [NSURLSession sharedSession];
3.发送请求,通过会话来发送请求
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
// ...
}];
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request
fromData:data
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
// ...
}];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request
completionHandler:
^(NSURL *location, NSURLResponse *response, NSError *error) {
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSURL *documentsDirectoryURL = [NSURL fileURLWithPath:documentsPath];
return [documentsDirectoryURL URLByAppendingPathComponent:[[response URL] lastPathComponent]];
}];
(涉及到请求的返回的task的类型,有三种NSURLSessionTask,它是一个抽象子类,它有三个具体的子类是可以直接使用的:NSURLSessionDataTask,NSURLSessionUploadTask和NSURLSessionDownloadTask。这三个类封装了现代应用程序的三个基本网络任务:获取数据,比如JSON或XML,以及上传下载文件。)
17.直播的通信原理,
以及直播会遇到的问题(比如直播的卡顿问题,网速不好的情况,最主要的就应该是卡顿的问题,还有就是控制服务器那边的延迟来达到解决直播的延迟的问题,)
HTTP Live Streaming(HLS)是苹果公司(Apple Inc.)实现的基于HTTP的流媒体传输协议
RTMP协议是一个互联网TCP/IP五层体系结构中应用层的协议
RTMP是Real Time Messaging Protocol(实时消息传输协议) 的首字母缩写。该协议基于TCP,是一个协议族,
RTMP(Real Time Messaging Protocol)实时消息传送协议是Adobe Systems公司为Flash播放器和服务器之间音频、视频和数据传输 开发的开放协议。
这是直播遇到的问题的解决方案
HLS是以点播的技术方式来实现直播。由于数据通过HTTP协议传输,所以完全不用考虑防火墙 或者代理的问题,而且分段文件的时长很短,客户端可以很快的选择和切换码率,(解决网路不好的时候切换线路的问题,底层原理就是实时的根据客户端的带宽来发送对应于客户端的一些窗口大小,来适应客户端,如果客户端的窗口小,服务器大那就会造成传过来的数据排队,最后甚至直接断开和服务器的链接。反之,则会限制传输的数据量,导致视频一顿顿的,这些问题都可以通过切换码率来解决。切换码率的原理就是丢包率,码率越高丢包率越低,画质也就越好)以适应不同带宽条件下的播放。不过HLS的这种技术特点,决定了它的延迟一般 总是会高于普通的流媒体直播协议
关于怎么获取直播的地址的问题
跟下载课程的原理是一样的,先得通过网络请求获得这个直播的直播地址(我们是获得一个课程对象,这个对象是我们加载app的时候提前加载存到本地的,所以每次点击直播的窗口的时候,每个直播的窗口就类似于于我们每个课程,点击直播的时候我们会根据这个对象提供的直播地址进行直播,也就是把直播地址传给我们的b站的sdk里面就可以直接进行直播了 )
流媒体卡顿的原因
http渐进下载流媒体播放,是推流之后,服务器先下载最前面的一部分数据,然后之后会进行边播边下载。
tcp无法保证重传的数据还是在那些丢包的数据的时间点上,所以画面会因为丢帧导致卡顿,之后会出现重复的播放画面是因为tcp传输的数据接收到了。
视频的直播暂停的实现;主要是实现了rtsp/rtp的协议
通过在客户端上面传递rtsp的会话命令,完成直播的开始暂停和重新连接
他有一下几个命令
(1)setup,使服务器分配媒体流资源,并启动一个rtsp的会话
(2)play 在setup启动会话和分配资源的某个流上面开始进行数据的传输
(3)pause:暂停中指一个流的数据传输而不释放服务器的资源
(4)teardown:释放服务器上的流资源,结束rtsp会话
苹果的http live streaming协议 仅支持iOS8以上,压缩编码的特定标准,音视频流目前仅支持h.264视频,和aac音频。m3u8结尾的url是我们经常用的url类型