面试题
怎么理解OC是动态语言,Runtime又是什么?
静态语言:如C语言,编译阶段就要决定调用哪个函数,如果函数未实现就会编译报错。
动态语言:如OC语言,编译阶段并不能决定真正调用哪个函数,只要函数声明过即使没有实现也不会报错。
我们常说OC是一门动态语言,就是因为它总是把一些决定性的工作从编译阶段推迟到运行时阶段。OC代码的运行不仅需要编译器,还需要运行时系统(Runtime Sytem)来执行编译后的代码。
Runtime是一套底层纯C语言API,OC代码最终都会被编译器转化为运行时代码,通过消息机制决定函数调用方式,这也是OC作为动态语言使用的基础。
1.动态方法交换示例
现在演示一个代码示例:在视图控制中,定义两个实例方法printA与printB,然后执行交换
- (void)printA{
NSLog(@"打印A......");
}
- (void)printB{
NSLog(@"打印B......");
}
//交换方法的实现,并测试打印
Method methodA = class_getInstanceMethod([self class], @selector(printA));
Method methodB = class_getInstanceMethod([self class], @selector(printB));
method_exchangeImplementations(methodA, methodB);
[self printA]; //打印B......
[self printB]; //打印A......
2.拦截并替换系统方法
Runtime动态方法交换更多的是应用于系统类库和第三方框架的方法替换。在不可见源码的情况下,我们可以借助Rutime交换方法实现,为原有方法添加额外功能,这在实际开发中具有十分重要的意义。
下面将展示一个拦截并替换系统方法的示例:为了实现不同机型上的字体都按照比例适配,我们可以拦截系统UIFont的systemFontOfSize方法,具体操作如下:
步骤1:在当前工程中添加UIFont的分类:UIFont +Adapt,并在其中添用以替换的方法。
+ (UIFont *)zs_systemFontOfSize:(CGFloat)fontSize{
//获取设备屏幕宽度,并计算出比例scale
CGFloat width = [[UIScreen mainScreen] bounds].size.width;
CGFloat scale = width/375.0;
//注意:由于方法交换,系统的方法名已变成了自定义的方法名,所以这里使用了
//自定义的方法名来获取UIFont
return [UIFont zs_systemFontOfSize:fontSize * scale];
}
步骤2:在UIFont的分类中拦截系统方法,将其替换为我们自定义的方法,代码如下:
//load方法不需要手动调用,iOS会在应用程序启动的时候自动调起load方法,而且执行时间较早,所以在此方法中执行交换操作比较合适。
+ (void)load{
//获取系统方法地址
Method sytemMethod = class_getClassMethod([UIFont class], @selector(systemFontOfSize:));
//获取自定义方法地址
Method customMethod = class_getClassMethod([UIFont class], @selector(zs_systemFontOfSize:));
//交换两个方法的实现
method_exchangeImplementations(sytemMethod, customMethod);
}
添加一段测试代码,切换不同的模拟器,观察在不同机型上文字的大小:
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 100, 300, 50)];
label.text = @"测试Runtime拦截方法";
label.font = [UIFont systemFontOfSize:20];
[self.view addSubview:label];
3.实现分类添加新属性
现在演示一个代码示例:为UIImage增加一个分类:UIImage+Tools,并为其设置关联属性urlString(图片网络链接属性),相关代码如下:
//UIImage+Tools.h文件中
UIImage+Tools.m
@interface UIImage (Tools)
//添加一个新属性:图片网络链接
@property(nonatomic,copy)NSString *urlString;
@end
//UIImage+Tools.m文件中
#import "UIImage+Tools.h"
#import <objc/runtime.h>
@implementation UIImage (Tools)
//set方法
- (void)setUrlString:(NSString *)urlString{
objc_setAssociatedObject(self,
@selector(urlString),
urlString,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//get方法
- (NSString *)urlString{
return objc_getAssociatedObject(self,
@selector(urlString));
}
//添加一个自定义方法,用于清除所有关联属性
- (void)clearAssociatedObjcet{
objc_removeAssociatedObjects(self);
}
@end
测试文件中:
UIImage *image = [[UIImage alloc] init];
image.urlString = @"http://www.image.png";
NSLog(@"获取关联属性:%@",image.urlString);
[image clearAssociatedObjcet];
NSLog(@"获取关联属性:%@",image.urlString);
//打印:
//获取关联属性:http://www.image.png
// 获取关联属性:(null)
iOS常用-锁
我们在使用多线程的时候多个线程可能会访问同一块资源,这样就很容易引发数据错乱和数据安全等问题,这时候就需要我们保证每次只有一个线程访问这一块资源,锁 应运而生。
@synchronized关键字加锁 互斥锁,性能较差不推荐使用
@synchronized(这里添加一个OC对象,一般使用self) {
这里写要加锁的代码
}
NSLock 互斥锁 不能多次调用 lock方法,会造成死锁
//创建锁
_mutexLock = [[NSLock alloc] init];
//加锁
[_mutexLock lock];
//这里写要加锁的代码
//解锁
[_mutexLock unlock];
RunLoop
RunLoop的实质是一个死循环,用于保证程序的持续运行,只有当程序退出的时候才会结束
1,保持程序的持续运行
2,处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
3,节省CPU资源,提高程序性能:该做事的时候做事,该休息的时候休息
RunLoop模式常用
NSDefaultRunLoopMode->默认模式,主线程中默认是NSDefaultRunLoopMode
UITrackingRunLoopMode->视图滚动模式,RunLoop会处于该模式下
runloop和线程的关系
主线程Runloop已经创建好了,子线程的runloop需要手动创建 ;一条线程对应一个RunLoop对象,每条线程都有唯一一个与之对应的RunLoop对象。一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出
runloop的实际使用
1。RunLoop运行模式对NSTimer定时器的影响。
2。ImageView推迟显示-->实现图片加载性能优化
3。后台常驻线程(下载文件、后台播放音乐等)
iOS持久化的方式有什么
plist文件(属性列表)
preference(偏好设置):保存的位置是Library/Preferences 这个目录下的 plist 文件
NSKeyedArchiver(归档)
SQLite 3
CoreData
简述block
block是代码块,其本质和变量类似。不同的是代码块存储的数据是一个函数体。block使用最多的是存储代码块和回调。
@property (copy , nonatomic) void (^touchXxxx) (void);
!_touchXxxx ? : _touchXxxx();
SDwebImage加载图片的 原理
1、首先在webimagecache中寻找图片对应的缓存,它是以url为数据索引先在内存中查找是否有缓存;
2、如果没有缓存,就通过md5处理过的key来在磁盘中查找对应的数据,如果找到就会把磁盘中的数据加到内存中,并显示出来;
3、如果内存和磁盘中都没有找到,就会向远程服务器发送请求,开始下载图片;
4、下载完的图片加入缓存中,并写入到磁盘中;
5、整个获取图片的过程是在子线程中进行,在主线程中显示。
GCD,NSOperationQueue
事实:GCD是面向底层的C语言的API, NSOperationQueue是基于GCD面向OC的封装
GCD:
一般的需求很简单的多线程操作,用GCD都可以了,简单高效。
1,GCD 栅栏方法:dispatch_barrier_async
在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作。
/**
* 栅栏方法 dispatch_barrier_async
*/
- (void)barrier {
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任务1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_async(queue, ^{
// 追加任务2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_barrier_async(queue, ^{
// 追加任务 barrier
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_async(queue, ^{
// 追加任务3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_async(queue, ^{
// 追加任务4
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
}
});
}
2,GCD 延时执行方法:dispatch_after
/**
* 延时执行方法 dispatch_after
*/
- (void)after {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2.0秒后异步追加任务代码到主队列,并开始执行
NSLog(@"after---%@",[NSThread currentThread]); // 打印当前线程
});
}
3,一次性代码(只执行一次):dispatch_once
/**
* 一次性代码(只执行一次)dispatch_once 我们在创建单例、或者有整个程序运行过程中只执行一次的代码时
*/
- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的)
});
}
4,GCD的队列组 dispatch_group
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行1个耗时的操作
NSLog(@"耗时操作");
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行1个耗时的操作
NSLog(@"耗时操作");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程...
});
NSOperationQueue:
各个操作之间有依赖关系、操作需要取消暂停、并发管理、控制操作之间优先级,限制同时能执行的线程数量.让线程在某时刻停止/继续等
1,控制最大并发数
// 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 设置最大并发操作数
// queue.maxConcurrentOperationCount = 2;
queue.maxConcurrentOperationCount = 1; // 就变成了串行队列
// 添加操作
[queue addOperationWithBlock:^{
NSLog(@"1-----%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.01];
}];
[queue addOperationWithBlock:^{
NSLog(@"2-----%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.01];
}];
2,最吸引人的地方是它能添加操作之间的依赖关系
比如说有A、B两个操作,其中A执行完操作,B才能执行操作,那么就需要让B依赖于A
//操作依赖
- (void)addDependency
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1-----%@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2-----%@", [NSThread currentThread]);
}];
[op2 addDependency:op1]; // 让op2 依赖于 op1,则先执行op1,在执行op2
[queue addOperation:op1];
[queue addOperation:op2];
}
//可以看到,无论运行几次,其结果都是op1先执行,op2后执行。
3,NSOperation、NSOperationQueue 线程间的通信
/**
* 线程间通信
*/
- (void)communication {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// 2.添加操作
[queue addOperationWithBlock:^{
// 异步进行耗时操作
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
// 回到主线程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// 进行一些 UI 刷新等操作
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
}];
}
1.串行队列+同步任务:不会开启新的线程,任务逐步完成。
2.串行队列+异步任务:开启新的线程,任务逐步完成。
3.并发队列+同步任务:不会开启新的线程,任务逐步完成。
4.并发队列+异步任务:开启新的线程,任务同步完成。
我们如果要让任务在新的线程中完成,应该使用异步线程。为了提高效率,我们还应该将任务放在并发队列中。因此在开发中使用最多的是并发队列+异步任务
-(void)test7{
//并发同步--可以看到,程序没有并发执行,而且没有开启新线程,是在主线程执行的
NSLog(@"start");
dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"1----:%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"2----:%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"3----:%@",[NSThread currentThread]);
}
});
NSLog(@"end");
}
2019-03-05 14:46:21.202900+0800 text[1765:21622] start
2019-03-05 14:46:21.203168+0800 text[1765:21622] 1----:<NSThread: 0x6000025baf40>{number = 1, name = main}
2019-03-05 14:46:21.203318+0800 text[1765:21622] 1----:<NSThread: 0x6000025baf40>{number = 1, name = main}
2019-03-05 14:46:21.203428+0800 text[1765:21622] 2----:<NSThread: 0x6000025baf40>{number = 1, name = main}
2019-03-05 14:46:21.203540+0800 text[1765:21622] 2----:<NSThread: 0x6000025baf40>{number = 1, name = main}
2019-03-05 14:46:21.203645+0800 text[1765:21622] 3----:<NSThread: 0x6000025baf40>{number = 1, name = main}
2019-03-05 14:46:21.203744+0800 text[1765:21622] 3----:<NSThread: 0x6000025baf40>{number = 1, name = main}
2019-03-05 14:46:21.203853+0800 text[1765:21622] end
-(void)test8{
//并发异步--可以看到,是并发执行的,而且开启了不止一个新线程。
NSLog(@"start");
dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"1----:%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"2----:%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"3----:%@",[NSThread currentThread]);
}
});
NSLog(@"end");
}
2019-03-05 14:56:36.803048+0800 text[1989:26884] start
2019-03-05 14:56:36.803277+0800 text[1989:26884] end
2019-03-05 14:56:36.803387+0800 text[1989:26916] 1----:<NSThread: 0x600000074200>{number = 3, name = (null)}
2019-03-05 14:56:36.803543+0800 text[1989:26914] 2----:<NSThread: 0x600000076900>{number = 4, name = (null)}
2019-03-05 14:56:36.803635+0800 text[1989:26978] 3----:<NSThread: 0x600000084b40>{number = 5, name = (null)}
2019-03-05 14:56:36.803738+0800 text[1989:26916] 1----:<NSThread: 0x600000074200>{number = 3, name = (null)}
2019-03-05 14:56:36.803826+0800 text[1989:26914] 2----:<NSThread: 0x600000076900>{number = 4, name = (null)}
2019-03-05 14:56:36.803861+0800 text[1989:26978] 3----:<NSThread: 0x600000084b40>{number = 5, name = (null)}
-(void)test9{
//串行同步--可以看到是按顺输出的,是在同一个线程,但是没有开启新线程,是在主线程执行的
NSLog(@"start");
dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"1----:%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"2----:%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"3----:%@",[NSThread currentThread]);
}
});
NSLog(@"end");
}
2019-03-05 14:57:38.184850+0800 text[2026:27582] start
2019-03-05 14:57:38.185092+0800 text[2026:27582] 1----:<NSThread: 0x600001069440>{number = 1, name = main}
2019-03-05 14:57:38.185215+0800 text[2026:27582] 1----:<NSThread: 0x600001069440>{number = 1, name = main}
2019-03-05 14:57:38.185318+0800 text[2026:27582] 2----:<NSThread: 0x600001069440>{number = 1, name = main}
2019-03-05 14:57:38.185480+0800 text[2026:27582] 2----:<NSThread: 0x600001069440>{number = 1, name = main}
2019-03-05 14:57:38.185587+0800 text[2026:27582] 3----:<NSThread: 0x600001069440>{number = 1, name = main}
2019-03-05 14:57:38.185686+0800 text[2026:27582] 3----:<NSThread: 0x600001069440>{number = 1, name = main}
2019-03-05 14:57:38.185791+0800 text[2026:27582] end
-(void)test10{
//串行异步--可以看到是按顺输出的,是在同一个线程,而且开启了新线程,
NSLog(@"start");
dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"1----:%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"2----:%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"3----:%@",[NSThread currentThread]);
}
});
NSLog(@"end");
}
2019-03-05 14:58:50.021576+0800 text[2070:28513] start
2019-03-05 14:58:50.021959+0800 text[2070:28513] end
2019-03-05 14:58:50.022246+0800 text[2070:28543] 1----:<NSThread: 0x600002dc5480>{number = 3, name = (null)}
2019-03-05 14:58:50.022493+0800 text[2070:28543] 1----:<NSThread: 0x600002dc5480>{number = 3, name = (null)}
2019-03-05 14:58:50.022628+0800 text[2070:28543] 2----:<NSThread: 0x600002dc5480>{number = 3, name = (null)}
2019-03-05 14:58:50.022752+0800 text[2070:28543] 2----:<NSThread: 0x600002dc5480>{number = 3, name = (null)}
2019-03-05 14:58:50.022943+0800 text[2070:28543] 3----:<NSThread: 0x600002dc5480>{number = 3, name = (null)}
2019-03-05 14:58:50.023092+0800 text[2070:28543] 3----:<NSThread: 0x600002dc5480>{number = 3, name = (null)}
KVC,KVO
KVC(Key-value coding)键值编码,我们可以通过以字符串为Key对对象属性进行操作。
KVO(Key-Value-Observer)就是观察者模式。当一个对象被观察的属性发生变化时,观察者做出的处理
分类(category)
分类只能添加方法,不能添加属性
本类和分类的话,分类优先于本类的方法
类扩展(extension)
可以用来给当前类添加属性和新方法
协议 (protocol)
协议是委托别人做自己想做的事情,可以用来传值,或者用来监听、通知。代理(delegate)
深拷贝浅拷贝
浅拷贝:就是拷贝后,并没有进行真正的复制,而是复制的对象和原对象都指向同一个地址
深拷贝:是真正的复制了一份,复制的对象指向了新的地址
浅拷贝好比你的影子,你死了,影子也没了;深拷贝好比克隆人,你死了,它还在。
深拷贝和浅拷贝的本质是地址是否相同
copy: 对于可变对象为深拷贝,对于不可变对象为浅拷贝
mutableCopy:始终是深拷贝
copy方法返回的对象都是不可变对象
1118933-20170720203748536-592203434.png怎么定位突发bug
对于致命的Bug,我们可以通过Crash日志进行分析,常用的有第三方友盟统计
继承
继承是类中的一个重要的特性,他的出现使得我们没必要别写重复的代码,可重用性很高
苹果推送通知服务APNS
客户端:
客户端发送自身设备的 UDID 和 Bundle Identifier 给 APNs 服务器,经苹果服务器加密后生成 deviceToken,随后只需将用户的 deviceToken 发送服务器保存。
服务器:
服务器在需要给某个用户推送消息时,需要将消息内容和 deviceToken 一起发送给 APNs 服务器,苹果服务器对 deviceToken 解密后可以找到具体的设备,然后将消息推送给该用户。
降低耦合
耦合度的道理其实说起来很简单,就是模块之间相关联程度的度量,指模块与模块之间的关联性,所谓的低耦合就是将两个模块之间的关联性尽可能的降低,一个模块的改动对于其他模块的影响尽量小。
这样的话看起来很明了,平时简单的功能做起来也不难,比如一些简单的低耦合技巧:给tableViewCell赋值的时候,如果有dataSource,那么有些人会在tableView的代理中从dataSource取出需要的数据来赋值给cell,这样就增大了主视图的代码,增大了cell和主视图的联系,这时候就可以改为将dataSource里面的Model赋值给cell并重写setModel方法来实现低耦合。
1、少使用类的继承,多用接口隐藏实现的细节。
2、多用设计模式,比如采用MVC的设计模式就可以降低界面与业务逻辑的耦合度。
ARC基本原理
ARC的规则就是只要对象没有强指针引用,就会被释放掉,换而言之 只要还有一个强引用指针变量指向对象,那么这个对象就会存在内存中。弱指针指向的对象,会被自动变成空指针(nil指针),从而不会引发野指针错误。
ARC是Automatic Reference Counting(自动引用计数器)的简称
当一个对象被持有的时候计数加一,不再被持有的时候引用计数减一,当引用计数为零的时候,说明这个对象已经无用了,则将其释放。
内存溢出和内存泄露的区别
内存溢出:out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如说你申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
内存泄露: memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
memory leak会最终会导致out of memory!
堆和栈的区别
对于栈来讲,是由编译器自动管理,无需我们手工控制;
对于堆来说,释放工作由程序员控制,容易产生内存泄露(memory leak)
循环引用的问题
系统自带的方法中的Block不会造成循环引用,比如UIView动画block、Masonry添加约束block、AFN网络请求回调block等;
但我们在实际开发中,使用自定义Block,在Block { xxx }中使用self,容易导致了循环引用 ,
循环引用导致的原因: 相互强指向;比如block为了保证代码块内部对象不被提前释放,会对block中的对象进行强引用,就相当于持有了其中的对象,而如果此时block中的对象又持有了该block,就会造成循环引用。
TCP和UDP概念和区别
TCP是传输控制协议,提供的是面向连接、可靠的字节流服务。当客户的服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
UDP是用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序穿给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用再客户的服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
单例模式
单例模式是一种常用的设计模式,对于一个单例类,必须保证任意时刻只有一个单例对象,并且自行实例化该对象,并向整个系统提供该对象,也就是说无论实例化单例对象多少次,都只能创建出一个对象,该对象是全局的能够整个系统所访问
单例对象很像c中全局变量,单例类可以实现不同对象之间的数据共享
//SingleClass.h文件
#import <Foundation/Foundation.h>
@interface SingleClass : NSObject
@property (copy, nonatomic)NSString *name;
+ (SingleClass *)sharedSingleClass;
@end
#import "SingleClass.h"
//1:创建一个全局静态的单例子对象指针,初始值为nil
static SingleClass *single = nil;
@implementation SingleClass
+ (SingleClass *)sharedSingleClass{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
single = [[SingleClass alloc] init];
});
return single;
}
@end
沙盒
1,Documents/
保存应用程序的重要数据文件和用户数据文件等。用户数据基本上都放在这个位置(例如从网上下载的图片或音乐文件),该文件夹在应用程序更新时会自动备份,在连接iTunes时也可以自动同步备份其中的数据。
2,Library:
这个目录下有两个子目录,可创建子文件夹。可以用来放置您希望被备份但不希望被用户看到的数据。该路径下的文件夹,除Caches以外,都会被iTunes备份.
Library/Caches:
保存应用程序使用时产生的支持文件和缓存文件(保存应用程序再次启动过程中需要的信息),还有日志文件最好也放在这个目录。iTunes 同步时不会备份该目录并且可能被其他工具清理掉其中的数据。
Library/Preferences:
保存应用程序的偏好设置文件。NSUserDefaults类创建的数据和plist文件都放在这里。会被iTunes备份。
3,tmp/:
保存应用运行时所需要的临时数据。不会被iTunes备份。iPhone重启时,会被清空。