读oc52个有效方法的总结

oc语言的代码优化和技巧 oc语言的代码优化和技巧

1.熟悉oc语言

第一条:了解oc的语言起源

主要是对于oc语言的起源介绍和oc语言的特点进行概括,oc语言主要是使用消息结构而非函数调用,消息与函数调用之间的区别如下

//messaging (oc)

1.Object * obj = [Object new];

[obj performWith:parmeter1 and:parameter2];

//function calling(c++)

object * obj = new Object;

obj->perform(parameter1,parameter2);

区别在于:

  • a.使用消息结构的语言运行时所应执行的代码由运行环境决定;运用函数调用的则是在编译器决定,oc是一种动态绑定的语言

  • b.oc的重要工作都由运行期组件而非编译器来决定

  • c.oc是c得超集所以c语言中得所有功能在编写oc代码是依然使用,oc语言中得指针是用来指示对象的nsstrig * something = @The string; 此变量位指向Nsstring 的指针,所有oc语言的对象都必须这样声明,对象所占的内存从事分配在堆空间,而不会分配在栈上,不能在栈上分配oc对象

第一条的要点总结:

  • 1.oc位c语言添加了面向对象特性,是其超集,oc使用动态绑定的消息结构,也就是说在运行时才会检查对象类型,接收一条消息之后究竟应执行何种代码,有运行环境而非编辑器决定。

  • 2.理解c语言的核心尤其是内存和指针。

第二条:在类的头文件中尽量少引入其他文件

要点:1.除非确有必要,否则不要引入头文件,一般来说,应在某个类的头文件中使用向前声明来提及别的类,并在实现文件中引入哪些类的头文件,这样做可以尽量降低类之前的耦合

2.有时无法使用向前声明,比如要声明某个类遵循一项协议,这种情况下,尽量吧该类遵循某协议的这条声明移至分类中,如果不行的化就把协议单独放在一个头文件中,然后将其引入

第三条:多用字面量语法,少用与之等价的方法

字面数值: 封装各种类型的基本变量 NSnumber * someThing = [nsmunber numberwithint:1];等于 Nsmuber * sonmeThing = @1;

也始于如下

int x = 5;

int y = 6.32f;

nsnumber * expressionnuber = @(x*y);

字面量数组

通常声明NSarray * array = [NsArray arraywithobjects@"cat"];

等于NSArray * array = @[@"cat"];

nsstrin * dog = array[1];

取下标的时候要用字面量会发现错误

字面量字典

nsdictiongary * dic = @{@“first”:@“haha”};

nsstirng * lasName = dic[@"name"];

优点简洁

局限性

字面量语法有个小小的限制,就是除了字符串以外,所创建出来的对象必须属于Foundation框架才行使用字面量语法创建出来的字符串,数组,字典对象都是不可变的若想要可变版本的对象,侧需要复制一份

NSmutablearray * mutablearray = [[@1,@2]]mutablecopy];

要点:a.应该使用字面量语法来穿件字符串,数组,数值,字典,渔船件此类对象的常规方法相比,这么做更加简明扼要

b.应该通过去下表操作老访问数组下标或字典中得键所对应的元素

c.用字面量语法创建数组活字典是,若值中有nil,则会跑出异常,一次,务必确保值里不含nil

第四条:多用类型常量,少用define 预处理指令

1.编写代码的时候经常用到定义常量,大多数人会利用宏定义如定义一个 如:#define ANIMATION_DURATION 0.3

但是这样因为ANIMATION_DURATION并未特殊指明预处理的缺点是会把 所有碰到的ANIMATION_DURATION都替换掉;针对这种情况就可以利用 static const NSTimeInterval kAnimationDuration = 0.3来定义这种方式更容易阅读;另外就是位置定义常量的位置很重要一般我们会放在。h文件中但是这样的话当我们导入头文件的时候,在别的类中都会出现这个名字,所有我们可以在。m中声明如上。

变量一定要用static和const来声明,const修饰不可变的变量,他是一个定值,另外作用域仅限于此类中,和define 等同,但是用这种方式定义的常量室友类型信息的。

2,当我们有的时候需要对外界公开某个常量时候例如在类代码中调用通知,派发通知的时候仅声明一个外界课件的常值变量这样就可以用到如下定义:

extern nsstring * const EOCStringConstant;//.h中

nsstirng * const EocStringConstant = @“value”;//。m中

要点:a不要用预处理指令定义常量,这样定义出来的常量不含类型信息,编译器只是会再编译钱据此执行查找与替换操作。意识有人重新定义了常量值,编辑器也不会产生警告信息,这将导致应用程序中的常量值不一致

b。在实现文件中使用static const 来定义“只在编译单元内可见的常量”由于此类常量不在全局符号中,所有无需为其名称前加前缀

c.在头文件中使用extern来声明全局常量,并在相关实现文件中定义其值,这种常量出现在全局符号表中,所以期名称应加以区隔,通常用与之相关的类名做前缀。

第5条:多用枚举表示状态,选项,状态码

1.如表示套接字的链接状态enum EOCConnectionState{

EOCConnectionStateDisconnected,

EOCConnectionStateconnecting,

EOCConnectionStateconnected,
}

但是定义枚举的方式不简洁 如下: enum EOCConectionState state = EOCConectionStateDisconnected;

可以用typedef 关键字重新定义枚举类型

typedef enum EOCConnectionState EOCConnectionState; 》 EOCConnectionState state = EOCConectionStateDisconnected;

另外可以手工给某个枚举成员赋值 下面往后的都会在前一个基础+1

enum EOCConnectionState{

EOCConnectionStateDisconnected=1,

EOCConnectionStateconnecting,

EOCConnectionStateconnected,
}typed

2.可以给枚举定义类型

typedef NS_ENUM(NSUInteger, EOCConnectionState) {
EOCConnectionStateDisconnected,
EOCConnectionStateconnecting,
EOCConnectionStateconnected,
};

3.最后一种方式就是在switch中运用枚举这样简洁明了

switch (_currentState) {
EOCConnectionStateDisconnected:
//
break;
EOCConnectionStateconnecting:
//
break;
EOCConnectionStateconnected:
//
break;

        default:
            break;//一般我们都会用到default 但是在状态机的时候组号不要用default,这样如果稍后又加一种状态就出现警告
    }

要点:a.应该用枚举来表示状态机的状态,传递给方法的选项以及状态码等值,给这些值七个易懂的名字。

b.如果吧传递给某个方法的选项表示为枚举类型,而多个选项又可以同时使用,那么就将各选项值定义为2的幂,一边通过安位或操作将其组合起来

c.用NS_ENUM与NS_OPTIONS宏来定义枚举类型,病指明其底层数据类型,这样做可以确保枚举是用开发者所选的底层数据类型实现出来的,而不会采用编辑器所选的类型

d.在处理枚举类型那个的swith语句中不要实现default分支,这样的话,加入新枚举之后,编译器就会提示开发者,swith语句并未处理所有枚举

第5章 内存管理

第29条:理解引用计数

1.概念:oc使用引用计数管理内存,意思就是每个对象都有可以递增活递减的计数器,如果想让某个对象继续存活,那就递增引用计数,用完后就递减其计数当为0时候就可以销毁。

引用计数原理 retain 递增保留计数 release 递减保留计数 autorelease 稍后清理“自动释放池”,在递减保留计数

不要这样编写代码

nsnumber * number = [[nsnumber alloc]initwithInt:1337];

[array addObject:number];

[number release];

nslog(@number="%@",number); 如果调用release 基于某些原因保留计数可能会降为0这时候输出的话有可能会崩溃,但是这种情况不是一定的有可能所以可以这样写

在最后追加number = nil;

2.自动释放池 通常我们用release会立即递减引用计数,然而有时可以不用立即递减它这时候就用到autorelease,此方法会在稍后递减计数,通常是下一次“时间循环”递减,不过有可能执行的更早一点通常这样用

  • (nsstring*)stringValue{

nssstring * str = [[nsstring alloc]initWithFormat:@"I am this:%@",self];

return [str autorelease];

}

nsstring * str = [[self stringvalue]retain];//当我们想持有对象的时候可以retain 稍后在释放[str release];

3.保留环

有的时候可能会出现a引用b,b引用c,c有引用a这样的话就会出现循环引用这种情况通常采用弱引用来解决,或是从外界命令循环中的某个对象不在保留另外一个对象,这两种办法都能打破保留环从而避免内存泄露。

要点:引用计数机制通过可以递增递减的计数器来管理内存,对象创建好了之后,其保留计数至少为1.若保留计数为正,测对象继续存活,当保留计数降为0时,对象被销毁了。

在对象生命期中,其余对象通过引用来保留活释放此对象,保留与释放操作分别会递增及递减保留计数。

第30条以arc简化引用计数 arc和非arc机制是一样的不在多说

ARC会用一种安全的方式来设置:先保留新值,在释放旧值,最后设置实例变量,用了arc之后就不用考虑边界问题了

——strong:默认语义,保留此值。

——unsafe_unretained:不保留此值,这么做可能不安全,因为等到再次使用变量时,其对象可能已经回收了。

__weak:不保留此值,但是变量可以安全使用,因为如果系统吧这个对象回收了,那么变量也会自动清空。

__autoreleasing:把对象“按引用传递”给方法时使用这个特殊的修饰符,此值在方法返回时候自动释

声明一个块可能会出现保留环,因为块会自动保留其所捕获的全部对象,而若果这其中有某个对象有保留了快本身,那么可能出现保留环。可以用到__weak这样可以

的局部变量打破这种“保留环”

:nsurl * url = [nsurl ........];

EOCNetworkFectcer * fetcher = [EOCNetworkFecther alloc]initWithURL:URL];

EOCNetworkFectcer * __week weakFectcher = fetcher;

[fetcher startWithCompletion:^(BOOL success){

//省略。。。。

}];

2.ARC如何清理实例变量 通常手动管理的时候我们会:

  • (void)dealloc{
    [_foo release];

[_bar release];

[super dealloc];

}

当arc的时候通常不用写但是如果有非oc的对象时候,譬如COreFoundation中的对象是由malloc()分配, 要这样写

  • (void)dealloc{

CFRelease(_coreFoundationobject);

free(_heapAllocatedMemoryBlob);

}

3.覆写内存管理方法

不适用arc的时候可以覆写管理方法,但在arc环境下就不能这么做了,因为会干扰到arc分享对象生命周期的工作,不可调用及覆写这些方法

http://www.kuqin.com/shuoit/20140120/337708.html

要点:a。有ARC之后,程序员就无须担心内存管理问题了,使用ARC来编程,可省去类中的许多”样板代码“

b。ARC管理对象生命周期的办法基本上就是:在合适的地方插入”保留“以及”释放“操作,在ARC环境下,变量的内存管理语义可以通过修饰符指明,而原来则需要手工执行”保留“及”释放“操作

c。由方法所返回的对象,其内存管理语义总是通过方法名来体现,ARC将此确定为开发者必须遵守的规则

d。ARC只负责管理oc对象的内存,尤其要注意:CoreFoundation 对象不贵ARC管理,开发者必须适时调用CFRetaion/CFRelease.

第31条:在dealloc方法中只释放引用并解除监听

-(void)dealloc{

CFRelease(coreFoundationObject);

[[NSNotificationCenter defauleCenter]removeObserver:self];

}//ARC不用在调用[super dealloc];

要点:a.在dealloc方法中,应该做的事情就是释放指向其他对象的引用,并取消原来订阅的”键值观测KVO“活NSNotificationCenter等通知,不要做其他事情。

b.如果对象持有文件描述等系统资源,那么应该专门编写一个方法来释放此种资源,这样的类要和其使用者约定,用完资源后必须调用close方法。

c.执行异步任务的方法不应再dealloc里调用:只能正常状态下执行的那些方法也不应在dealloc里调用,因为此时对象已处于正在回收的状态了。

第32条:编写”异常安全代码“时留意内存管理问题

当你编码中使用到了第三方库而此程序库所跑出的异常又不受你控制时,就需要捕获及处理异常了,有些系统的库也会用到异常,如kvo弱项注销一个尚未注册的”观察者,“便会抛出异常

EOCSomeClass * object;
@try {
object = [[EOCSomeClass alloc]init];
[object doSonmenThingMayThrow];
}
@catch (NSException *exception) {
NSLog(@"出错了");
}
@finally {
[object release];
}

在ARC环境下

@try {
EOCSomeClass * object = [[EOCSomeClass alloc]init];
[object doSonmenThingMayThrow];
}
@catch (NSException *exception) {
NSLog(@"出错了");
}
要点:捕获异常时,一定要注意将try块内所创立德对象清理干净

在默认情况下,ARC不生成安全处理异常所需的清理代码,开启编译器标志后,可生成这种代码,不过会导致应用程序变大,而且会降低运行效率。

第33条:以弱引用避免保留环

@class EOCClassA;
@class EOCClassB;
@interface EOCClassA : NSObject
@property (nonatomic,strong)EOCClassA * other;
@end
@interface EOCClassB : NSObject
@property (nonatomic,strong)EOCClassA * other;

@end

以上代码会导致循环

优化后

@class EOCClassA;
@class EOCClassB;
@interface EOCClassA : NSObject
@property (nonatomic,strong)EOCClassA * other;
@end
@interface EOCClassB : NSObject
@property (nonatomic,unsafe_unretained)EOCClassA * other;//使用weak和unsafe_unretained都可以但是unsafe_unretained更好点会令代码更安全

@end

要点:将某些引用设为week,可避免出现”保留环“。

weak引用可以自动清空,也可以不自动清空,自动清空是随着ARC而引入的新特性,由运行期系统来实现,在具备自动清空功能的弱引用上,可以随意读取其数据,因为这种引用不会指向已经回收过的对象。

第35条:以”自动释放池“降低内存峰值

//嵌套的可以降低峰值

@autoreleasepool {
NSString * string = [NSString stringWithFormat:@"1=%i",1];
@autoreleasepool {
NSNumber * number = [NSNumber numberWithInt:1];
}
}

另外就是对于for循环

for (int i = 0; i<100000; i++) {

@autoreleasepool {
[slef dosomething];

}
}

NSMutableArray * people = [NSMutableArray new];
for (NSDictionary * record in databaseRecores) {
@autoreleasepool {

       EOCPerson * person [[EOCPerson alloc]initWithRecord:recoed];
        [people addObject:person ];
    }
}

老式的写法有

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init];

NSMutableArray * people = [NSMutableArray new];
for (NSDictionary * record in databaseRecores) {

       EOCPerson * person [[EOCPerson alloc]initWithRecord:recoed];
        [people addObject:person ];
    if(++i==10)

{
[pool drain];

i=0;
}

要点:自动释放池排布在栈中,对象受到autorelease消息后,系统将其放入最顶端的池里

b.合理运用自动释放池,可降低应用程序的内存峰值

c.@auroreleasepool这宗信息写法能创建出更为轻便的自动释放池。

//arc 下的block

http://www.cnbluebox.com/?p=255

第6章 块与大中枢派发

对于一些ui之外需要使用一些线程来执行程序,当前多线程编程的核心就是“块”与“gcd”

块是一种可以在c,c++,和oc代码中使用的词法闭包,它极为有用,在定义“块”的范围内,他可以访问到其中的全部变量

gcd 是一种与块有关的技术,它提供了对线程的抽象,而这种抽象则基于“派发队列”,开发者可将块排入队列中,由gcd负责处理所有调度事宜。gcd会根据系统资源,适时的创建,服用,摧毁后台线程,以便处理每一个队列,此外,使用gcd换可以方便的完成常见编程任务,比如“只执行一次的线程安全代码”或者根据可用的系统资源来并发执行多个操作。

第37条:理解“块”这一概念

1.块的基础知识

^{
//block implementation here
}

无参无返回值

void(someBlock)()=
{
//block implementation here
};

有参数有返回值的

int (^addBlock)(int a,int b) = ^(int a, int b)
{
return a+b;
};

int add = addBlock (5,3)

块的强大之处在于:在声明它的范围里,所有变量都可以为其所捕获,这也就是说,那个范围里的全部变量,在快里依然可以使用

int additional = 5;
int (^addBlock)(int a, int b) = ^(int a, int b)
{
return a+b+additional;
};

int add = addBlock(2,5);

默认情况下,为块所捕获的变量,是不可以在块里修改的,如果在快中修改additional的值编译器就会报错,但是声明变量如果加上__block修饰符,这样就可以在块内中修改了,如

NSArray * array = @[@0,@1,@2,@3,@4,@5];
__block NSInteger count = 0;
[array enumerateObjectsUsingBlock:^(NSNumber * number , NSUInteger idx, BOOL *stop) {
if ([number compare:@2] == NSOrderedAscending) {
count++;
}

}];
//count=2

这段代码演示了内联块 的用法,传给 enumerateObjectsUsingBlock 方法的块并未先付给局部变量,而是直接内敛在函数里调用,由此可以看出块为何如此有用。

如果块所捕获的变量是对象类型,那么就会自动保留它,系统在释放这个块的时候,也会将其一并释放,这就引出了一个与块有关的重要问题,块本身可是为对象,实际上,在其他oc对象所能相应的选择子中,有很多事块也可以相应的,而最重要之处则在于,块本身和其他对象一样,有引用计数,当最后一个指向块的引用移走之后,快就回收了,回收时候也会释放块所捕获的变量,以便平衡捕获时候所执行的保留操作。

如果将块定义在oc类的实例方法中,那么除了可以访问类的所有实例变量之外,换可以使用self变量

2.块的内部结构

每个oc对象都占据者某一个内存区域,同时,每一个对象所占据的内存区域有大也有小,快本身也是对象,在存放块对象的内存区域中,首个变量是指向class对象的指针,该指针叫做isa,其余内存里含有块对象正常运转所需的各种信息。

在北村布局中,最重要 的就是invoke变量,这是个函数指针,指向块的实现代码,函数原型至少要接受一个void*型的参数,此参数代表块,刚才说过,块其实就是一种代替函数指针的语法结构,原来使用函数指针时,需要用“不透明的void指针”来传递状态,而该用块之后,册可以把原来用标准c语言特性所编写的代码封装成且易用的借口。

块会把它所捕获的所有变量都拷贝一份,这分拷贝放在descriptor变量后,捕获了多少个变量,就要占据多少内存空间,请注意,拷贝的并不是对象本身,而是指向这些对象的指针变量,invoke函数为何需要把块对象作为参数传递进来呢,原因就是,块执行时,要从内存中把这些捕获到的变量读出来。

3,全局块,栈块及堆块

定义块的时候,其所占的内存区域是分配在栈中的,也就是说块只定义在它的那个范围内有效

void (^block)();
if (/* some condition*/) {
block = ^{
NSLog(@"Block A");
};
}
else
{
block = ^{
NSLog(@"Block B");
};
}
block();
定义在if以及else中两个块分配在栈内存中,编译器会给每一个块分配好栈内存,然而等离开了相应的范围之后,编译器有可能吧分配给块的内存覆写掉。于是,这两个块只能保证在对应的if或else语句范围内有效,这样可以写出来的代码可以编译,但是运行起来时而正确时而错误。若编译器未覆写待执行的快,则程序照常运行,若覆写,则程序崩溃。

void (^block)();
if (/* some condition*/) {
block = ^{
[NSLog(@"Block A")copy ];
};
}
else
{
block = ^{
[NSLog(@"Block B") copy];
};
}
block();

可以给块对象发送copy消息以拷贝之。这样的话可以把块从栈复制到堆上了,若果不需要就释放它,arc就不用了

另外还有一种全局块,这种块不会捕捉任何状态,运行时也无须有状态来参与,块所使用的整个内存区域,在编译期就已经完全确定了,因此,全局快可以声明在全局内存中,而不需要在每次用到的时候于栈中创建,另外,全局快的拷贝操作是个空操作,因为全局块决不可能为系统所回收,这种块相当于是个单利

void (block)()=
{
NSLog(@"this is a block");
};
由于全局块所需的全部信息都能在编译器决定,所以可把它做成全局块。

要点:块是c,c++,oc中的词法闭包。

块可接受参数,也可返回值

块可以分配在栈或堆上,也可以是全局的,分配在栈上的块可拷贝到堆里,这样的话,就和标准的oc对象一样,具备引用计数了。

第38条 为常用的快类型创建typedef

以typedef重新定义块类型,可令块变量用起来更加简单。

定义新类型时应该遵从现有的命名习惯,勿使其名称与别的类型相冲突

不妨为同一个块签名定义多个类型别名,如果要重构的代码使用了快类型的某个别名,那么只需要修改相应的typedef中的块签名即可,无需改动其他typedef。

第39条:用handler块降低代码分散程度

下面是用委托模式和block分别写的数据请求

@class EOCNetworkFetcher;
@protocol EOCNetworkFetcherDelegate

  • (void)networkFetcher:(EOCNetworkFetcher)networkFetcher didFinishWithData:(NSData)data;

@end

@interface EOCNetworkFetcher:NSObject
@property (nonatomic,weak)iddelegate;

  • (id)initWithUrl:(NSURL*)url;
  • (void)start;

@end

//协议方法

  • (void)networkFetcher:(EOCNetworkFetcher)networkFetcher didFinishWithData:(NSData)data
    {
    _fetchedFooData = data;
    }

另一种块写出来的

typedef void(^)(EOCNetworkFetcherCompletionHandler)(NSData *data);
@interface EOCNetworkFetcher : NSObject

  • (id)initWithUrl:(NSURL *)url;
  • (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHander)hander;

@end

//和上边类似不过好处在于可以调用statr方法时直接以内联形式定义completion hander,可以使代码更加易懂。

typedef void(^)(EOCNetworkFetcherCompletionHandler)(NSData *data);
@interface EOCNetworkFetcher : NSObject

  • (id)initWithUrl:(NSURL *)url;
  • (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHander)hander;

@end

  • (void)fetchFooData{
    NSURL * url = [[NSURL alloc]initWithString:@"http://www.baidu.com" ];
    EOCNetworkFetcher * fetcher = [[EOCNetworkFetcher alloc]initWithUrl:url ];
    [fetcher StartWithCompletionHander:^(NSdata * data){
    _fetchedFooData = data;
    }];
    }

可以看出块写出来的代码更加整洁,另外委托模式有个缺点就是如果类要分别使用多个获取器下载不同数据的时候那么久的在delegate回调方法里根据传入的获取器参数来切换。代码省略。。

可以从asi和封装的blok请求看出。。block更加紧密。

要点:在创建对象时,可以使用内联的handler块将相关业务逻辑一并声明。

在有多个实例需要监控时,如果采用委托模式,那么经常需要根据传入的对象来切换,而若改用handler块来实现,则可直接将块与相关对象放在一起。

设计API时如果用到了hander块,那么可以增加一个参数,是调用者可通过此参数来决定应该把块安排在那个队列上执行。

第41条 多用派发队列,少用同步锁

在oc中,如果有多个线程要执行同一份代码,就需要用锁来实现某种同步机制,防止资源被强占,在gcd出现之前,有两种办法,第一种是采用内置的“同步块”

  • (void)synchronizedMethod
    {
    @synchronized(self)
    {
    //safe
    }
    }

这种写法会根据给定的对象,自动创建一个锁,并等待块中的代码执行完毕,执行到这段代码结尾处,锁就释放了,这种写法的一个弊端,因为公用同一个锁的那些同步块,都必须按顺序执行,若是在self对象上频繁加锁,那么程序可能要等另一端与此无关的代码执行完毕,才能继续执行当前代码,这样做是没必要的。

另一种写法是NSlock

NSLock * lock = [[NSLock alloc]init];

  • (void)synchronizedMethod
    {
    [lock lock];
    //safe
    [lock unlock];

}
上面这两种锁有个弊端就是所有同步块都要彼此抢夺同一个锁,那么执行速度就会大大减慢,并且这样做虽然保证线程安全,但是无法保证访问该对象时绝对安全。如在同一个线程上多次调用获取方法,每次获取到得结果未必相同,在两次访问操作之间,其他线程可能会写入新的属性值。

一种简单的代替同步块或锁对象就是用“串行同步队列”,将读取和写入操作都安排在同一个队列里,可保证数据同步。

_syncQueue = dispatch_queue_create("com.exemple.syncQueue", NULL);

  • (NSString*)someString
    {
    __block NSString * localSomeString;
    dispatch_sync(_syncQueue, ^{
    localSomeString = _someString;
    });
    return localSomeString;

}

  • (void)setSomeString:(NSString*)someString
    {
    dispatch_sync(_syncQueue, ^{
    _someString = someString;
    });
    }

当然不一定要设置成同步的就可以用到异步的

  • (void)setSomeString:(NSString*)someString
    {
    dispatch_async(_syncQueue, ^{
    _someString = someString;
    });
    }

//异步操作会提高速度,并且写入操作依然会按照顺序运行,但是异步派发的时候,需要拷贝块,若拷贝快所用的时间明显超过执行块所化的时间,则这种做法将比原来更慢,当然,在执行繁重的任务时,就需要考虑用到了。

上边的时串行队列,改用并发队列会更加有效。

_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

  • (NSString*)someString
    {
    __block NSString * localSomeString;
    dispatch_sync(_syncQueue, ^{
    localSomeString = _someString;
    });
    return localSomeString;

}

  • (void)setSomeString:(NSString*)someString
    {
    dispatch_sync(_syncQueue, ^{
    _someString = someString;
    });
    }

当然像上边的代码,还无法正确实现同步,所有读取操作与写入操作都会在同一个队列上执行,不过由于是并发队列,所以读取与写入操作可以随时执行,我们又不想让这些操作随意执行,此问题需要用到栅栏(barrier)如下

dispatch_barrier_async(dispatch_queue_t queue#, ^(void)block)

dispatch_barrier_sync(dispatch_queue_t queue#, ^(void)block)

在队列中,栅栏必须单独执行,不能与其他块并行,这只对并发队列有意义,串行队列都是按顺序执行的。并发队列如果发现接下来要处理的块是个栅栏块,那么就会一直等当前所有并发块执行完毕,才会单独执行这个栅栏块,当栅栏块执行完毕后才会继续执行。

上边的set方法代码可以改为

  • (void)setSomeString:(NSString*)someString
    {
    dispatch_barrier_async(_syncQueue, ^{
    _someString = someString;
    });

}
1代表读取2代表写入

要点:派发队列额用来表述同步语义,这种做法要比使用@synchronous块和NSLock对象更简单

将同步与异步派发结合起来,可以实现与普通加锁机制一样的同步行为,而这么做却不会阻塞执行异步派发的线程

使用同步队列以及栅栏块,可以令同步行为更加高效。

第42条:多用GCD,少用performSelector系列方法,

http://blog.sina.com.cn/s/blog_a3dbd02a0101b8y4.html

void perfprmSelector 有一些弊端所以用gcd可以代替

[self performSelector:@selector(domeSomthing) withObject:nil afterDelay:0.5];

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0*NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
[self dosomeThing];
});
把任务放到主线程上

[self performSelectorOnMainThread:@selector(dosomThing) withObject:nil waitUntilDone:NO];

dispatch_async(dispatch_get_main_queue(), ^{
//
});

要点:performSelector系列方法在内存管理方面容易有问题,它无法确定将要执行的选择子具体是什么,因而arc编译器也就无法插入适当的内存管理方法。

performSelector系列方法所能处理的选择子泰国局限了,选择子的返回值类型及发送给方法的参数个数都受限制。

如果想把任务放在另一个线程上执行,那么最好不要用performSelector可以用gcd代替。

第44条通过Dispatch Group机制,根据系统资源状况来执行任务

Dispatch Group可以吧任务分组,带哦用着可以等待这组任务执行完毕,也可以在提供回调函数之后继续往下执行,这组任务完成时,调用者会得到通知,这个功能有许多用途,最重要的就是把将要并发执行的多个任务合为一组,调用者可以知道这些任务合适才能全部执行完毕,比方说,可以吧压缩一系列文件的任务表示成Dispatch Group;

dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);

2个参数一个是等待的group,另一个是代表等待时间的timeout值,如果执行group的时间小于timeout则返回0,否则返回非0,也可以取forver永远执行不停止。

另一个方法

dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, ^(void)block);

此方法可以向此函数传入块,等dispatch group执行完毕之后,块会在在特定的线程上执行,假如当前线程不阻塞,开发者想在那些任务全部完成的时候得到通知。所有ui绘图都是在主线程执行

如果想领数组中每一个对象都执行某项任务,并且想等待所有任务执行完毕,那么就可以使用这个gcd特性来实现

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t dispatchGroup = dispatch_group_create();
for (id object in <#collection#>) {
dispatch_group_async(dispatchGroup, queue, ^{
[object performTask];
});
}
dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER);
/////

若当前线程不应阻塞,则可用notify函数来取代wait;notify回调时所选用的队列,完全应该根据具体情况来定,

下面例子中,所有任务都派发到同一个队列之中,实际上未必一定要这样做,也可以吧这些任务放在优先级高的线程上执行,同时仍然吧所有任务都归于同一个dispatch group,并执行完毕后获得通知

dispatch_queue_t lowPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t highPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_group_t dispatchGroup = dispatch_group_create();
for (id object in lowPriorityobjects) {
dispatch_group_async(dispatchGroup
, lowPriorityQueue, ^{
[object performTask];
});
}

for (id object in highPriorityobjects) {
    dispatch_group_async(dispatchGroup
                         , highPriorityQueue, ^{
                             [object performTask];
                         });
}

dispatch_queue_t notifyQueue = dispatch_get_main_queue();
dispatch_group_notify(dispatchGroup, notifyQueue, ^{
    //continue processing after completing tasks
});

除了上面的这样把任务提交并发队列之外也可以吧任务提交各个串行队列中,并用dispatch group跟踪其执行情况,但是多有任务排在同一个串行队列里面,dispatch group 用处就不打了,所以 只需要在提交完全部任务之后在提交一个即可,下面和通过notify函数等待dispatch group执行完毕后回调是等效的

dispatch_queue_t queue = dispatch_queue_create(@"com.exemple.queue", NULL);
for (id object in <#collection#>) {
    dispatch_async(queue, ^{
        [object performTask];
    });
}

dispatch_async(queue, ^{
    //continue processing after completing tasks
});

一种for循环可以用gcd替代

for (int i = 0; i<10; i++) {
<#statements#>
}

dispatch_queue_t queue =dispatch_queue_create(@"com.exemple.queue", NULL);
dispatch_apply(10, queue, ^(size_t i) {
    <#code#>
})

如果上边的处理的时数组

dispatch_queue_t queue =dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL);
dispatch_apply(array.count, queue, ^(size_t i) {
id object = array[i];
[object performTask];
})

所以未必总要使用dispatch group ,然而,dispatch_apply会持续阻塞直到执行完毕,若想在后台执行任务,则应使用dispatch group

要点

一系列任务可归入一个dispatch group之中,开发者可以在这组任务执行完毕时获得通知

通过dispatch group可以在并发派发队列里同时执行多项任务,此时gcd会根据系统资源状况来调度这些并发执行的任务,开发者若自己来实现此功能,则需要编写大量代码,gcd可以完美取代。

45 使用dispatch_once 来执行只需运行一次的线程安全代码 (单利模式)

要点

经常需要编写只需要执行一次的线程安全代码通过gcd可以实现

  • (id)shareInstance{

static class * shareInstance = nil;

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shareInstance = [self alloc]init;
});

return shareInstance;

posted @ 2014-11-02 17:40  小天才努努  阅读(336)  评论(0编辑  收藏  举报