iOSiOS学习收录首页投稿(暂停使用,暂停投稿)

第七章 系统框架(EffectiveObjective-C)

2016-07-14  本文已影响181人  谁动了MyWorld

1 第一节

1.多有块枚举,少用for循环

  NSArray *array = [NSArray array];
   //for循环遍历
   for (int i = (int)(array.count - 1); i>0; i--) { 
        id obj = array[i]; 
        //do something with obj
    }
  
  //NSEnumerator遍历法
    NSEnumerator *enumerator = [array reverseObjectEnumerator];
    id obj;
    //enumerator关键方法是nextobject,他返回枚举里的下个对象,每次调用该方法时,其内部数据结构都会更新
    //使得下次调用方法时能返回下个对象,等到枚举中的全部对象都已返回之后,再调用就返回nil.这表示到末端了
    while ((obj = [enumerator nextObject]) != nil) {
        //do something with obj
    }

    //快速遍历法
    //某个对象支持快速遍历,就表示该对象遵守了NSFastEnumeration协议   
   for (id obj in array) { 
       //do something with obj 
   }
    //块枚举法
    //NSEnumerationReverse 反向
    [array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        //do something with obj
    }];  
  NSDictionary *dictionary = @{@"key1":@"value1",@"key2":@"value2",@"key3":@"value3"}; 
  //指定对象的精确类型之后,编译器就可以检测出开发者是否调用了该对象所不具备的方法,并在发现这种问题的    //时候报错
  [dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key,NSString *obj, BOOL * _Nonnull stop) {  
      //do something     
    }];

2 对自定义其内存管理语意的collection使用无缝桥接

Function框架定义了NSDictionary,NSArray,NSSet等collection所对应的OC类,与之相似,CoreFoundation框架也定义了一套C语言API,用于操作表示这些collection及其他各种collection的数据结果.如NSArray是Foundation框架中表示数组的OC对象,而CFArray则是CoreFoundation框架中的等价物.这2中创建数组的方式有区别,但是我们可以使用"无缝桥接"(toll-free bridging)来平滑转换.如

 NSArray *anArray = @[@1,@2,@3];
 CFArrayRef aCFArray = (__bridge CFArrayRef)(anArray);
 NSLog(@"size of array = %li",CFArrayGetCount(aCFArray));
 //output:size of array = 3

转换操作中的__bridge告诉ARC,如何处理转换所涉及的OC对象.
__bridge:本事的意思是:ARC仍然具备这个OC对象的所有权
__bridge_retained:则与之相反,意味着ARC将交出对象的所有权.若是前面那段代码改用他来实现,那么用完数组之后要加上CFRelease来手动释放.
__bridge_transfer:可以把CFArrayRef转换为NSArray*,并且相令ARC获得所有权,那么就可以采用此种方式转换

何种情况下会使用呢?
其实Foundation框架中的collection对象拥有CoreFoundation中的collection结构所不具备的功能.然而使用Foundation中的字典对象时会遇到一个大问题,那就是其键的内存管理语意为"拷贝",而值的语意却是"保留".除非使用强大的无缝桥接技术,否则无法改变其语义.
CoreFoundation中有个CFMutableDictionary.创建方法:

CFDictionaryCreateMutable(
        <#CFAllocatorRef allocator#>,//第一个参数内存分配器,一般传NULL,表示使用默认的分配器
        <#CFIndex capacity#>,//字典初始大小,它并不会限制字典的最大容量,只是向分配器提示了一个开始应该分配多少内存
        <#const CFDictionaryKeyCallBacks *keyCallBacks#>,//指向结构体的指针如下
        <#const CFDictionaryValueCallBacks *valueCallBacks#>)//指向结构体的指针如下

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

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

关于2个结构体指针的说明:
第一个参数version参数目前应设为0.
第二个参数函数指针,接受2个参数,类型分别为CFAllocatorRef与void *.传入的value参数表示即将存入字典的键或值.返回的void *则表示要加到字典的最终值
typedef const void * (*CFDictionaryRetainCallBack)(CFAllocatorRef allocator, const void *value);
如下实现:把即将加入字典的值照样返回,用它来充当retain回调函数来创建字典,则不会保留键与值
const void * CustomCallBack(CFAllocatorRef allocator,const void *value){ return value;
}
后面几个参数同第二个参数.

下列完整的展示了这种字典的创建:

const void * EOCRetainCallBack(CFAllocatorRef allocator,const void *value){
    return CFRetain(value);
}
void EOCReleaseCallBack(CFAllocatorRef allocator,const void *value){
    CFRelease(value);
}
- (void)createAnCFDictionary{
       CFDictionaryKeyCallBacks keyCallBacks = {
        0,
        EOCRetainCallBack,
        EOCReleaseCallBack,
        NULL,
        CFEqual, 
       CFHash
    };
    CFDictionaryValueCallBacks valueCallBacks = {
        0,
        EOCRetainCallBack,
        EOCReleaseCallBack, 
       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"方法
键与值所对应的retain与release回调函数指针分别指向EOCRetainCallBack与EOCReleaseCallBack函数.我们在使用Foundation框架中的dictionary加入键值的时候,字典会自动拷贝键,保留值.如果用作键的对象不支持拷贝操作,会报错
在使用CoreFoundation创建的字典,我们修改了内存管理语意,对键执行保留而非拷贝操作

3 构建缓存时选用NSCache而非NSDictionary

在开发应用程序时,经常会遇到需要用缓存的时候.在实现缓存的时候我们应该使用NSCache而非NSDictionary.因为:

typedef void(^SJNetWorkToolCompletionHandler) (NSData *data);
@interface SJNetWorkTool : NSObject
 - (instancetype)initWithURL:(NSURL *)url;
 - (void)startWithCompletionHandler:(SJNetWorkToolCompletionHandler)handler;
@end

 #import "SJNetWorkTool.h"
@implementation SJNetWorkTool{
    NSCache *_cache;
}
 - (instancetype)init{
    if (self = [super init]) {
        _cache = [NSCache new];
        //chache a maximum of 100 urls
        _cache.countLimit = 100;
        // set a cost limt of 5MB
        _cache.totalCostLimit = 5 *1024 * 1024;
    }
    return  self;}
 - (void)downLoadDataForURL:(NSURL *)url{
    NSData *cacheData = [_cache objectForKey:url];
    if (cacheData) {
        [self useData:cacheData];
    }else{
        SJNetWorkTool *tool = [[SJNetWorkTool alloc] initWithURL:url];
        [tool startWithCompletionHandler:^(NSData *data) {
            [_cache setObject:data forKey:url cost:data.length]; 
           [self useData:data];
        }];
    }}
 - (void)useData:(NSData *)data{ 
   //use data do something
}
@end

还有个类叫做NSPurgeableData,和NSCache搭配起来效果更好,此类是NSMutableData的子类,而且实现了NSDiscardableContent协议,如果某个对象所占内存能够根据需要随时丢弃,那么就可以实现改协议所定义的接口.也就是说.当系统资源紧张时,可以把保存NSPurgeableData对象的那块内存释放.NSDiscardableContent协议里定义了名为isContendDiscarded的方法,用来查询相关内存是否已释放.
如果需要访问某个NSPurgeableData对象,可以调用其beginContendAccess方法,告诉他现在还不应该丢弃自己所占据的内存.用完之后,调用endContendAccess方法,告诉他在必要时可以丢弃自己所占据的内存.
如果将NSPurgeableData对象加入NSCache,那么当该对象为系统所丢弃时,也会自动从缓存总移除.通过NSCache的evictsObjectsWithDiscardedContend属性,可以开启或关闭此功能.
刚才哪个例子可用NSPurgeableData改写如下:

 - (void)downLoadDataForURL:(NSURL *)url{
    NSPurgeableData *cacheData = [_cache objectForKey:url];
    if (cacheData) {
        //stop the data being purged
        [cacheData beginContentAccess];
        //use the cacheData
        [self useData:cacheData]; 
       //mark that the data may be purged again
        [cacheData endContentAccess];
    }else{
        SJNetWorkTool *tool = [[SJNetWorkTool alloc] initWithURL:url];
        [tool startWithCompletionHandler:^(NSData *data) { 
           NSPurgeableData *purgeableData = [NSPurgeableData dataWithData:data];
            //cache
            [_cache setObject:purgeableData forKey:url cost:purgeableData.length];
            //don't need to beginContentAccess as it begin
            //use the cacheData
            [self useData:purgeableData];
            //mark that the data may be purged again
            [purgeableData endContentAccess];
        }]; 
   }
}

注意:创建好的NSPurgeableData对象之后,其"purge引用计数"会多1.所以无需在调用beginContentAccess了.然而其后必须调用endContentAccess,将多出来的这个"1"抵消掉

4 精简initialize 与 load 的实现代码

  //在类被加载的时候调用
 + (void)load {
} 
 //在类被使用的时候调用 
 + (void)initialize{
}
 + (void)initialize{
    if (self == [SJNetWorkToolclass]) { 
       //do something
    }
}

5 别忘了NSTimer会保留其目标对象

@interface SJTool : NSObject
 - (void)startPolling;
 - (void)stopPolling;
@end
#import "SJTool.h"
@implementation SJTool{
    NSTimer *_pollTimer;
}
 - (void)startPolling{
    _pollTimer = [NSTimerscheduledTimerWithTimeInterval:5.0target:selfselector:@selector(p_doPoll) userInfo:nilrepeats:YES];
}
 - (void)stopPolling{
    [_pollTimerinvalidate];
    _pollTimer = nil;
}
 - (void)p_doPoll{
    //do poll thing
}
 - (void)dealloc{
    [_pollTimerinvalidate];
}
@end

上面的例子中,SJTool强引用了timer,由于timer是repeat的所以会强引用SJTool.如图

retainCycle.png
 @interface NSTimer (BlocksSupport)
 + (NSTimer *)sj_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats;
@end

 #import "NSTimer+BlocksSupport.h"
 @implementation NSTimer (BlocksSupport)
 + (NSTimer *)sj_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void (^)())block repeats:(BOOL)repeats{
    return [NSTimerscheduledTimerWithTimeInterval:interval target:selfselector:@selector(sj_blockInvoker:) userInfo:[block copy] repeats:repeats];
}
 + (void)sj_blockInvoker:(NSTimer *)timer{
    void(^block)() = timer.userInfo; 
   if (block) {
        block();
    }
}
@end

创建timer的时候引入分类

 #import "NSTimer+BlocksSupport.h"
 - (void)startPolling{
    _pollTimer = [NSTimersj_scheduledTimerWithTimeInterval:5.0block:^{
        [selfp_doPoll];    } repeats:YES];
}

仔细看看代码,还是有保留环,因为块捕获了self变量,所以块要保留当前类的实例self.而计时器又通过userInfo参数保留了块,最后,实例本身还要保留计时器.如图

aRetainCycle.png

解决办法可以改用weak修饰当前引用计时器的实例对象self

- (void)startPolling{
    //before
//    _pollTimer = [NSTimer sj_scheduledTimerWithTimeInterval:5.0 block:^{
//        [self p_doPoll];
//    } repeats:YES];
    //now
    __weakSJTool *weakSelf = self;
    _pollTimer = [NSTimersj_scheduledTimerWithTimeInterval:0.5block:^{
        SJTool *strongSelf = weakSelf;
        [strongSelf p_doPoll];
    } repeats:YES];
}

在这段代码中采用了一种很有效的写法,weak-strong-dance.先定义一个弱引用,令其指向self,然后使块捕获这个引用,而不直接去捕获普通的self变量.也就是说,self不会为计时器所保留.当块开始执行时.立刻生成strong引用.保证实例在执行期间继续存活.

上一篇 下一篇

猜你喜欢

热点阅读