小蓝书的最后一张学习的主要内容是OC的系统框架,对于OC而言Foundation框架是OC最基本最重要的框架了
OC的Foundation框架,像NSObject NSArray, NSDictionary等类都在其中。Foundation框架里的类都是用NS前缀,因为OC之前作为NeXTSTEP操作系统确定的。
将一系列代码封装为动态库,并在其中放入描述其接口的头文件,这样做出来的东西就叫框架。
Foundation框架提供了collection等基础核心功能,而且还提供了字符串处理等复杂功能。还存在一个CoreFoundation框架,在之前了解过他是不属于OC框架之内的,但是OC应用程序的编写离不开这个框架,Foundation框架的许多功在CoreFoundation框架都可以找到对应的C语言API
他其中的很多类都和Foundation框架相似,并且我们还可以通过“无缝桥接”功能实现CoreFoundation框架中的C语言数据结构平滑转换为Foundation框架中的OC对象,也可以反向转换。无缝桥接技术是用某些相当复杂的代码实现出来的,这些代码可以使运行期系统把CoreFoundation框架中的对象视为普通的OC对象。
NSString所对应的就是CFString对象。
OC编程的重要特点就是:经常需要使用底层的C语言级 API。用c语言实现 APTI的好处是,可以统过 Objeotive-C 的运行期系统,从而提升执行速度
在编写新的工具类之前可以在系统框架搜一下,通常有写好的类可以供直接使用
在OC里,列举collection中的元素可以使用C语言的for循环,还可以使用快速遍历。当学习了block块特性的时候,又提供了多种遍历collection的方式,可以传入块。
遍历数组的时候for循环最基本
- (void)forMethod {
NSArray *testArray = @[@"kd", @"lbj", @"Curry", @"KW", @"@PG"];
for (int i = 0; i < testArray.count; i++) {
// 操作数组
}
}
不过对于字典或者集合,字典和set都是无序的,所以要先把它转换为数组才可以正常使用for循环来使用
NSDictionary *textDic = @{@"LA": @"LBJ", @"PHX" :@"KD"};
NSArray *allKeys = [textDic allKeys];
for (int i = 0; i < allKeys.count; i++) {
// 操作字典或集合
}
for循环还有个比较好的地方就是反向遍历,在需要执行反向遍历的时候for循环往往更方便。
NSEnumerator是个抽象基类,其中只定义了两个方法,供其具体子类来实现:
关键的是其中的nextObject对象,它返回枚举里的下个对象,当返回不为nil的时候就会一直调用下一个对象,常用while语句
- (void)NSEnumerator {
// Array
NSArray *testArray = @[@"kd", @"lbj", @"Curry", @"KW", @"@PG"];
NSEnumerator *enumerator = [testArray objectEnumerator];
// 正向
id object;
while ((object = [enumerator nextObject]) != nil) {
// 操作数组
}
// 反向
NSEnumerator *reverseEnum = [testArray reverseObjectEnumerator];
id object2;
while ((object2 = [reverseEnum nextObject]) != nil) {
// 操作数组
}
// Dict And Set
NSDictionary *textDic = @{@"LA": @"LBJ", @"PHX" :@"KD"};
NSEnumerator *enumertorDic = [textDic keyEnumerator];
id key;
while ((key = [enumertorDic nextObject]) != nil) {
// 操作字典
id value = textDic[key];
}
}
快速遍历是OC2.0引入的语法功能,引入了in关键字,语法更加简洁了collection的遍历过程。尤其是字典类
- (void)fast {
NSArray *testArray = @[@"kd", @"lbj", @"Curry", @"KW", @"@PG"];
for (id object in testArray) {
//
}
NSDictionary *textDic = @{@"LA": @"LBJ", @"PHX" :@"KD"};
for (id key in textDic) {
NSLog(@"%@", textDic[key]);
}
}
对于块的引入,数组字典和集合都有自己的块遍历方法
- (void)block {
NSArray *testArray = @[@"kd", @"lbj", @"Curry", @"KW", @"@PG"];
__block NSInteger x = 2;
[testArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (x < 4) {
x++;
NSLog(@"%@", obj);
// 等效
NSLog(@"%@", testArray[idx]);
*stop = NO;
} else {
*stop = YES;
NSLog(@"x == 4 STOP");
}
}];
//
}
字典和集合是一样的思路。
遍历时可以直接从块里获取更多信息,并且它能够修改块的方法签名,以免进行类型转换操作。
块的遍历也有反向遍历数组字典集合,我们需要传入一个新的选项掩码
我知道反向遍历是通过NSEnumerationReserve来实现的,当然反向遍历只针对有序的数组和集合
“无缝桥接”技术其实就是不同库之间相同类型的相互转换。
使用“无缝桥接”技术,可以在定义于Foundation框架中的OC类和定义于CoreFoundation框架中的C数据结构之间互相转换
- (void)seamlessBridging {
NSArray *testArray = @[@"kd", @"lbj", @"Curry", @"KW", @"@PG"];
CFArrayRef aCFArray = (__bridge CFArrayRef)testArray;
NSLog(@"cfArratSize = %li", CFArrayGetCount(aCFArray));
}
转换操作中的__bridge
告诉ARC如何处理转换所涉及的OC对象。__bridge
本身的意思是:ARC仍然具备这个OC对象的所有权。而__bridge_retained
则与之相反,意味着ARC将交出对象的所有权。与之相似,反向转换可通过__bridge_transfer
来实现,也就是将对象的所有权交给ARC。这三种转换方式称为“桥式转换”
就目前的知识而言,对于从网上下载的图片如何缓存,我会把图片全部放到字典里,使用的时候就无需再次下载了,OC提供了一个NSCache类更好的方便缓存。
NSCache类的优势在于当系统资源即将耗尽的时候自动删减缓存,这是字典类不能做到的。并且遵循先删减最久未使用的对象
NSCache并非拷贝 键 而是保留 键。这和字典完全不同,并且NSCache是线程安全的,它在开发者不自己编写安全锁的情况下多个线程可以同时访问NSCache,这对于缓存来说是很重要的,多线程完成这个任务更加方便
NSCache可以令开发者操控缓存删减其内容的时机,可以调整缓存里的对象总数和对象的总开销,就是在讲对象加入缓存的时候开发者可以知道开销值,对象总数或总开销超过上限,缓存就会自动删减不需要的对象,当然这个删减不能确定会不会删减掉某个必要的对象,所以把对象转换成NSData对象之后把数据大小当作缓存值更合适这样避免了复杂的计算开销值,变成了读取数据大小的步骤
书上提供了实用NSCache的例子,并引入其他的知识。
下载数据所用的URL,就是缓存的键。若缓存未命中,即缓存中没有访问者所需的数据,则下载数据并将其放入缓存。而数据的“开销值”则设为其长度
存在一个类叫做NSPurgeableData
和NSCache
搭配起来用,它是NSMutableData的子类,而且实现了NSDiscardableContent协议。如果某个对象所占的内存能够根据需要随时丢弃,那么就可以实现该协议所定义的接口。这就是说,当系统资源紧张时,可以把保存NSPurgeableData对象的那块内存释放掉。NSDiscardableContent协议里定义了名为isContentDiscarded的方法,用来查询相关内存是否释放。
如果需要访问某个NSPurgeableData对象,可以调用其beginContentAccess
方法,告诉它现在还不应丢弃自己所占据的内存。用完之后,调用endContentAccess方法,告诉它在必要时可以丢弃自己所占据的内存了。这些调用可以嵌套,类似于对象的引用计数机制,为0就告诉系统可以销毁对象了
将NSPurgeableData对象加入NSCache,那么当该对象为系统所丢弃时,也会自动从缓存中清除。通过NSCache的evictsObjectsWithDiscardedContent属性,选择开启或者关闭此功能。
NSCache
对象设置上限,用以限制缓存中的对象总个数及“总成本”,而这些尺度则定义了缓存删减其中对象的时机。但是绝对不要把这些尺度当成可靠的“硬限制”,它们仅对NSCache起指导作用。NSPurgeableData与NSCache
搭配使用,可实现自动清除数据的功能,也就是说,当NSPurgeableData对象所内存为系统所丢弃时,该对象自身也会从缓存中移除。在OC里一个类必须初始化才能使用,大多数类继承与NSObject这个根类,提供了load
和initalize
方法
加入运行期系统中的每个类和分类来说,会调用此方且仅调用一次,当类和分类的程序载入系统的时候就会执行这个方法,调用顺序是类大于分类。
load方法执行的时候运行期系统处于脆弱状态,在执行子类的load方法之前必须执行所有超类的load方法,其中还会执行代码涉及到的库的load,导致在load方法里面使用其他类不安全
在B类里调用A类,无法保证A类已经加载完成,也就是只有A的load方法执行完成才能完整的使用A类。
某个类没实现load方法,那么他的超类不论实现该方法,都不会调用。
load尽量不用它。
该方法是在程序首次使用该类之前调用且仅有一次,是由运行期系统调用的,不通过代码调用。和load存在区别
initalize方法只应该用来设置内部数据,不能在内部调用其他的方法。
initalize还可以初始化某个无法在编译器初始化的全局变量
无法初始化数组等一些类,整数可以在编译期定义。
放到initalize里面即可在调用前完成该全局变量的初始化
+ (void)initialize {
if (self == [ViewController class]) {
staticArray = [NSArray new];
}
}
Foundation框架中有个类叫NSTimer,开发者可以指定绝对的日期与时间,以便到时执行任务,计时器要和“运行循环(run loop)”相关联,运行循环到时候会触发任务。创建NSTimer时,可以将其“预先安排”在当前的运行循环中,也可以先创建好,然后由开发者自己来调度。无论采用哪种方式,只有把计时器放在运行环里,它才能正常触发任务
_pollTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(pDoPoll) userInfo:nil repeats:YES];
用此方法创建出来的计时器,会在指定的间隔时间之后执行任务。也可以令其反复执行任务,知道开发者稍后将其手动关闭为止。
计时器会保留其目标对象,等到自身“失效”时再释放此对象。调用invalidate方法可令计时器失效;执行完相关任务之后,一次性的计时器也会失效。开发者若将计时器设置成重复执行模式,那么必须自己调用invalidate
方法,才能令其停止。
由于计时器会保留其目标对象,所以反复执行任务通常会导致应用程序出现保留环。
// NSTimer
- (void)startPolling {
_pollTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(pDoPoll) userInfo:nil repeats:YES];
[self stopPolling];
}
- (void)stopPolling {
[_pollTimer invalidate];
_pollTimer = nil;
}
- (void)pDoPoll {
NSLog(@"poll");
}
// 为啥不实现dealloc?
- (void)dealloc {
[_pollTimer invalidate];
}
这里为什么一直打印poll不是很理解 为什么没有调用dealloc方法?
创建计时器的时候,由于目标对象是self,所以要保留此实例。然而,因为计时器是用实例变量存放的,所以实例也保留了计时器,于是,就产生了保留环。所以说,调用stopPolling,或者令系统将此实例回收,只有这样才能打破保留环。
因为是类和这个类中的实例出现了保留环,不管你外界怎么对这个类释放,这个计时器始终都会保留这个类,而这个类也会保留这个计时器,互相引用保留导致他们的计数永远都不会降为0
如果从外界直接先调用stop方法,代码没办法自己检测。
使用块和weak关键字合理的打破保留环,块可以传递代码,这一功能可以利用
这段代码将计时器所应执行的任务封装成“块”,在调用计时器函数上,把它作为userInfo参数传进去。该参数可用来存放“不透明值”(即万能值),只要计时器还有效,就会一直保留着它。传入参数时要通过copy方法将block拷贝到“堆”上(之前在blk提过,copy方法把块变成了有引用计数的对象。)
否则等到稍后要执行它的时候,该块可能已经无效了。计时器现在的target是NSTimer类对象,这是个单例,因此计时器是否会保留它,其实都无所谓。此处依然有保留环,使用
方法和weak关键字打破它
[NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^
这段代码采用了一种很有效的写法,他先定义了一个弱引用,令其指向self,然后使块捕获这个引用,而不直接去捕获普通的self变量。也就是说,self不会为计时器所保留。当块开始执行时,立刻生成strong引用,以保证实例在执行期间持续存活。
小蓝书的最后一章刚开始看的不是很理解,有些代码还是需要自己手动打一下才知道原理,其中49.50 就是看着书写的,理解的不是很完全,GCD和block还是需要再次多加学习才能掌握。