李洪强经典面试题21

怎么设置缓存数据的大小和缓存时间?

AFN是怎么工作的,运行时的字典转模型怎么做?

UITableViewCell怎么优化?

UITableViewCell有个NSString *reuseIdentifier属性,可以在初始化UITableViewCell的时候传入一个特定的字符串标识来设置reuseIdentifier(一般用UITableViewCell的类名)。当UITableView要求dataSource返回UITableViewCell时,先 通过一个字符串标识到对象池中查找对应类型的UITableViewCell对象,如果有,就重用,如果没有,就传入这个字符串标识来初始化⼀一个UITableViewCell对象。

 

SVN和Git的比较

1. Git是分布式的,SVN是集中式的,好处是跟其他同事不会有太多的冲突,自己写的代码放在自己电脑上,一段时间后再提交、合并,也可以不用联网在本地提交;

2. Git下载下来后,在本地不必联网就可以看到所有的log,很方便学习,SVN却需要联网;

3. Git鼓励分Branch,而SVN,说实话,我用Branch的次数还挺少的,SVN自带的Branch merge我还真没用过,有merge时用的是Beyond Compare工具合并后再Commit的;

4. Tortoise也有出Git版本,真是好东西;

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

3、GET请求,将参数直接写在访问路径上。操作简单,不过容易被外界看到,安全性不高,地址最多255字节;

4、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 

  2、ASI的底层实现基于纯C语言的CFNetwork框架 
  3、因为NSURLConnection和NSURLSession是在CFNetwork之上的一层封装,因此ASI的运行性能高于AFN

二、对服务器返回的数据处理

1、ASI没有直接提供对服务器数据处理的方式,直接返回的是NSData/NSString 

2、AFN提供了多种对服务器数据处理的方式 
   (1)JSON处理-直接返回NSDictionary或者NSArray 
   (2)XML处理-返回的是xml类型数据,需对其进行解析 
   (3)其他类型数据处理

三、监听请求过程 

1、AFN提供了success和failure两个block来监听请求的过程(只能监听成功和失败) 
  * success : 请求成功后调用 
  * failure : 请求失败后调用 
  2、ASI提供了3套方案,每一套方案都能监听请求的完整过程 
  (监听请求开始、接收到响应头信息、接受到具体数据、接受完毕、请求失败) 
  * 成为代理,遵守协议,实现协议中的代理方法 
  * 成为代理,不遵守协议,自定义代理方法 
  * 设置block

四、在文件下载和文件上传的使用难易度  

1、AFN 
  *不容易实现监听下载进度和上传进度 
  *不容易实现断点续传 
  *一般只用来下载不大的文件 
  2、ASI 
  *非常容易实现下载和上传 
  *非常容易监听下载进度和上传进度 
  *非常容易实现断点续传 
  *下载大文件或小文件均可 
  3、实现下载上传推荐使用ASI

五、网络监控 

1、AFN自己封装了网络监控类,易使用 
  2、ASI使用的是Reachability,因为使用CocoaPods下载ASI时,会同步下载Reachability,但Reachability作为网络监控使用较为复杂(相对于AFN的网络监控类来说) 
  3、推荐使用AFN做网络监控-AFNetworkReachabilityManager

六、ASI提供的其他实用功能 

1、控制信号旁边的圈圈要不要在请求过程中转 
  2、可以轻松地设置请求之间的依赖:每一个请求都是一个NSOperation对象 
  3、可以统一管理所有请求(还专门提供了一个叫做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=》集成开发=》测试应用=》发布应用

1、注册百度开发者账号

百度账号注册地址:https://passport.baidu.com/v2/?reg&regType=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,填写好个人信息,提交。

2、创建新应用

在使用百度地图SDK之前需要先获取百度地图移动版开发密钥。

百度地图iOS SDK开发密钥的申请地址为:http://lbsyun.baidu.com/apiconsole/key。

点击以上网址进入API控制台,选择创建应用,填写应用信息:

3、下载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)如果程序重新启动

  那么此时内存缓存已经消失,没有数据,硬盘缓存依旧存在,还有数据。如果此时再次请求数据,需要读取内存中缓存的数据。

提示:从硬盘缓存中读取数据后,内存缓存中又有数据了

 

三、缓存的实现

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 等).

    // 详细见代码

 

}

 

posted @ 2016-07-06 09:19  李洪强  阅读(1448)  评论(0编辑  收藏  举报