IOS block 教程

本章学习目标: 1. 了解何谓block。  2. 了解block的使用方法。 注:变数=变量 app

   Block 是iOS在4.0以后新增的程式语法,严格来讲block的概念并不算是基础程式设计的范围,对初学者来讲也不是很容易了解,可是在iOS SDK 4.0以后,block几乎出如今全部新版的API之中,换句话说,若是不了解block这个概念就没法使用SDK 4.0版本之后的新功能,所以虽然block自己的语法有点难度,但为了使用iOS的新功能咱们仍是得硬着头皮去了解这个新的程式概念。 dom

    1.初探Block async

    在这一小节咱们先用一些简单范例来导入block的概念。 函数

           1.1 宣告和使用Block: 学习

            咱们使用「^」运算子来宣告一个block变数并且在block的定义最后面要加上「;」来表示一个完整的述句(也就是将整个block定义视为前面章节所介绍的简单述句,由于整个定义必须是一个完整的句子,因此必须在最后面加上分号),下面是一个block的范例: spa

int multiplier = 7;
int (^myBlock)(int) = ^(int num)
{
   return num * multiplier;
};

        咱们宣告一个「myBlock」变数,用「^」符号来表示这是一个block。这是block的完整定义,这个定义将会指定给「myBlock」变数。表示「myBlock」是一个回传值为整数(int)的block。它有一个参数,型态也是整数。这个参数的名字叫作「num」。这是block的内容。 设计

        值得注意的地方是block可使用和自己定义范围相同的变数,能够想像在上面的例子中multiplier 和myBlock 都是某一个函数内定义的两个变数也就是这个变数都在某个函数两个大括号「{」和「 }」中间的区块,由于它们的有效范围是相同的,所以在block中就能够直接使用 multiplier 这个变数,此外当把block定义成一个变数的时,咱们能够直接像使用通常函数般的方式使用它: code

int multiplier = 7 ;
int (^myBlock)( int ) = ^( int num)
{
   return num * multiplier;
};
printf ( "%d" , myBlock( 3 ));
//结果会打印出21
         1.2   直接使用Block


        在不少状况下,咱们并不须要将block宣告成变数,反之咱们能够直接在须要使用block的地方直接用内嵌的方式将block的内容写出来,在下面的例子中qsort_b函数,这是一个相似传统的qsort_t函数,可是直接使用block作为它的参数: orm

char *myCharacters[3] = { "TomJohn" , "George" , "Charles Condomine"};
qsort_b (myCharacters, 3,
         sizeof ( char *),
         ^( const void *l, const void *r)//block部分
           {
               char *left = *( char **)l;
               char *right = *( char **)r;
               return strncmp (left, right, 1 );
           }
);
          1.3  __block 变量


        通常来讲,在block内只能读取在同一个做用域的变数并且没有办法修改在block外定义的任何变数,此时若咱们想要这些变数可以在block中被修改,就必须在前面挂上__block的修饰词,以上面第一个例子中的 multiplier 来讲,这个变数在 block 中是惟读的,因此 multiplier = 7 指定完后,在 block 中的 multiplier 就只能是 7 不能修改,若咱们在 block 中修改 multiplier,在编辑时就会产生错误,所以若想要在 block 中修改 multiplier ,就必须在 multiplier 前面加上 __block 的修饰词,请参考下面的范例: 索引

__block int multiplier = 7;
int (^myBlock)(int) = ^(int num)
{
   if (num > 5 )
   {
       multiplier = 7 ;
   }
   else
   {
       multiplier = 10 ;
   } 
   return num * multiplier;                 
};
    2. Block概要


        Block 提供咱们一种可以将函数程式码内嵌在通常述句中的方法,在其余语言中也有相似的概念称作「closure」,可是为了配合Objective-C的贯例,咱们一概将这种用法称为「block」

             2.1 Block 的功能

                Block 是一种具备匿名功能的内嵌函数,它的特性以下: 如通常的函数般能拥有带有型态的参数。拥有回传值。能够撷取被定义的词法做用域(lexical scope)状态。能够选择性地修改词法做用域的状态。

                注:词法做用域(lexical scope)能够想像成是某个函数两个大括号中间的区块,这个区块在程式执行时,系统会将这个区块放入堆叠记忆体中,在这个区块中的宣告的变数就像是咱们常听到的区域变数,当咱们说block能够撷取同一词法做用域的状态时能够想像block变数和其余区域变数是同一个层级的区域变数(位于同一层的堆叠里),而block的内容能够读取到和他同一层级的其余区域变数。

               咱们能够拷贝一个block,也能够将它丢到其余的执行绪中使用,基本上虽然block在iOS程式开发中可使用在C/C++开发的程式片断,也能够在Objective-C中使用,不过在系统的定义上,block永远会被视为是一个Objective-C的物件。

            2.2 Block 的使用时机

                Block 通常是用来表示、简化一小段的程式码,它特别适合用来创建一些同步执行的程式片断、封装一些小型的工做或是用来作为某一个工做完成时的回传呼叫(callback) 。在新的iOS API中block被大量用来取代传统的delegate和callback,而新的API会大量使用block主要是基于如下两个缘由:

                    能够直接在程式码中撰写等会要接着执行的程式,直接将程式码变成函数的参数传入函数中,这是新API最常使用block的地方。

                    能够存取区域变数,在传统的callback实做时,若想要存取区域变数得将变数封装成结构才能使用,而block则是能够很方便地直接存取区域变数。

      3宣告和创建Block

         3.1 宣告Block的参考

                Block 变数储存的是一个block的参考,咱们使用相似宣告指标的方式来宣告,不一样的是这时block变数指到的地方是一个函数,而指标使用的是「*」,block则是使用「^」来宣告,下面是一些合法的block宣告:

/* 回传void ,参数也是void 的block*/
void(^blockReturningVoidWithVoidArgument)( void );

/* 回传整数,两个参数分别是整数和字元型态的block*/
int(^blockReturningIntWithIntAndCharArguments)(int,char);

/*回传void,含有10个block的阵列,每一个block都有一个型态为整数的参数*/
void(^arrayOfTenBlocksReturningVoidWinIntArgument[10])(int);

             3.2 创建一个Block

             咱们使用「^」来开始一个block,并在最后使用「;」来表示结束,下面的范例示范了一个block变量,而后再定义一个block把它指定给block变量:

/* 宣告block 变量*/
int (^oneFrom)( int );
/* 定义block 的内容并指定给上面宣告的变量*/
oneFrom = ^(int anInt)
{
    return anInt = - 1 ; 
};

             3.3 全局的Block

能够在档案中宣告一个全域的block,请参考如下范例:

int GlobalInt = 0;
int(^getGlobalInt)(void) = ^(void){
   return GlobalInt; 
};


4Block和变量

    4.1 变量的型态

        咱们能够在block中遇到日常在函数中会遇到的变量类型:

             全域(global)变量或是静态的区域变量(static local)。

             全域的函数。

             区域变量和由封闭领域(enclosing scope)传入的参数。

             除了上述以外block额外支援了另外两种变量:在函数内可使用__block 变量,这些变量在block中是可被修改的。汇入常数(const imports)。此外,在方法的实做里,block可使用Objective-C的实体变量(instance variable)。

       下列的规则能够套用到在block中变量的使用:

            能够存取全域变量和在同一领域(enclosing lexical scope)中的静态变量。

            能够存取传入block的参数(使用方式和传入函数的参数相同)。

            在同一领域的区域变数在block中将视为常数(const)。

            能够存取在同一领域中以__block 为修饰词的变数。

            在block中宣告的区域变数,使用方式和日常函数使用区域变数的方式相同。

            下面的例子介绍了区域变数(上述第三点)的使用方式:


int x = 123;
void (^printXandY)(int) = ^(int y)
{
   printf("%d %d", x,y);
}

printXAndY( 456 );//将会印出123 456

就如上面第三点所提到的,在上例中的int x = 123的变量x,在传入block后将视同常数,所以若咱们在block中试着去修改x的值时就会产生错误.若在block中想要修改上面的变数x,必须将x宣告加上修饰词__block,请参考接下来这一小节的介绍。    

    4.2 __block 型态变量

咱们能够藉由将一个由外部汇入block的变数放上修饰词__block来让这个变数由惟读变成能够读和写,不过有一个限制就是传入的变数 在记忆体中必须是一个占有固定长度记忆体的变数,__block修饰词没法使用于像是变更长度的阵列这类不定长度的变数,请参考下面的范例: 

//加上__block 修饰词,因此能够在block 中被修改。
__block int x = 123;
void (^printXandY)(int) = ^(int y)
{
   x = x+y;
   printf("%d %d",x,y);
}
printXAndY( 456 ); // 将会印出579 456
下面咱们使用一个范例来介绍各种型的变数和block之间的互动: 


extern NSInteger CounterGlobal;
static NSInteger CounterStatic;
{
   NSInteger localCounter = 42 ;
   __block char localCharacter;
   void(^aBlock)(void) = ^(void)
   {
      ++ CounterGlobal; //能够存取 
      ++ CounterStatic; //能够存取
      CounterGlobal = localCounter; //localCounter在block 创建时就不可变了
      localCharacter = 'a' ; //设定外面定义的localCharacter 变数
   }
   ++localCounter; //不会影响的block 中的值
   localCharacter = 'b';
   aBlock(); //执行block 的内容
   //执行完后,localCharachter 会变成'a'
}
      4 .3 物件和Block变量


Block 支援在Objective-C、C++物件和其余block中看成变数来使用,不过由于在大部分的状况咱们都是使用Objective-C的撰写程式,因 此在这一小节咱们仅针对Objective-C的状况进行介绍,至于其余两种状况就留给有兴趣的读者再自行深刻研究了。

    4.3.1Objective-C物件

在拥有参考计数(reference-counted)的环境中,若咱们在block中参考到Objective-C的物件,在通常的状况下它将会自动增长物件的参考计数,不过若以__block为修饰词的物件,参考计数则是不受影响。

     若是咱们在Objective-C的方法中使用block时,如下几个和记忆体管理的事是须要额外注意的:

        若直接存取实体变量(instance variable),self的参考计数将被加1。

        若透过变数存取实体变数的值,则只变数的参考计数将被加1。

        如下程式码说明上面两种状况,在这个假设instanceVariable是实体变量:

dispath_async(queue,^{

   //由于直接存取实体变数instanceVariable ,因此self 的retain count会加1
   doSomethingWithObject (instanceVariable); 
});

id localVaribale = instanceVariable;
dispatch_async(queue,^{
   //localVariable是存取值,因此这时只有localVariable 的retain count 加1
   //self 的return count并不会增长。
   doSomethingWithObject (localVaribale);
});
5. 使用Block


    5.1 呼叫一个Block

当block宣告成一个变量时,咱们能够像使用通常函数的方式来使用它,请参考下面两个范例:

int(^oneFrom)(int) = ^(int anInt){
   return anInt-1;
};
printf("1 from 10 is %d", oneFrom(10));
//结果会显示:1 from 10 is 9


float(^distanceTraveled)(float, float, float) = ^(float startingSpeed, float acceleration, float time)
{
   float distance = (startingSpeed * time) + ( 0.5 * acceleration * time * time);
   return distance;
};
float howFar = distanceTraveled( 0.0 , 9.8 , 1.0 );
//howFar会变成4.9
在通常常见的状况中,如果将block当作是参数传入函数,咱们一般会使用「内嵌」的方式来使用block。


    5.2 将Block看成函数的参数

咱们能够像使用通常函数使用参数的方式,将block以函数参数的型式传入函数中,在这种状况下,大多数咱们使用block的方式将不会倾向宣告block而是直接之内嵌的方式来将block传入,这也是目前新版SDK中主流的作法,咱们将补充前面章节的例子来讲明:

char *myCharacters[3]={"TomJohn","George","Charles Condomine"};
qsort_b(myCharacters, 3, sizeof(char *),^(const void *l,const void *r){
   char *left = *(char **)l;
   char *right = *( char **)r;
   return strncmp (left, right, 1 );
}//这里是block 的终点。 
);
//最后的结果为:{"Charles Condomine", "George", "TomJohn"}
在上面的例子中,block自己就是函数参数的一部分,在下一个例子中dispatch_apply函数中使用block,dispatch_apply的定义以下:


void dispatch_apply(size_t iterations,dispatch_queue_t queue,void(^block)(size_t));
这个函数将一个block提交到发送伫列(dispatch queue)中来执行多重的呼叫,只有当伫列中的工做都执行完成后才会回传,这个函数拥有三个变数,而最后一个参数就是block ,请参考下面的范例:


size_t count = 10;
dispatch_queue_t queue = dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT , 0 );
dispatch_apply(count, queue, ^(size_t i))
{
   printf ( "%u\n" , i);
}


        5.3 将Block看成方法的参数

在SDK中提供了许多使用block的方法,咱们能够像传递通常参数的方式来传递block,下面这个范例示范如何在一个阵列的前5笔资料中取出咱们想要的资料的索引值:

//全部的资料
NSArray *array = [ NSArray arrayWithObjects : @"A" , @"B" , @"C" , @"A" , @"B" , @"Z" , @"G" , @"are" , @" Q" ,nil ];
//咱们只要这个集合内的资料
NSSet *filterSet = [ NSSet setWithObjects : @"A" , @"B" , @"Z" , @"Q" , nil ];
BOOL (^test)(id obj,NSUInteger idx,BOOL *stop);
test = ^ ( id obj, NSUInteger idx, BOOL *stop)
{
   //只对前5笔资料作检查
   if (idx < 5 ) {
      if ([filterSet containsObject : obj]) {
         return YES ;   
      }
   }
   return NO ;
}
NSIndexSet *indexes = [array indexesOfObjectsPassingTest :test];
NSLog ( @"indexes: %@" , indexes); 
//结果:indexes: <NSIndexSet: 0x6101ff0>[number of indexes: 4 (in 2 ranges), indexes: (0-1 3-4)]
//前5笔资料中,有4笔符合条件,它们的索引值分别是0-1, 3-4
         5 .4  该避免的使用方式

在下面的例子中,block是for回圈的区域变数所以在使用上必须避免将区域的block指定给外面宣告的block:

//这是错误的范例,请勿在程式中使用这些语法!!
void dontDoThis() {
   void (^blockArray[3])(void); // 3 个block 的阵列
   for (int i = 0; i < 3; ++i) {
      blockArray[i] = ^{ printf("hello, %d\n", i); };
      // 注意: 这个block 定义仅在for 回圈有效。
   }
}

void dontDoThisEither() {
   void (^block)(void);
   int i = random():
   if (i > 1000) {
      block = ^{ printf("got i at: %d\n", i); };
   }
}
相关文章
相关标签/搜索