iOS DeveloperiOS开发effect object c

阅读Effective Object-C 2.0 笔记(七)

2016-04-28  本文已影响189人  iLeooooo

还是要好好学习英文啊,笔者只能看中文版的,下载地址如下:
http://download.csdn.net/detail/m6830098/7977521
看书的时候还是困的不行不行的-

今天来学习学习本书的第七章。

第一条:熟悉系统框架。将一系列的代码封装为动态库(dynamic library),并在其中放入描述其接口的头文件,这样做出来的东西就叫做框架。有时候为iOS平台构建的第三方框架所使用的是静态库(static library),这是因为iOS应用程序不允许在其中包含动态库。这些动态库严格来讲并不是真正的框架,然而也经常视为框架。所有的iOS平台的系统框架任然使用动态库。开发者会碰到的主要框架就是Foundation,像NSObject、NSArray、NSDictionary等类都在其中。Foundation框架中的类,使用NS这个前缀,次前缀是在Object-C语言用作NeXTSTEP操作系统的编程语言时首次确定的。Foundation框架是所有Object-C应用程序的基础。还有个与Foundation相伴的框架,叫做CoreFoundation。虽然从技术上讲,CoreFoundation框架不是Object-C框架,但他却是编写Object-C应用程序时所应熟悉的重要框架。有个功能叫"无缝桥接"(tollfree bridging),可以把CoreFoundation中的C语言数据结构平滑转换为Foundation中的Object-C对象,也可以反向转换。如:NSString <--> CFString 。 无缝桥接技术是用某些相当复杂的代码实现出来的,这些代码可以使运行期系统把CoreFoundation框架中的对象视为普通的Object-C对象。除了Foundation和CoreFoundation框架之外,还有很多系统库,如下:

CFNetwork 此框架提供了C语言级别的网络通信能力,它将"BSD套接字"(BSD socket)抽象成易于使用的网络接口。而Foundation则将该框架里的部分内容封装为Object-C语言的接口,以便进行网络通信。

CoreAudio 该框架所提供的C语言API可用来操作设备上的音频硬件。这个框架属于比较难的那种,因为音频处理本身就很复杂。所幸由这套API可以抽象出另外一套Object-C式的API,使得音频处理变得会跟简单些。

AVFoundation 此框架所提供的Object-C对象可以用来回放并录制音频及视频,比如能够在UI视图类里播放视频。

CoreData 此框架所提供的Object-C接口可以将对象放入数据库,便于持久保存。CoreData会处理数据的获取及存储事件,而且可以跨越Mac OS X及iOS平台。

CoreText 此框架提供的C语言接口可以高效执行文字排版及渲染操作。

CoreAnimation是用Object-C语音完成的,它提供了一些工具,而UI框架则用这些工具来渲染图像并播放动画。CoreAnimation本身不是框架,它是QuartzCore框架的一部分。

第二条:多用块枚举,少用for循环。for循环可以实现反向遍历,计数器的值从"元素个数减一"开始,每次迭代递减,直到0为至。执行反向遍历时,使用for循环会比其他方式简单许多。

使用NSEnumerator来遍历。NSEnumerator是个抽象基类,其中只定义了两个方法,供其具体子类(concrete subclass)来实现:

- (NSArray *)allObjects;
- (id)nextObject;

其中关键的方法是nextObject,它返回枚举里的下一个对象。每次调用该方法时,其内部数据结构都会更新,是的下次调用方法是能返回下一个对象。等到枚举中的全部对象都返回之后,在调用就将返回nil,这表示达到枚举末端了。Foundation框架中内建的collection类都是些了这种遍历方式。遍历数组代码如下:

NSArray *anArray = /*  ... */;
NSEnumerator *enumerator = [anArray objectEnumerator];
id object;
while ((object = [enumerator nextObject]) != nil) {
    // do something with 'object'
}

这种写法的功能与标准的for循环相似,但是代码变多了。用该方法的优势:不论遍历哪种collection,都可以采用这套相似的语法。遍历字典及set时也可以按照这个来写:

NSDictionary *aDictionary = /*  ... */;
NSEnumerator *enumerator = [aDictionary objectEnumerator];
id key;
while ((key = [enumerator nextObject]) != nil) {
    id value = aDictionary[key];
    // do something with 'object'
}

//Set
NSSet *aSet = /* ... */;
NSEnumerator *enumerator = [aSet objectEnumerator];
id object;
while ((object = [enumerator nextObject]) != nil) {
    // do something with 'object'
}

遍历字典的方式与数组和set有一些不同,因为字典里是键值对,所以要根据键把对应的值取出来。NSEnumerator还有个好处,就是有多种"枚举器"(enumerator)可供使用。比如:反向遍历数组的枚举器

NSArray anArray = / ... */;
NSEnumerator *enumerator = [anArray reverseobjectEnumerator];
id object;
while ((object = [enumerator nextObject]) != nil) {
// do something with 'object'
}

快速遍历:for in 语句。

NSArray *anArray = /*  ... */;
for (id object in anArray) {
    // do something with 'object'
}

如果某个类的对象支持快速遍历,那么该类就遵从了NSFastEnumeration的协议,从而令开发者可以采用此语法来迭代该对象。此协议只定义了一个方法:

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state  objects:(id *)stackbuffer count:(NSUInteger)length;

该方法允许类实例同时返回对个对象。

//Dictionary
NSDictionary *aDictionary = /*  ... */;
for (id key in aDictionary) {
    id value = aDictionary[key];
    // do something with 'key'  and  'value'
}

//Set
NSSet *aSet = /*  ... */;
for (id object in aSet) {
    // do something with 'object'
}

由于NSEnumeration对象也实现了NSFastEnumeration协议,所以能用来执行反向遍历。如下:
NSArray anArray = / ... */;
for (id object in [anArray reverseObjectEnumerator]) {
// do something with 'object'
}
缺点:这种遍历方式无法很好的获取当前遍历操作对象的下标。

基于块的遍历方式:NSArray中定义了下面的这个方法,它可以实现最基本的遍历功能:

- (void)enumerateObjectsUsingBlock:(void (^)(id object, NSUInteger idx, BOOL *stop))block;

该方法有三个参数,分别指当前迭代所针对的对象,该对象的下标,以及指向布尔值的指针。通过第三个参数,开发者可以终止遍历操作。

 NSArray *anArray = /*  ... */;  
[arr enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    //Do something with 'object';
    if (shouldStop) {
        *stop = YES;
    }
}];

NSSet和NSDictionary里面都同样有块的枚举方法,只不过有部分不同。在此不再多说。
此方式比其他方式的好处:遍历是可以直接从块里获取更多信息。在遍历数组时,可以知道当前所针对的下标。还有一个好处就是,能够修改块的方法签名,改变参数类型,以免进行类型转换操作。
用此方法也可以执行方向遍历。数组、字典、set都实现了前述方法的另一个版本,可以向其传人"选项(option mask)":

- (void)enumerateObjectsWithOptions:(NSEnumerationOptions)options usingBlock:(void (^)(id object, NSUInteger idx, BOOL *stop))block;

NSEnumerationOptions类型是个enum,其各种取值可用"按位或"(bitwise OR)连接。可以请求并发方式执行各轮迭代,也就是如果当前系统资源状况运行,那么每次迭代所用的块就可以并行执行。通过NSEnumerationConcurrent可以开启次功能。反向遍历是通过NSEnumerationReverse来实现的。

第三条:对自定义其内存管理语义的collection使用无缝桥接。CoreFoundation框架定义了一套C语言的API,用于操作Foundation以及其他各种collect类所对应的Object-C类的数据结构。使用"无缝桥接"技术,可以在定义于Foundation框架中的Object-C类和定义于CoreFoundation框架中的C数据结构直接进行相互转换。CoreFoundation框架中的数据与Object-C中的类或者对象不同。CFArray要通过CFArrayRef来引用,而这是指向stuct_ _CFArray的指针。CFArrayGetCount这种函数则可以操作此struct,用来获取数组的大小。用代码演示简单的无缝桥接:

NSArray *anNSArray = @[@1, @2, @3, @4, @5];
CFArrayRef aCFArray = (__bridge CFArrayRef)anNSArray;
NSLog(@"Size of array = %li", CFArrayGetCount(aCFArray));
//Output: Size of array = 5

转换操作中的_ bridge告诉ARC如何处理转换所涉及的Objective-C对象。 bridge本身的意思是:ARC任然具备这个Objective-C对象的所有权。而 bridge_retained则与之相反,意味着ARC将交出对象的所有权。若是前面的那段代码改用它来实现,那么用完数组之后就要加上CFRelease(aCFArray)以释放其内存。与之相似,反向转换可以通过 _bridge_transfer来实现。想把CFArrayRef转换为NSArray *,并且想令ARC获得该对象的所有权,就可以采用此种转换,这三种转换方式称为"桥式转换"(bridgedcast)。Foundation框架中的Objective-C类所具备的某些功能,是CoreFoundation框架中的C语言数据结构所不具备,反之也是这样。在使用Foundation框架中的字典的时候会遇到一个问题,那就是其键的内存管理语义为"拷贝",而值的语义却是"保留",除非使用强大的无缝桥接技术否则无法改变其语义。

CoreFoundation框架中的字典类型叫做CFDictionary。可变版本为CFMutableDictionary。创建CFMutableDictionary时,可以通过下列方法来指定键和值的内存管理语义:

CFMutableDictionaryRef CFDictionaryCreateMutable(
    CFAllocatorRef allocator,
    CFIndex capacity,
    const CFDictionaryKeyCallBacks *keyCallBacks,
    const CFDictionaryValueCallBacks *valueCallBacks
)

首个参数表示将要使用的内存分配器(allocator)。CoreFoundation对象里的数据结构需要占用内存,而分配器负责分配及回收这些内存。这个参数通常传入NULL,表示采用默认的分配器。
第二个参数定义了字典的初始大小。它并不会限制字典的最大容量,只是向分配器提示了一个开始应该分配多少内存。
最后两个参数,它们定义了许多回调函数,用于指示字典中的键和值在遇到各种事件是应该执行何种操作。这两个参数都是指向结构体的指针,二者所对应的结构体如下:

struct CFDictionaryKeyCallBacks {
    CFIndex version;
    CFDictionaryRetainCallBack retain;
    CFDictionaryReleaseCallBack release;
    CFDictionaryCopyDescriptionCallBack copyDescription;
    CFDictionaryEqualCallBack equal;
    CFDictionaryHashCallBack hash;
};

struct CFDictionaryValueCallBacks {
    CFIndex version;
    CFDictionaryRetainCallBack retain;
    CFDictionaryReleaseCallBack release;
    CFDictionaryCopyDescriptionCallBack copyDescription;
    CFDictionaryEqualCallBack equal;
};

version参数目前应设为0。当前编程时总是取这个值,这个参数可以用于检测新版与旧版数据结构之间是否兼容。结构体中的其余成员都是函数指针,它们定义了当各种事件发生时应该采用哪个函数来执行相关任务。比如,如果字典中加入了新的键与值,那么就会调用retain函数。此参数的类型定义如下:

typedef const void * (*CFDictionaryRetainCallBack) {
    CFAllocatorRef allocator,
    const void *value
};

由此可见,retain是个函数指针,其所指向的函数接受两个参数,其类型分别是CFAllocatorRef和const void *。传给此函数的value参数表示即将加入字典的键或者值。而返回的void *则表示要加到字典里的最终值。可以用下列代码实现这个回调函数:

const void * CustomCallback(CFAllocatorRef  allocator, const void *value) 
{
    return value;
}

这么写只是把即将加入字典中的值照原样返回。所以,如果用它充当retain回调函数来创建字典,那么该字典就不会"保留"键与值了。将此种写法与无缝桥接搭配起来就可以创建出特殊的NSDictionary对象,而其行为与用Objective-C创建的普通字典不同。

#import <CoreFoundation/CoreFoundation.h>

const void* WWRetainCallback(CFAllocatorRef allocator, const void *value)
{
    return CFRetain(value);
}

void WWReleaseCallback(CFAllocatorRef allocator, const void *value)    
{
    CFRelease(value);
}

CFDictionaryKeyCallBacks keyCallbacks = {
    0,
    WWRetainCallback,
    WWReleaseCallback,
    NULL,
    CFEqual,
    CFHash
};

CFDictionaryValueCallBacks valueCallbacks = {
    0,
    WWRetainCallback,
    WWReleaseCallback,
    NULL,
    CFEqual
};

CFMutableDictionaryRef aCFDictionary = CFDictionaryCreateMutable(NULL, 0, &keyCallbacks, &valueCallbacks);

NSMutableDictionary *anNSDictionary = (__bridge_transfer NSMutableDictionary *)aCFDictionary;

在设定回调函数的时候,copyDescription取值NULL,采用默认的实现就可以了。equal和hash采用CFEqual和CFHash,这两个的做法与NSMutableDictionary的默认实现相同。CFEqual最终会调用NSObject的"isEqual:"方法.

第四条:构建缓存是选用NSCache而非NSDictionary。NSCache的好处是,当系统资源将要耗尽时,他可以自动删减缓存。此外,NSCache还会先行删减"最久未使用的"(lease recently used)对象。NSCache也是线程安全的。开发者可以操控缓存删减其内容的时机。有两个与系统资源相关的尺度可供调整。其一是缓存中的对象总数,其二是所有对象的"总开销(overall cost)",开发者在将对象加入缓存的时候,可以为其指定"开销值"。当对象总数或总开销超过上限时,缓存就可能会删除其中的对象了。

#import "WWCacheClass.h"
#import "WWNetworkFetcher.h"

@implementation WWCacheClass
{
    NSCache *_cache;
}

- (id)init
{
    if ((self = [super init])) {
        _cache = [NSCache new];
    
        //cache a maximum of 100 URLs
        _cache.countLimit = 100;
    
        /**
         * The size in byte of data is used as the cost
         * so this sets a cost limit of 5MB.
         */
        _cache.totalCostLimit = 5 * 1024 * 1024;
    }
    return self;
}

- (void)downloadDataForURL:(NSURL *)url
{
    NSData *cachedData = [_cache objectForKey:url];
    if (cachedData) {
        //cache hit
        [self useData:cachedData];
    } else {
        //cache miss
        WWNetworkFetcher *fetcher = [[WWNetworkFetcher alloc] initWithURL:url];
        [fetcher startWithCompletionHander:^(NSData *data){
            [_cache setObject:data forKey:url cost:data.length];
            [self useData:cachedData];
        }];
    }
}
@end

还有个类叫NSPurgeableData,和NSCache可以搭配使用。此类事NSMutableData的子类。也实现了NSDiscardableContent协议。如果某个对象所占的内存能根据需要随时丢弃,那么久可以实现该协议所定义的接口。NSDiscardableContent协议里定义了名为isContentDiscasrded方法,可以用来查询相关内存释放已释放。如果要访问某个NSPurgeableData对象,可以调用beginContentAccess方法,使之不丢弃自己所占的内存。用完之后,调用endContentAccess方法。使之在必要时可以丢弃自己所占的内存。如果将NSPurgeableData对象加入NSCache,那么当该对象为系统丢弃时,也会自动从缓存中移除。通过NSCache的evictsObjectsWithDiscardedContent属性,可以开启或关闭该功能。改写刚才的方法:

- (void)downloadDataForURL:(NSURL *)url
{
    NSPurgeableData *cachedData = [_cache objectForKey:url];
    if (cachedData) {
        //cache hit

        //stop the data  being purged
        [cacheData beginContentAccess];
        [self useData:cachedData];
        //Mark that the data my be purged again
        [cacheData endContentAccess];
    } else {
        //cache miss
        WWNetworkFetcher *fetcher = [[WWNetworkFetcher alloc] initWithURL:url];
        [fetcher startWithCompletionHander:^(NSData *data){
            NSPurgeableData *purgeableData = [NSPurgeableData dataWithData:data];
            [_cache setObject:data forKey:url cost:purgeableData.length];
            //创建好NSPurgeableData对象之后,引用计数会多1,所以无须调用beginContentAccess
            [self useData:cachedData];
            //Mark that the data my be purged again
            [cacheData endContentAccess];
        }];
    }
}

第五条:精简initialize与load的实现代码。对于加入运行期系统中的每个类及分类来说,必定会调用+ (void)load方法而且仅调用一次。如果分类和其所属的类都定义了load方法,则先调用类里的,在调用分类里的。load方法并不像普通的方法那样,它并不遵从那套继承规则,如果某个类本身没有实现load方法,那么不管其各级超类舒服实现此方法,系统都不会调用。而且load方法务必实现的精简一些,也就是尽量减少其所执行的操作,因为整个应用程序在执行load方法是都会阻塞。想执行与类相关的初始化操作,还可以覆写+ (void)initialize方法。对于每个类来说,该方法会在程序收藏用该类之前调用,且只调用一次。不能通过代码直接调用。它是"惰性调用的",既只有当程序用带了相关的类时,才会调用。故,如果某个类一直都没有使用,那么其initialize方法就一直不会运行。initialize方法与其他的消息一样,如果某个类未实行它,而其超类实现了,那么久会运行超类的实现代码。

整数可以在编译期运行,然而可变数组不行,因为它是个Objective-C对象,所以穿件实例之前必须先激活运行期系统。某些Objective-C对象也可以在编译期创建,如NSString实例。下面这个创建会报错:

static NSMutableArray *kSomeObjects = [NSMutableArray new];

第六条:NSTimer会保留其目标对象。Foundation框架中有个类叫NSTimer,可以使用它指定绝对日期与时间,以便到时执行任务,也可以指定执行任务的相对延迟时间。计时器还可以重复运行任务,有个与之相关的"间隔值"(interval)可用来指定任务的触发频率。计时器要和"运行循环"(run loop)相关联,运行循环到时会触发任务。可以使用以下方法:

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)selector userInfo:(id)userInof repeats:(BOOL)repeats;

用此方法创建出来的计时器,会在指定的间隔时间之后执行任务。也可以反复执行任务。target与selector参数表示计时器将在哪个对象上调用哪个方法。计时器将会保留其目标对象,等到自身"失效"时才释放此对象。此时需要注意循环引用的问题。

最后,本书一共7个章节,这已经是这本书的最后一章了,系统框架。

共勉!一步一个巴掌印。。。。。

上一篇下一篇

猜你喜欢

热点阅读