美文网首页iOS DeveloperiOS开发effect object c
阅读Effective Object-C 2.0 笔记(七)

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

作者: iLeooooo | 来源:发表于2016-04-28 14:08 被阅读189次

    还是要好好学习英文啊,笔者只能看中文版的,下载地址如下:
    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个章节,这已经是这本书的最后一章了,系统框架。

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

    相关文章

      网友评论

        本文标题:阅读Effective Object-C 2.0 笔记(七)

        本文链接:https://www.haomeiwen.com/subject/froqrttx.html