怎么设置缓存数据的大小和缓存时间?java
AFN是怎么工做的,运行时的字典转模型怎么作?react
UITableViewCell怎么优化?android
UITableViewCell有个NSString *reuseIdentifier属性,能够在初始化UITableViewCell的时候传入一个特定的字符串标识来设置reuseIdentifier(通常用UITableViewCell的类名)。当UITableView要求dataSource返回UITableViewCell时,先 经过一个字符串标识到对象池中查找对应类型的UITableViewCell对象,若是有,就重用,若是没有,就传入这个字符串标识来初始化⼀一个UITableViewCell对象。ios
SVN和Git的比较程序员
1. Git是分布式的,SVN是集中式的,好处是跟其余同事不会有太多的冲突,本身写的代码放在本身电脑上,一段时间后再提交、合并,也能够不用联网在本地提交;web
2. Git下载下来后,在本地没必要联网就能够看到全部的log,很方便学习,SVN却须要联网;面试
3. Git鼓励分Branch,而SVN,说实话,我用Branch的次数还挺少的,SVN自带的Branch merge我还真没用过,有merge时用的是Beyond Compare工具合并后再Commit的;数据库
4. Tortoise也有出Git版本,真是好东西;json
5. SVN在Commit前,咱们都建议是先Update一下,跟本地的代码编译没问题,并确保开发的功能正常后再提交,这样其实挺麻烦的,有好几回同事没有先Updata,就 Commit了,发生了一些错误,耽误了你们时间,Git可能这种状况会少些。设计模式
还问了一个loadView的使用场景
loadView在每一次使用self.view这个property,而且self.view为nil的时候被调用,用以产生一个有效的 self.view。这个接口本来是为了让咱们自定义view用的。在不被subclass实现的状况下,也就是[super loadView]的效果,应该就是产生了一个有效的view,也就是一个空白的view。
在上面这种状况下,loadView被实现为空(只有一条打印语句),并且咱们没有经过XIB初始化ViewController,因此在 viewDidLoad被执行时,self.view是为nil的。因此在执行[self.view addSubView:customButton]时,loadView被调用,用来产生一个有效的view,使得self.view再也不为nil。罢 特,咱们错了(-_-!)。咱们的loadView什么也没有作,因而就出现了上面的情形,不断的调用一个什么都不作的loadView....
固然,咱们只要在loadView中增长一句[super loadView]就没有问题了。但这并非Cocoa的设计者所指望的。
loadView仅仅应该在开发者但愿自行经过编码而不是Interface Builder定制view的时候被实现,并且不该该在其中调用[super loadView],你的loadView中应该有self.view = ...这样的行为。
若是仅仅是想要在当前view上增长一些UIButton或是UILabel,应该在viewDidLoad里去作,此时不要实现本身的loadView。
14. 浅复制和深复制的区别?
答案:浅层复制:只复制指向对象的指针,而不复制引用对象自己。 深层复制:复制引用对象自己。 意思就是说我有个A对象,复制一份后获得A_copy对象后,对于浅复制来讲,A和A_copy指向的是同一个内存资源,复制的只不过是是一个指针,对象自己资源 仍是只有一份,那若是咱们对A_copy执行了修改操做,那么发现A引用的对象一样被修改,这其实违背了咱们复制拷贝的一个思想。深复制就好理解了,内存中存在了 两份独立对象自己。
json解析的用法,用框架的用法简单介绍:
底层原理遍历字符串中的字符,最终根据格式规定的特殊字符,好比{}号,[]号, : 号 等进行区分, {}号是一个字典的开始,[]号是一个数组的开始, : 号是字典的键和值的分水岭,最终乃是将json数据转化为字典,
字典中值多是字典,数组,或字符串而已。
18. 代理的做用?
答案:代理的目的是改变或传递控制链。容许一个类在某些特定时刻通知到其余类,而不须要获取到那些类的指针。能够减小框架复杂度。 另一点,代理能够理解为java中的回调监听机制的一种相似。
19. 什么是推送消息?
推送则是服务器端主动push。
38. 堆和栈的区别
管理方式:对于栈来说,是由编译器自动管理,无需咱们手工控制;对于堆来讲,释放工做由程序员控制,容易产生memory leak。
申请大小:
栈: 在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就肯定的常数),若是申请的空间超过栈的剩余空间时,将提示overflow。因 此,能从栈得到的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是因为系统是用链表来存储的空闲内存地址的,天然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。因而可知,堆得到的空间比较灵活,也比较大。
碎片问题:对于堆来说,频繁的new/delete势必会形成内存空间的不连续,从而形成大量的碎片,使程序效率下降。对于栈来说,则不会存在这个问题,由于栈是先进后出的队列,他们是如此的一一对应,以致于永远都不可能有一个内存块从栈中间弹出
分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,好比局部变量的分配。动态分配由alloca函数进行分配,可是栈的动态分配和堆是不一样的,他的动态分配是由编译器进行释放,无需咱们手工实现。
分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的。
1. SEL和IMP
最先我把SEL理解为函数指针,如今看来,不是这样的。
1.1 SEL类型
SEL类型的变量,能够经过@selector(方法名)来取得,固然了,Objective C的方法名,咱们也知道了,多么恶心(比Java废话还多)。
而它真正获得的,只要方法名同样,它的值就是同样的,无论这个方法定义于哪一个类,是否是实例方法【再说了,@selector的时候,除了方法名也没有什么类啊,对象啊什么事情】。
因此我如今把它理解为“方法名的某种映射结果”。(从C++程序员的眼光看,我以为没啥能够对应的,既不是函数指针,也不是函数指针类型,像是函数指针类型的名字吧。)
1.2 IMP类型
这个才是函数指针,IMP能够从 对象 & SEL的方法获得:
IMP imp = [self methodForSelector:selector];
这是IMP的定义:
typedef id (*IMP)(id, SEL, );
另外注意NSObject里面的这两个方法:
- (IMP)methodForSelector:(SEL)aSelector;
+ (IMP)instanceMethodForSelector:(SEL)aSelector;
1. - (void)makeObjectsPerformSelector:(SEL)aSelector;
2. - (void)makeObjectsPerformSelector:(SEL)aSelector withObject:(id)argument;
这是 NSArray和NSSet的两个方法,相信你们不多用,它相似于 for循环,但有效率高于for循环
makeObjectsPerformSelector:相似于NSNotifation机制,并发的执行同一件事,不能像for循环那样区别对待
因此参数 argument 必须是非基本类型 ,若是要是用基本类型 请转换程 NSNumber 或者NSValue。
用法:若是一个数组objArr中存储了一组有hide属性的对象,须要将数组里全部对象的hide所有赋值为真,就能够这么写:
1. [objArr makeObjectsPerformSelector:@selector(setHidden:) withObject:@YES];
不用再去for循环调用隐藏了
简历最好上午9点或者下午2点投
工做经验写在前面
技能写在后面
怎么用post get
三、GET请求,将参数直接写在访问路径上。操做简单,不过容易被外界看到,安全性不高,地址最多255字节;
四、POST请求,将参数放到body里面。POST请求操做相对复杂,须要将参数和地址分开,不过安全性高,参数放在body里面,不易被捕获。
Static
const就是只读的意思,只在声明中使用;
static通常有2个做用,规定做用域和存储方式.对于局部变量,static规定其为静态存储方式,每次调用的初始值为上一次调用的值,调用结束后存储空间不释放;
对于全局变量,若是以文件划分做用域的话,此变量只在当前文件可见;对于static函数也是在当前模块内函数可见.
static const 应该就是上面二者的合集.
下面分别说明:
全局:
const,只读的全局变量,其值不可修改.
static,规定此全局变量只在当前模块(文件)中可见.
static const,既是只读的,又是只在当前模块中可见的.
文件:
文件指针可看成一个变量来看,与上面所说相似.
函数:
const,返回只读变量的函数.
static,规定此函数只在当前模块可见.
类:
const,通常不修饰类,(在VC6.0中试了一下,修饰类没啥做用)
AFNetworking 和 ASI区别使用
1、底层实现
一、AFN的底层实现基于OC的NSURLConnection和NSURLSession
二、ASI的底层实现基于纯C语言的CFNetwork框架 三、由于NSURLConnection和NSURLSession是在CFNetwork之上的一层封装,所以ASI的运行性能高于AFN
2、对服务器返回的数据处理
一、ASI没有直接提供对服务器数据处理的方式,直接返回的是NSData/NSString
二、AFN提供了多种对服务器数据处理的方式 (1)JSON处理-直接返回NSDictionary或者NSArray (2)XML处理-返回的是xml类型数据,需对其进行解析 (3)其余类型数据处理
3、监听请求过程
一、AFN提供了success和failure两个block来监听请求的过程(只能监听成功和失败) * success : 请求成功后调用 * failure : 请求失败后调用 二、ASI提供了3套方案,每一套方案都能监听请求的完整过程 (监听请求开始、接收到响应头信息、接受到具体数据、接受完毕、请求失败) * 成为代理,遵照协议,实现协议中的代理方法 * 成为代理,不遵照协议,自定义代理方法 * 设置block
4、在文件下载和文件上传的使用难易度
一、AFN *不容易实现监听下载进度和上传进度 *不容易实现断点续传 *通常只用来下载不大的文件 二、ASI *很是容易实现下载和上传 *很是容易监听下载进度和上传进度 *很是容易实现断点续传 *下载大文件或小文件都可 三、实现下载上传推荐使用ASI
5、网络监控
一、AFN本身封装了网络监控类,易使用 二、ASI使用的是Reachability,由于使用CocoaPods下载ASI时,会同步下载Reachability,但Reachability做为网络监控使用较为复杂(相对于AFN的网络监控类来讲) 三、推荐使用AFN作网络监控-AFNetworkReachabilityManager
6、ASI提供的其余实用功能
一、控制信号旁边的圈圈要不要在请求过程当中转 二、能够轻松地设置请求之间的依赖:每个请求都是一个NSOperation对象 三、能够统一管理全部请求(还专门提供了一个叫作ASINetworkQueue来管理全部的请求对象) * 暂停/恢复/取消全部的请求 * 监听整个队列中全部请求的下载进度和上传进度
数据库原理
• NSUserDefaults,用于存储配置信息
• NSUserDefaults被设计用来存储设备和应用的配置信息,它经过一个工厂方法返回默认的、也是最经常使用到的实例对象
• NSUserDefaults把配置信息以字典的形式组织起来,支持字典的项包括:字符串或者是数组,除此以外还支持数字等基本格式。
• NSUserDefaults的全部数据都放在内存里,所以操做速度很快,并还提供一个归档方法:+ (void)synchronize。
•
• SQLite,用于存储查询需求较多的数据
• SQLite擅长处理的数据类型其实与NSUserDefaults差很少,也是基础类型的小数据,只是从组织形式上不一样。开发者能够以关系型数据库的方
• 式组织数据,使用SQL DML来管理数据。
• 通常来讲应用中的格式化的文本类数据能够存放在数据库中,尤为是相似聊天记录、Timeline等这些具备条件查询和排序需求的数据。
•
• CoreData,用于规划应用中的对象
• 官方给出的定义是,一个支持持久化的,对象图和生命周期的自动化管理方案。严格意义上说CoreData是一个管理方案,他的持久化能够经过
• SQLite、XML或二进制文件储存。如官方定义所说,CoreData的做用远远不止储存数据这么简单,它能够把整个应用中的对象建模并进行自动化的
• 管理。
•
• 使用基本对象类型定制的个性化缓存方案(plist)
Readwrite能使用场景?
assign:指定setter方法用简单的赋值,这是默认操做。你能够对标量类型(如int)使用这个属性。你能够想象一个float,它不是一个对象,因此它不能retain、copy。
assign:简单赋值,不更改索引计数(Reference Counting).使用assign: 对基础数据类型 (NSInteger)和C数据类型(int, float, double, char,等)
retain:指定retain应该在后面的对象上调用,前一个值发送一条release消息。你能够想象一个NSString实例,它是一个对象,并且你可能想要retain它。
retain:释放旧的对象,将旧对象的值赋予输入对象,再提升输入对象的索引计数为1 ,使用retain: 对其余NSObject和其子类 ,retain,是说明该属性在赋值的时候,先release以前的值,而后再赋新值给属性,引用再加1。
copy:指定应该使用对象的副本(深度复制),前一个值发送一条release消息。基本上像retain,可是没有增长引用计数,是分配一块新的内存来放置它。copy是建立一个新对象,retain是建立一个指针,引用对象计数加1。copy: 创建一个索引计数为1的对象,而后释放旧对象,copy是建立一个新对象,retain是建立一个指针,引用对象计数加1。
readonly:将只生成getter方法而不生成setter方法(getter方法没有get前缀)。
readwrite:默认属性,将生成不带额外参数的getter和setter方法(setter方法只有一个参数)。
atomic:对于对象的默认属性,就是setter/getter生成的方法是一个原子操做。若是有多个线程同时调用setter的话,不会出现某一个线程执行setter所有语句以前,另外一个线程开始执行setter的状况,相关于方法头尾加了锁同样。
nonatomic:不保证setter/getter的原子性,多线程状况下数据可能会有问题。nonatomic,非原子性访问,不加同步,多线程并发访问会提升性能。先释放原先变量,再将新变量 retain而后赋值;
Mvc使用
MVC 约定, Model
不容许与View 打交道。 Model 是管理数据的, 当Model中的数据发生变化时,与之对应的视图应更新。 这就须要一种机制来支持。为此
iOS 框架提供了两种支持机制: Notification 和KVO (Key-Value Observing)。
KVO 可简单理解为,为你所关注的 Key 对象注册一个监听器。 当有数据发生变化时,就会发出广播给全部的监听器。
MVC 也约定, View 不容许直接引用Modal, 它只能被Controller 所控制。 Controller 控制
View 显示什么数据。咱们知道,View 所要显示的数据是来源于 Modal, View 上产生的事件 ( 好比 Touch事件)须要通知
Controller。 既然MVC 不容许直接打交道,就须要提供一种机制。
不错, iOS 确实提供了一种机制, 名曰: Delegate。 Delegate 这个词,
有人将它译为“委托”,也有人将它译为“代理”。名称上的差别没有什么,重要的是如何理解 Delegate。
Delegate设计模式的引入,就是为了解决UIView与Controller松耦合互动问题。
UserDefault:轻量级的本地数据存数方式,用于保存,恢复应用程序相关的偏好设置,配置数据等
测试打包
1.登陆apple的开发者主页:developer.apple.com
2.选择Ad Hoc生成一个ios_distribution.cer: 让电脑具有打包程序的能力
3.利用用户设备的UDID注册设备
4.新建一个App ID : 方便打包哪一个程序
5.选择Ad Hoc利用ios_distribution.cer + 设备UDID + App ID --> 描述文件
(描述文件的做用:
1> 能知道在哪台电脑上, 为哪台设备打包哪一个程序
2> 哪台设备须要安装打包哪一个程序)
6.最终产生了3个文件
1> CertificateSigningRequest.certSigningRequest
* 包含了电脑的信息
* 发送给苹果服务器, 苹果服务器根据文件信息来生成一个电脑证书
* 生成的证书就可让对应的电脑具有某个特殊的能力
2> ios_distribution.cer
* 打包证书
* 安装这个证书后, 电脑就具有打包程序的能力
3> mj_iphone5_news.mobileprovision
* 里面包含了3个信息:ios_distribution.cer + 设备UDID + App ID
7.安装证书和描述文件
1> ios_distribution.cer
2> mj_iphone5_news.mobileprovision
8.项目Scheme右边的设备选择iOS Device
9.点击Xcode的菜单
Product --> Archive --> Distribute --> ....Ad Hoc... --> 选择对应的描述文件
10.生成一个ipa文件,发给测试人员和客户
* ipa本质是zip
* android的安装包是APK格式,本质也是zip
7. 5.6.7.8适配 区别
11 什么地图 集成
如何快速集成百度地图:
注册百度开发者账号=》建立应用=》下载SDK=》集成开发=》测试应用=》发布应用
一、注册百度开发者帐号
百度帐号注册地址:https://passport.baidu.com/v2/?reg®Type=1&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2F,若是你已经有百度帐号能够跳过这步。
登陆后,进入开发者中心,注册成为百度开发者,http://developer.baidu.com/user/info?u=http://lbsyun.baidu.com/apiconsole/key?from=developer,填写好我的信息,提交。
二、建立新应用
在使用百度地图SDK以前须要先获取百度地图移动版开发密钥。
百度地图iOS SDK开发密钥的申请地址为:http://lbsyun.baidu.com/apiconsole/key。
点击以上网址进入API控制台,选择建立应用,填写应用信息:
三、下载IOS SDK百度地图iOS SDK的下载地址:http://developer.baidu.com/map/sdkiosdev-download.htm
进入后能够下载所有功能,也能够根据本身须要选择模块选择下载:
1)新建一个工程
2)添加百度SDK和静态库
解压下载后的iOS SDK压缩包将压缩包内的inc文件夹和mapapi.bundle文件拷贝到工程目录下。
接下来根据模拟器和真机两种环境选择使用静态库文件:
若是用的真机,就导入iphoneos文件夹内的libbaidumapapi.a文件到工程,若是用的模拟器,就导入iphonesimulator文件夹内的libbaidumapapi.a文件到工程。
引入以下图所示的framework:
而后照着API一步步走呗
单元测试:
测试软件中局部某个业务的正确性
AFNetworking实际上使用了两个独立的缓存机制:
• AFImagecache:一个提供图片内存缓存的类,继承自NSCache。
• NSURLCache:NSURLConnection’s默认的URL缓存机制,用于存储NSURLResponse对象:一个默认缓存在内存,经过配置能够缓存到磁盘的类。
• AFImageCache是如何工做的
• AFImageCache是UIImageView+AFNetworking分类的一部分。它继承自NSCache,经过一个URL字符串做为它的key(从NSURLRequest中获取)来存储UIImage对象。
NSURLCache如何工做
默认是能够的,但最好仍是手动配置一下
既然AFNetworking使用NSURLConnection,它利用了原生的缓存机制NSURLCache。NSURLCache缓存了从服务器返回的NSURLResponse对象。
NSURLCache的shareCache方法默认是可使用的,缓存获取的内容。不幸的是,它的默认配置只是缓存在内存并无写到硬盘。为了解决这个问题,你能够声明一个 sharedCache,
怎么设置缓存数据的大小和缓存时间,
为了提升程序的响应速度,能够考虑使用缓存(内存缓存\硬盘缓存)
第一次请求数据时,内存缓存中没有数据,硬盘缓存中没有数据。
缓存数据的过程
当服务器返回数据时,须要作如下步骤
(1)使用服务器的数据(好比解析、显示)
(2)将服务器的数据缓存到硬盘(沙盒)
此时缓存的状况是:内存缓存中有数据,硬盘缓存中有数据。
再次请求数据分为两种状况:
(1)若是程序并无被关闭,一直在运行
那么此时内存缓存中有数据,硬盘缓存中有数据。若是此时再次请求数据,直接使用内存缓存中的数据便可
(2)若是程序从新启动
那么此时内存缓存已经消失,没有数据,硬盘缓存依旧存在,还有数据。若是此时再次请求数据,须要读取内存中缓存的数据。
提示:从硬盘缓存中读取数据后,内存缓存中又有数据了
3、缓存的实现
1.说明:
因为GET请求通常用来查询数据,POST请求通常是发大量数据给服务器处理(变更性比较大)
所以通常只对GET请求进行缓存,而不对POST请求进行缓存
在iOS中,可使用NSURLCache类缓存数据
iOS 5以前:只支持内存缓存。从iOS 5开始:同时支持内存缓存和硬盘缓存
2.NSURLCache
iOS中得缓存技术用到了NSURLCache类。
缓存原理:一个NSURLRequest对应一个NSCachedURLResponse
缓存技术:把缓存的数据都保存到数据库中。
3.NSURLCache的常见用法
(1)得到全局缓存对象(不必手动建立)NSURLCache *cache = [NSURLCache sharedURLCache];
(2)设置内存缓存的最大容量(字节为单位,默认为512KB)- (void)setMemoryCapacity:(NSUInteger)memoryCapacity;
(3)设置硬盘缓存的最大容量(字节为单位,默认为10M)- (void)setDiskCapacity:(NSUInteger)diskCapacity;
(4)硬盘缓存的位置:沙盒/Library/Caches
(5)取得某个请求的缓存- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request;
(6)清除某个请求的缓存- (void)removeCachedResponseForRequest:(NSURLRequest *)request;
(7)清除全部的缓存- (void)removeAllCachedResponses;
4.缓存GET请求
要想对某个GET请求进行数据缓存,很是简单
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 设置缓存策略
request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
只要设置了缓存策略,系统会自动利用NSURLCache进行数据缓存
5.iOS对NSURLRequest提供了7种缓存策略:(实际上能用的只有4种)
NSURLRequestUseProtocolCachePolicy // 默认的缓存策略(取决于协议)
NSURLRequestReloadIgnoringLocalCacheData // 忽略缓存,从新请求
NSURLRequestReloadIgnoringLocalAndRemoteCacheData // 未实现
NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData // 忽略缓存,从新请求
NSURLRequestReturnCacheDataElseLoad// 有缓存就用缓存,没有缓存就从新请求
NSURLRequestReturnCacheDataDontLoad// 有缓存就用缓存,没有缓存就不发请求,当作请求出错处理(用于离线模式)
NSURLRequestReloadRevalidatingCacheData // 未实现
6.缓存的注意事项
缓存的设置须要根据具体的状况考虑,若是请求某个URL的返回数据:
(1)常常更新:不能用缓存!好比股票、彩票数据
(2)一成不变:果断用缓存
(3)偶尔更新:能够按期更改缓存策略 或者 清除缓存
提示:若是大量使用缓存,会越积越大,建议按期清除缓存
/*--------------------------------------- 卡住主线程------------------------------------------*/
重点:1.线程进程区别(面试)! 2.串行执行!
{
1. 问题演示 :
为何在执行打印输出(执行耗时代码)的时候, UITextView 不能滚动? 按钮不能点击?
由于在同一条线程中,代码按顺序执行!因此在执行打印输出(执行耗时代码)的时候,卡住了主线程!
如何解决这个问题? NSThread类开启新线程!
// 开启一条新的线程: NSThread
// 1.建立一条线程;
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(longTimeOperation) object:nil];
// 2.启动线程; 调用start方法,告诉CPU线程准备就绪;线程被 CPU 调度以后会自动执行@selector()中的方法;
[thread start];
2. 线程
应用程序中的代码是由线程来执行的!
一个进程至少包含一条线程!
在一个应用程序启动以后,会默认开启一条线程 ----> 主线程!
主线程以外的线程---->子线程
问题:线程是如何执行应用程序中的代码的?
串行执行:
在线程中的代码是按顺序执行的!同一时间内,只能有一个代码块执行!
3. 进程
就是在系统中'正在运行'的应用程序!
进程为应用程序开辟独立的内存空间;
// 这块内存空间是独立的,受保护的!进程和进程之间是互不干扰的!
}
/*------------------------- 多线程实现原理 ------------------------*/
重点:并发执行!
{
1. 问题又来了! 为何开启一条新线程以后就能解决卡住主线程这个问题了呢?
答: 由于线程和线程之间是并发执行(同时执行)!
2.多线程
进程是由许多条线程组成的!
一个进程能够包含不少条线程,每条线程均可以执行不一样的代码!
并发执行(同时执行):
线程和线程之间是同时执行的! ----> 提升程序的运行效率!
为何多条线程之间能够并发(同时)执行呢?
线程是由 CPU 来执行的,同一时间只能有一条线程被执行!CPU在多条线程之间快速的切换!
因为 CPU 的执行速度很是快!就给咱们形成了多条线程并发执行的'假象' ------- 多线程实现原理!
既然多线程这么爽, 线程是否是越多越好呢?
<1> 开启线程须要消耗必定的内存(默认状况下,线程占用 512KB 的栈区空间);
<2> 会使应用程序增长不少代码!代码变多以后,程序复杂性就会提升!
<3> CPU 在多条线程之间来回切换!线程越多, CPU就越累!
建议: 在移动应用的开发中; 通常只开3~5条线程!
}
/*------------------- UI 线程-----------------*/
重点:用户体验(面试)!
{
在 iOS 开发中,多线程开发的知识点:// 只有知道了这些知识点,你们才能进行多线程的开发!
1. 主线程又称为 UI 线程! 主线程的做用: // 有关UI操做,建议都放在主线程中执行!
<1> 更新UI/ 刷新UI界面;
<2> 处理 UI 事件(点击/拖拽/滚动等)
2. 耗时操做会卡住主线程!,影响 UI 操做的流畅度!给用户一种'卡顿'的坏体验!
注意点:别将耗时操做放在主线程中执行!
}
/*----------------------------iOS中多线程实现方案 1.pthread----------------------*/
重要知识点:
在 C 语言中的 void * 就等同于 OC 中的 id;
Ref/ _t
{
添加 pthread.h
#import <pthread.h>
看一遍代码!有时间看,没时间就别看!
}
/*-------------------------------------- 桥接 (__bridge) ------------------------------------*/
重点:为何要使用桥接?你是怎么进行混合开发的?
{
桥接 (__bridge) :C 和 OC 之间传递数据的时候须要使用桥接! why?为何呢?
1.内存管理:
在 OC 中,若是是在 ARC环境下开发,编译器在编译的时候会根据代码结构,自动为 OC 代码添加 retain/release/autorelease等. ----->自动内存管理(ARC)的原理!
可是, ARC只负责 OC 部分的内存管理!不会负责 C 语言部分代码的内存管理!
也就是说!即便是在 ARC 的开发环境中!若是使用的 C 语言代码出现了 retain/copy/new/create等字样呢!咱们都须要手动为其添加 release 操做!不然会出现内存泄露!
在混合开发时(C 和 OC 代码混合),C 和 OC 之间传递数据须要使用 __bridge 桥接,目的就是为了告诉编译器如何管理内存
在 MRC中不须要使用桥接! 由于都须要手动进行内存管理!
2.数据类型转换:
Foundation 和 Core Foundation框架的数据类型能够互相转换的
Foundation : OC
Core Foundation : C语言
NSString *str = @"123"; // Foundation
CFStringRef str2 = (__bridge CFStringRef)str; // Core Foundation
NSString *str3 = (__bridge NSString *)str2;
CFArrayRef ---- NSArray
CFDictionaryRef ---- NSDictionary
CFNumberRef ---- NSNumber
Core Foundation中手动建立的数据类型,都须要手动释放
CGPathRef path = CGPathCreateMutable();
CGPathRetain(path);
CGPathRelease(path);
CGPathRelease(path);
3.桥接的添加:
利用 Xcode 提示自动添加! --简单/方便/快速
/**
凡是函数名中带有create\copy\new\retain等字眼, 都应该在不须要使用这个数据的时候进行release
GCD的数据类型在ARC环境下不须要再作release
CF(Core Foundation)的数据类型在ARC\MRC环境下都须要再作release
*/
}
/*----------- iOS中多线程实现方案2.NSThread - 1基本使用 -------------*/
重点:1.三种建立线程! 2.经常使用方法!
{
1.NSThread: 一个 NSThread 就表明一个线程对象!
// OC语言 / 使用面向对象 / 须要手动管理线程生命周期(建立/销毁等)
2.三种多线程实现方案:
1> 先建立,后启动
// 建立
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download:) object:nil];
// 启动
[thread start];
2> 建立完自动启动
[NSThread detachNewThreadSelector:@selector(download:) toTarget:self withObject:nil];
3> 隐式建立(自动启动)
[self performSelectorInBackground:@selector(download:) withObject:nil];
3.经常使用方法:
名字/得到主线程/得到当前线程/阻塞线程/退出线程
// 不经常使用: 栈区大小/优先级
1> 得到当前线程
+ (NSThread *)currentThread;
2> 得到主线程
+ (NSThread *)mainThread;
3> 睡眠(暂停)线程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
4> 设置线程的名字
- (void)setName:(NSString *)n;
- (NSString *)name;
}
/*------------- iOS中多线程实现方案2.NSThread - 2线程状态 --------------*/
重点:1. "Crash, P0级别 Bug(面试)!" 2.理解线程状态!
{
// 建立线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
// 启动线程
[thread start];
线程池:存放线程的池子! 分为:
可调度线程池: CPU 只会调度可调度线程池中的线程! 下面蓝色状态都位于可调度线程池中! '就绪' ,'运行'!
不可调度线程池: 下面红色状态都位于不可调度线程池中! "新建" ,"阻塞" ,"死亡"!
线程状态:
start CPU调度当前线程 运行结束/强制退出(exit)
"新建" ---------->'就绪' -----------------> '运行' -----------------------> "死亡";
CPU 调度其余线程 CPU调度当前线程
'运行' ------------------> '就绪'-----------------> '运行'
调用 sleep/等待互斥锁 sleep时间到/获得互斥锁
'运行' -----------------------> "阻塞"-----------------------> '就绪';
线程运行结束或者强制退出(exit)就进入 "死亡" 状态;
"注意:一旦线程中止(死亡),就不能够再次开启任务!程序会挂掉: Crash!
面试语句: 平时开发中,要特别关注 Crash! :"PO"级别的 "Bug";
}
/*----------- iOS中多线程实现方案2.NSThread - 3资源共享 ---------------*/
重点:1.线程同步技术! 2.理解资源共享
{
当多条线程访问同一块资源的时候,就会出现数据错乱和数据安全的问题!
1.ATM机取钱; 卖票;
2.解决方案:互斥锁 @synchronized(锁对象self){ /*须要锁住的代码,越少越好!*/ } ------- 厕所加锁!
注意:锁定一份代码只用一把锁,用多把锁是无效的!
优势:能有效防止因多线程抢夺资源而引发的数据安全问题!
缺点:须要消耗大量的CPU资源!
结论:尽可能少加锁!互斥锁的使用前提是多条线程抢夺同一块资源!
3.添加互斥锁技巧: [[NSUserDefaults standardUserDefaults] synchronize];
4.线程同步技术: ----- 互斥锁使用了线程同步技术!
多条线程在同一条线上按顺序执行任务!
5.线程安全:保证多条线程进行读写操做,都可以获得正确的结果!
用 '锁' 来实现线程安全!
}
/*----------------- 原子属性和非原子属性 -------------------*/
重点:1.面试问题: 为何要在主线程更新UI? 2.原子和非原子属性选择!
{
1.原子属性和非原子属性:
OC在定义属性时有 atomic 和 nonatomic 两种选择!
atomic(默认属性): 原子属性,自动为setter 方法加锁!线程安全的,须要消耗大量的 CPU 资源!
nonatomic: 非原子属性,不会为 setter 方法加锁!非线程安全的,适合内存小的移动设备!
咱们在声明属性的时候该如何选择?
面试问题: 为何要在主线程更新UI?
由于UIKit 框架都不是线程安全的!为了获得更好的用户体验,UIKit框架牺牲了线程安全;
因此咱们要在主线程更新UI;
2.iOS 开发建议:
<1> 全部属性都声明为 nonatomic!
<2> 尽可能避免多线程抢夺同一块资源!
<3> 尽可能将加锁,资源抢夺等业务逻辑交给服务器端处理,减少移动客户端的压力!
}
/*--------- iOS中多线程实现方案2.NSThread - 4线程间通讯 ---------------*/
1.下载图片? 更新 UI?
{
1.后台线程(子线程)下载图片;
[self performSelectorInBackground:@selector(downloadImage) withObject:nil];
2.主线程更新 UI.
线程间通讯经常使用方法:
// 最后一个参数:是否等待调用方法执行结束!
<1>[self performSelectorOnMainThread:@selector(setImageWithImage:) withObject:nil waitUntilDone:YES];
<2>[self performSelector:@selector(setImageWithImage:) onThread:[NSThread mainThread] withObject:nil waitUntilDone:YES];
}
/*------------------------------------------- 知识点补充 --------------------------------------*/
1.项目支持ARC
{
-fobjc-arc :就可让旧项目支持arc
-fno-objc-arc :让原来支持arc的不使用arc
}
/*----------------- GCD使用 1.队列和任务-------------------*/
重点:1."串行队列"? "并发队列"? 2.block?
{
1.GCD(Grand Central Dispatch) ---- '牛逼的中枢调度器'!
// C语言框架 / 自动管理线程的生命周期(建立/释放)
推出GCD的目的:取代NSThread!
为"多核"的"并行"运算提出的解决方案!
优势:
<1> GCD 可以自动利用更多的CPU的核数(双核/四核)!
<2> GCD 会自动管理线程的生命周期.
程序员只须要告诉 GCD 想要执行的任务(代码)!
2.GCD中的两个核心概念:
"任务":
想要作的事情/执行什么操做.
GCD 中的任务定义在block中.
void (^myBlock)() = ^{
// 想要作的事情/任务
}
"队列":
用来'存放'任务!
队列 != 线程!
队列中存放的任务最后都要由线程来执行!
队列的原则:先进先出,后进后出(FIFO/ First In First Out)!
队列的类型:
<1> '串行'队列:(Serial Dispatch Queue)
存放按顺序执行的任务!(一个任务执行完毕,再执行下一个任务)!
// 建立一个串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
<2> '并发'队列:(Concurrent Dispatch Queue)
存放想要同时(并发)执行的任务!
// 建立一个并发队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent",DISPATCH_QUEUE_CONCURRENT);
注意两个很是经常使用的特殊队列:
<1> 主队列: // UI 操做放在主队列中执行!
跟主线程相关联的队列!
主队列是 GCD 自带的一种特殊的串行队列!
主队列中的任务都会在主线程中执行!
//获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
<2> 全局并发队列: // 通常状况下,并发任务均可以放在全局并发队列中!
//获取全局并发队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
}
/*---------------- GCD使用 2.执行任务 ---------------------*/
重点:1."同步"函数!"异步"函数! 2.容易混淆的四个概念: '串行' ,'并发' ,"同步" ,"异步"之间的区别?
{
问题:串行队列中的任务一定按顺序执行吗?并发队列中的任务一定同时执行吗?
GCD中有两个用来执行任务的函数:
'同步'执行任务:
dispatch_sync(<#dispatch_queue_t queue#>, <#^(void)block#>)
'异步'执行任务:
dispatch_async(dispatch_queue_t queue, <#^(void)block#>)
// <#dispatch_queue_t queue#> :队列
// <#^(void)block#>:任务
"同步"和"异步"的区别:
"同步": 只能在'当前'线程中执行任务,不具有开启新线程的能力.
"异步": 能够在'新'的线程中执行任务,具有开启新线程的能力.
GCD 使用有两个步骤:
<1> 将任务添加到队列中;
<2> 选择同步仍是异步的方式执行任务.
注意:四个容易混淆的术语:
'串行' ,'并发' ,"同步" ,"异步".
}
/*----------------- GCD使用 3.各类队列的执行效果 ------------------*/
重点:1.掌握两个经常使用的组合!
{
常见的组合(掌握)
1> dispatch_async + 全局并发队列 (能够开启多条线程)
2> dispatch_async + 本身建立的串行队列 (开启一条线程)
只有'异步'执行"并发"队列,才能够开启多条线程.
注意:
在主线程中同步执行主队列中的任务,会形成'主线程'和'主队列'相互等待,卡住主线程!
}
/*----------------- GCD使用 4.线程间通讯 --------------------*/
重点:1.从子线程回到主线程(经典用法)! 2.两个注意点.
{
1.经典用法(子线程下载(耗时操做),主线程刷新UI):
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 执行耗时的异步操做...
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主线程,执行UI刷新操做
});
});
2.注意:
<1> 须要设置按钮的image,建议先把按钮类型改成custom,才能保证设置成功
<2> 属性名不能以new开头
}
/*--------------------- GCD使用 5.延时执行 --------------------------*/
重点:1.iOS常见的两种延时执行方式
{
iOS中的延时执行方式:
// 定制好延时任务后,不会阻塞当前线程.
<1> 调用 NSObject 方法:
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// 2秒后再调用self的run方法
<2> GCD 函数实现延时执行:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2秒后执行这里的代码... 在哪一个线程执行,跟队列类型有关
});
注意:
不要使用sleep,会阻塞当前线程.
}
/*------------------ GCD使用 6.队列组 ----------------*/
重点:1.了解队列组的使用方法.
{
项目需求:
首先:分别异步执行两个耗时操做;
其次:等两次耗时操做都执行完毕后,再回到主线程执行操做.
使用队列组(dispatch_group_t)快速,高效的实现上述需求.
dispatch_group_t group = dispatch_group_create(); // 队列组
dispatch_queue_t queue = dispatch_get_global_queue(0, 0); // 全局并发队列
dispatch_group_async(group, queue, ^{ // 异步执行操做1
// longTime1
});
dispatch_group_async(group, queue, ^{ // 异步执行操做2
// longTime2
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // 在主线程刷新数据
// reload Data
});
}
/*------------------------ GCD使用 7.一次性代码 ------------------------*/
重点:1.掌握一次性代码的实现.
{
一次性代码:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行一次的代码(这里面默认是线程安全的).
});
}
/*----------- 补充: 单例设计模式 ------------*/
重点:1.掌握单例!
{
1.单例简介:
做用:
保证程序在运行过程当中,一个类只有一个实例对象.这个实例对象容易被外界访问!
控制实例对象个数(只有一个),节约系统资源.
使用场合:
在整个应用程序中,共享一份资源(这份资源只须要建立初始化一次).
举例:
打印机/视图窗口/一些网络工具类等等
// 懒汉式: 用到的时候再加载.
// 饿汉式: 只要程序运行就加载. // 不须要掌握,也不要这么写!
// 掌握懒汉式.
2.单例实现:(两种方式:互斥锁(@synchronized(self))和一次性代码(dispatch_once));
2.1互斥锁 @synchronized(self):
<1>.在 .m 文件中保留一个全局的 static 的实例.
static id _instance;
<2>.重写若干方法(allocWithZone:和 copyWithZone:)并提供一个类方法让外界访问惟一的实例.
//(1)重写 allocWithZone:方法,在这里建立惟一的实例(注意线程安全). //alloc 内部都会调用这个方法.
+(instancetype)allocWithZone:(struct _NSZone *)zone {
if (_instance == nil) { // 防止频繁加锁
@synchronized(self) {
if (_instance == nil) { // 防止建立屡次
_instance = [super allocWithZone:zone];
}
}
}
return _instance;
}
//(2)重写 copyWithZone:方法.
+(id)copyWithZone:(struct _NSZone *)zone
{
return _instance;
}
//(3)提供1个类方法让外界访问惟一的实例
+(instancetype)shareSingleton
{
if (!_instance) { // 防止频繁加锁
@synchronized(self){
if (!_instance) { // 防止建立屡次
_instance = [[self alloc] init];
}
}
}
return _instance;
}
2.2 一次性代码(dispatch_once):
<1>.在 .m 文件中保留一个全局的 static 的实例.
static id _instance;
<2>.重写若干方法(allocWithZone:和 copyWithZone:)并提供一个类方法让外界访问惟一的实例.
//(1)重写 allocWithZone:方法,在这里建立惟一的实例(注意线程安全).
+ (id)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instace = [super allocWithZone:zone];
});
return _instace;
}
//(2)重写 copyWithZone:方法.
+(id)copyWithZone:(struct _NSZone *)zone
{
return _instance;
}
//(3)提供1个类方法让外界访问惟一的实例
+ (instancetype)shareSingleton
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instace = [[self alloc] init];
});
return _instace;
}
注意:在 ARC 和 MRC 中单例的实现方式略有不一样. MRC 下单例的实现比 ARC 多了几个内存管理的方法:
MRC 中增长以下方法的实现:
- (instancetype)retain { return self; }
- (NSUInteger)retainCount { return 1; }
- (oneway void)release {}
- (instancetype)autorelease { return self; }
3.判断当前环境(ARC/MRC)
#if __has_feature(objc_arc)
// ARC
#else
// MRC
#endif
4.注意两个方法:
// 面试问题:两个方法的区别?
<1> +(void)load;
// 当类加载到OC运行时环境(内存)中的时候,就会调用一次(一个类只会加载一次).
// 程序一启动就会调用.
// 程序运行过程当中,只会调用1次.
<2> +(void)initialize;
// 当第一次使用这个类的时候(好比调用了类的某个方法)才会调用.
// 并不是程序一启动就会调用.
}
/*----------- NSOperation使用 1.简介 ---------------*/
重点:理解操做 NSOperation 和操做队列 NSOperationQueue!
{
1.NSOperation(操做)简介:
NSOperation: // 本质是对 GCD 的封装, OC 语言.
NSOperation 和 GCD 的比较:
GCD使用场合:
一些简单的需求,简单的多线程操做. //简单高效
NSOperation使用场合:
各个操做之间有依赖关系,操做须要取消/暂停;须要限制同时执行的线程数量,让线程在某时刻中止/继续等.
配合使用 NSOperation和 NSOperationQueue 也能够实现多线程.
2.NSOperation使用:
NSOperation: 抽象类,不能直接使用,须要使用其子类.
抽象类:定义子类共有的属性和方法.// CAAnimation/CAPropertyAnimation...
两个经常使用子类: NSInvocationOperation(调用) 和 NSBlockOperation(块);
二者没有本质区别,后者使用 Block 的形式组织代码,使用相对方便.
自定义子类继承自 NSOperation,实现内部相应的方法. // 高级用法
}
/*----------------- NSOperation使用 2.NSOperation ----------------------*/
重点:1.NSBlockOperation, NSInvocationOperation的简单使用.
{
1. 建立 NSInvocationOperation 对象
// 建立 NSInvocationOperation
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(longTimeOperation:) object:@"op1"];
//默认状况下,调用 start 方法以后,不会开启新线程,只会在当前线程执行操做.
[op1 start];
注意:只有将 NSOperation 放到一个 NSOperationQueue 中,才会异步执行操做.
2. 建立 NSBlockOperation 对象
// 建立 NSBlockOperation
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下载图片1---%@",[NSThread currentThread]);
}];
// 添加更多操做
[op2 addExecutionBlock:^{
NSLog(@"下载图片2---%@",[NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
NSLog(@"下载图片3---%@",[NSThread currentThread]);
}];
// 只要 NSBlockOperation 中封装的操做数 > 1, 调用start方法以后就会开启多条线程并发执行
// 若是 NSBlockOperation 中封装的操做数 == 1,调用 start 方法以后,不会开启新线程,只会在当前线程执行操做
[op2 start];
注意: 只要 NSBlockOperation 中封装的操做数 > 1,就会异步执行这些操做.(将操做添加到 NSOperationQueue中或者直接调用 start方法都会开启多条线程异步执行).
}
/*----------------- NSOperation使用 3.NSOperationQueue ------------------*/
重点:将操做添加到队列中;
{
NSOperation 能够调用 start 方法来执行任务,但默认是同步执行的.
将 NSOperation 添加到 NSOperationQueue(操做队列) 中,系统会自动异步执行NSOperationQueue中的操做.
1.NSOperationQueue(操做队列):
<1> 主队列
[NSOperationQueue mainQueue] //获取主队列
添加到"主队列"中的操做,都会放在主线程执行!
<2>非主队列
[[NSOperationQueue alloc] init]; //建立非主队列
添加到"非主队列"中得操做,都会放在子线程中执行.
2.使用: 添加操做到操做队列中.
// 建立 NSInvocationOperation 操做
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(longTimeOperation:) object:@"op1"];
// 建立 NSBlockOperation 操做
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下载图片1---%@",[NSThread currentThread]);
}];
// 1.建立一个 NSOperationQueue
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.将操做添加到队列中.
[queue addOperation:op1];
[queue addOperation:op2];
注意:另一种添加操做到队列中的方法: block
[queue addOperationWithBlock:^{
NSLog(@"下载图片5---%@",[NSThread currentThread]);
}];
推荐使用: block // 简单. 本身哪一个使用熟练就用哪一个.
注意:队列中任务的执行是无序的.
问题:是否可让队列中的操做有序执行?
}
/*----------------- NSOperation使用 4.常见用法1 ----------------------*/
重点:1.设置操做依赖. 2.设置最大并发数.
{
回答上问题: 能,设置操做依赖.
1.NSOperation设置操做依赖: // 执行顺序: op1,op2,op3;
// 操做op3依赖于操做op2;
[op3 addDependency:op2];
// 操做op2依赖于操做op1;
[op2 addDependency:op1];
注意:不能相互依赖.
2.NSOperationQueue设置最大并发数.
并发数:同时开启的线程数.
// 建立操做队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 设置操做队列的最大并发数
queue.maxConcurrentOperationCount = 3;
[queue setMaxConcurrentOperationCount:3];
}
/*-------------------- NSOperation使用 5.常见用法2 -----------------------*/
重点:1.队列的取消/暂停/恢复 2.线程间通讯. 注意问题:为毛要取消恢复队列? 在何时用?
{
1.NSOperationQueue 的取消/暂停/恢复
// 取消操做 op1. 取消单个操做.
[op1 cancel];
// 取消全部操做,不会再次恢复
[queue cancelAllOperations];
// 暂停全部操做;注意,已经开始的操做不会暂停.
[queue setSuspended:YES];
// 从新开始全部操做
[queue setSuspended:NO];
问:为毛要取消恢复队列? 在何时用?
答:1.为了内存管理,处理内存警告; 2.为了用户体验,保证滚动流畅.
// 接收到内存警告的时候果断取消队列中的全部操做
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// [queue cancelAllOperations]; // 取消队列中的全部任务(不可恢复)
}
// 开始滚动的时候暂停队列中的任务.
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
// [queue setSuspended:YES]; // 暂停队列中的全部任务
}
// 滚动结束的时候恢复队列中的任务.
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
// [queue setSuspended:NO]; // 恢复队列中的全部任务
}
2.线程间通讯 // 子线程下载图片,主线程设置图片.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
// 1.异步下载图片
NSURL *url = [NSURL URLWithString:@"http://d.hiphotos.baidu.com/image/pic/item/37d3d539b6003af3290eaf5d362ac65c1038b652.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
// 2.回到主线程,显示图片
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = image;
}];
}];
}
/*------------- NSOperation使用 6.AppleWatch 展现 ----------------*/
{
// RectiveCocoa
http://www.devtang.com/blog/2014/02/11/reactivecocoa-introduction/
1.九宫格的绘制;
2.图片的异步下载;
3.主线程显示图片;
4.设置操做依赖(先显示第一张);
5.完善功能.
}
/*-------------- NSOperation综合案例: 1.项目简介/UI 搭建 -------------*/
重点: 1.搭建 UI 界面; 2.构建数据模型; 3.缓存开发中须要用到的数据模型; 4.注意在懒加载方法中,不要出现点语法.
{
1. 搭建 UI 界面;
// 导航控制器 + 表格视图控制器(根控制器)
2. 构建数据模型;
// 注意提供一个字典转模型的方法.
// KVC(Key - Value - Code)键值编码使用注意;
// 注意与 KVO(Key - Value - Observe)键值监听的区别; 存放
3. 将数据模型缓存到可变数组中.开发的数据直接来源于这个可变数组. "apps -------> 数据模型"
// 懒加载存放数据模型的数组.操做步骤: 存放
(1)将 apps.plist 文件转换为数组(数组中存放的是数据字典): array ---------> 字典模型
{
// <1> 得到 apps.plist 文件 的路径(知道了文件路径,就可以找到文件):
{
// 获取 apps.plist 文件名的全路径
NSString *path =[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];
}
// <2> 根据 apps.plist文件 转换为数组
{
// 根据 apps.plist文件 转换为数组; array 中存储的是数据字典
NSArray *array = [NSArray arrayWithContentsOfFile:path];
}
}
(2)取出数据字典,将数据字典转换为模型,而且将模型存放在apps数组中:
{
// 取出数据字典,将数据字典转换为模型
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// array: 存放字典模型;
// obj: 数组中存放的字典;
// idx: obj在数组中的位置
// <1>取出数组中的数据字典
NSDictionary *dict = obj;
// <2>将数据字典转换为数据模型
ITApp *app = [ITApp ITAppWithDictionary:dict];
// 将数据模型添加到临时可变数组中.
[appsArray addObject:app];
}];
// 将数据模型存放在 apps 中
_apps = appsArray;
}
// 注意,取出数组中的字典的两种方式:<1> for 循环; <2>Block.推荐使用第二种.效率更高.
#pragma 懒加载
// 存放数据模型的数组
-(NSMutableArray *)apps
{
if (!_apps) {
_apps = [NSMutableArray array];
// 获取 apps.plist 文件名的全路径
NSString *path =[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];
// 根据 apps.plist文件 转换为数组; array 中存储的是数据字典
NSArray *array = [NSArray arrayWithContentsOfFile:path];
// NSLog(@"%@",array);
// 定义一个临时的可变数组来存放数据模型;
NSMutableArray *appsArray = [NSMutableArray array];
// 拿出数据字典,将数据字典转换为模型,而且将模型存放在apps数组中:
//两种方法:
//<1> for 循环
// for (int i = 0 ; i < array.count; i ++) {
//
// // 取出 array 中存放的数据字典
// NSDictionary *dict = array[i];
//
// // 将数据字典转换为数据模型
// ITApp *app = [ITApp ITAppWithDictionary:dict];
//
// // 将数据模型添加到临时可变数组中.
// [appsArray addObject:app];
// };
//
// <2> Block
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// obj:数组中存放的字典; idx: obj在数组中的位置
NSDictionary *dict = obj;
// 将数据字典转换为数据模型
ITApp *app = [ITApp ITAppWithDictionary:dict];
// 将数据模型添加到临时可变数组中.
[appsArray addObject:app];
}];
// 将数据模型存放在 apps 中
_apps = appsArray;
}
return _apps;
}
4. 注意点: UITableView的数据源方法:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
必须返回一个 :UITableViewCell;
// 调试的时候,用下面这种方式写,不会报错.
return [UITableViewCell new];
}
}
/*-------------- NSOperation综合案例: 2.下载图片/技术选择 ----------------*/
重点: 1.分析项目需求; 2.处理内存警告,优化用户体验; 3.技术选择,技术点实施,代码编写. 4.Bug?
{
项目需求:
<1> 下载图片;
分析:
1>.子线程下载图片,主线程显示图片.
2>.开启子线程有三种技术方案可供选择: (1)NSThread ,(2)GCD ,(3)NSOperation 配合 NSOperationQueue使用.
<2> 内存警告处理;
分析:
接收到内存警告的时候,中止一切下载操做,防止闪退.
<3> 用户体验;
分析:
在与用户作 UI 交互的时候,最好暂停图片的下载;用户滚动结束以后,再继续下载图片.
1. 技术选择 : (3)NSOperation 配合 NSOperationQueue使用.
2. 技术点实施:
// <1>用户开始滚动的时候,暂停下载操做;中止滚动以后,恢复下载操做.
{
#pragma UIScrollViewDelegate
// 开始滚动的时候调用
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
NSLog(@"暂停下载---");
// 暂停全部下载操做
[self.queue setSuspended:YES];
}
// 滚动结束的时候调用
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
NSLog(@"恢复下载---");
// 恢复全部下载操做
[self.queue setSuspended:NO];
}
}
// <2>接收到内存警告的时候,取消一切操做.
{
// 接收到内存警告的时候调用
-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// 取消一切下载操做
[self.queue cancelAllOperations];
}
}
// <3>将下载图片的操做封装在 NSBlockOperation.最后将操做放在并发队列中.自动执行!
{
__weak typeof(self) wself = self;
// 定义下载操做
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// 下载网络图片
UIImage *webImage = [wself downloadWebImage:app.icon];
//回到主线程显示图片
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// 显示图片
cell.imageView.image = webImage;
}];
}];
// 将下载操做放在并发队列中.自动开启下载.
[self.queue addOperation:op];
}
3.运行程序以后,发现 3 个Bug:
<1> 程序运行以后,图片不会直接显示出来.须要刷新以后才能显示(滚动/点击都会重绘 UI).
<2> 图片错位问题.
<3> 用户体验方面的Bug:只要滚动,图片就会重复下载.即便已经下载好的图片,也会从新下载.(耗费流量,体验巨差,巨耗电).
}
/*-------------- NSOperation综合案例: 3.Bug 解决 -----------------*/
重点: 1.分析Bug产生的缘由并解决Bug. 2.知识点回顾:如何防止一个url对应的图片重复下载?
{
Bug 产生的缘由分析:
<1> "程序运行以后,图片不会直接显示出来.须要刷新以后才能显示(滚动/点击都会重绘 UI)."
Bug产生缘由: UITableViewCell 中刚开始没有设置显示图片的 Frame,也就是说没有 Frame.图片下载完后,点击/刷新以后,就会从新绘制 UITableViewCell,这样就会显示图片了.
解决 Bug :下载以前最好先把图片的 Frame 绘制出来.好比,能够添加一张占位图片.
{
// 设置占位图片.
cell.imageView.image = [UIImage imageNamed:@"placeholder"];
}
<2> "图片错位问题."
Bug产生缘由: UITableViewCell 的重用以及网络下载延时产生的.
解决 Bug :让数据控制视图(视图根据数据来显示),设置一个图片缓存,cell 中的图片来源于这个图片缓存.
{
<1>.设置图片缓存.
定义一个字典作为图片缓存,保存下载好的图片(以图片的 url 做为 key 值;如下载好的图片为 Value).
// 能够选择 NSCache 代替字典做为缓存机制.
// NSCache在内存紧张的时候,会自动释放一些资源(自动销毁图片,咱们没法控制).
// 若是使用字典,在接收到内存警告以后,须要手动释放资源.
<2>.从缓存中取出 cell 对应的图片.
cell 设置图片:根据 cell 的 app.icon(url)从字典中取出对应的图片.
<3>.图片下载完毕以后刷新所在行的数据.
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
注意: 不必刷新整个表格视图,刷新所在行的数据就能够了.
}
<3> "用户体验方面的Bug:只要滚动,图片就会重复下载.即便已经下载好的图片,也会从新下载.(耗费流量,体验巨差,巨耗电)."
Bug产生缘由: 只要上下滚动,就会调用 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 这个方法.这个方法频繁调用致使下载操做一直建立.
解决 Bug :每次建立下载操做以前,最好先检查一下载操做是否存在,对于已经存在的操做,不要重复下载.
{
<1>.设置操做缓存.
定义一个字典做为下载操做缓存,保存已经建立好的下载操做(一样,以图片的 url 为 key值;以操做Operation为 Value).
<2>.从缓存中取出操做比较.
每次从新建立下载操做以前,首先从下载操做缓存之中查看操做是否已经存在.若是存在,就不要再次建立;若是不存在,建立新的下载操做.
<3>.防止操做缓存愈来愈大
图片下载成功以后,就下载操做就没有存在的意义了,应该及时清除缓存,防止下载操做缓存愈来愈大.
}
补充知识点:NSCache
{
// 能够选择 NSCache 代替字典做为缓存机制.
NSCache类结合了各类自动删除策略,以确保不会占用过多的系统内存.若是其它应用须要内存时,系统自动执行这些策略.
NSCache是线程安全的,咱们能够在不一样的线程中添加/删除和查询缓存中的对象,而不须要锁定缓存区域.
}
2. 问题:如何防止一个url对应的图片重复下载?
答:cell下载图片思路 - 无沙盒缓存;
}
/*------------- NSOperation综合案例: 4.完善项目-添加沙盒缓存 ----------------*/
重点: 1.处理内存警告. 2.添加沙盒缓存.
{
1.因为添加了内存缓存机制(图片缓存和操做缓存),在接收到内存警告的时候,最好释放内存缓存.
将图片缓存在沙盒中,能够优化用户体验.之后每次展现图片,对于已经下载过的图片,就不须要从新下载.
2.添加沙盒缓存.
<1> 认识沙盒:
默认状况下,每一个沙盒含有3个文件夹:Documents, Library 和 tmp.
Documents:苹果建议将程序中创建的或在程序中浏览到的文件数据保存在该目录下,iTunes备份和恢复的时候会包括此目录.
Library:存储程序的默认设置或其它状态信息;
// Library/Caches:存放缓存文件,iTunes不会备份此目录,此目录下文件不会在应用退出删除
tmp:提供一个即时建立临时文件的地方.
<2> 将图片写入沙盒:将文件存储在 Library/Caches路径下
1>.获取 ~/Caches路径.
{
// 获得 Caches 路径
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject;
}
2>.拼接文件路径.
{
// 拼接文件路径( path + app.icon.lastPathComponent ),以url 中图片的名做为名字.
NSString *file = [path stringByAppendingPathComponent:app.icon.lastPathComponent];
}
3>.将 image 转换为 二进制数据. //沙盒中不能直接存取图片数据.
{
// 将 UIImage 图片转换为二进制数据
NSData *data = UIImagePNGRepresentation(webImage);
}
4>.将图片的二进制数据存入沙盒.
{
// 将图片的二进制数据存入沙盒,路径为:file
[data writeToFile:file atomically:YES];
}
<3> 从沙盒中获取图片:
1>.获取 ~/Caches路径.
2>.拼接完整的图片文件路径.
3>.根据完整的图片文件路径获取图片.
{
UIImage *image = [UIImage imageWithContentsOfFile:fileName];
}
}
/*------------- NSOperation综合案例: 5.SDWebImage使用 ------------------*/
重点: 1.了解 SDWebImage.
{
1.SDWebImage:
SDWebImage是一个开源的第三方库,它提供了UIImageView的一个分类,以支持从远程服务器下载并缓存图片的功能.
<1> SDWebImageManager
在实际的运用中,咱们并不直接使用SDWebImageDownloader类及SDImageCache类来执行图片的下载及缓存.
为了方便用户的使用,SDWebImage提供了SDWebImageManager对象来管理图片的下载与缓存.
咱们常常用到的诸如UIImageView+WebCache等控件的分类都是基于SDWebImageManager对象的.
该对象将一个下载器和一个图片缓存绑定在一块儿,并对外提供两个只读属性来获取它们.
<2> UIImageView+WebCache
咱们在使用SDWebImage的时候,使用得最多的是UIImageView+WebCache中的针对UIImageView的扩展方法,这些扩展方法将UIImageView与WebCache集成在一块儿,来让UIImageView对象拥有异步下载和缓存远程图片的能力.
其中最核心的方法是 -sd_setImageWithURL:placeholderImage:options:progress:completed:,其使用SDWebImageManager单例对象下载并缓存图片,完成后将图片赋值给UIImageView对象的image属性,以使图片显示出来.
2.面试题
1> SDWebImage的默认缓存是多长时间?
* 1个星期
2> SDWebImage的默认最大并发数是多少?
* 6
3> SDWebImage底层是怎么实现的?
* cell下载图片思路 – 有沙盒缓存
3.SDWebImage经常使用方法:
1> 经常使用方法
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;
2> SDWebImageOptions
* SDWebImageRetryFailed : 下载失败后,会自动从新下载
* SDWebImageLowPriority : 当正在进行UI交互时,自动暂停内部的一些下载操做
* SDWebImageRetryFailed | SDWebImageLowPriority : 拥有上面2个功能
3> 内存处理:当app接收到内存警告时
/**
* 当app接收到内存警告
*/
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
{
SDWebImageManager *mgr = [SDWebImageManager sharedManager];
// 1.取消正在下载的操做
[mgr cancelAll];
// 2.清除内存缓存
[mgr.imageCache clearMemory];
}
}
/*-------------- NSOperation综合案例: 6.自定义 NSOperation --------------*/
{
这一块知识点比较复杂,难以理解.只须要知道如下几点:
自定义 NSOperation的步骤:
* 重写 -(void)main 方法,在里面实现想执行的操做.
重写 -(void)main 方法注意点:
1> 本身建立自动释放池(若是异步操做,没法访问主线程的自动释放池).
2> 常常经过 -(BOOL)isCancelled 方法检测操做是否取消,对取消作出响应.
// 若是真的想搞这块,还须要掌握'不一样对象间'通讯的的方法:(通知/代理/Block 等).
// 详细见代码
}