IOS:个人笔记|objc__block知识点

     自己也是初学者,从其它人的博客,以及教学视频总结来的,如果有错误,请大家指正。希望能够帮上屏幕的你。

    觉得不错的点个赞吧,转载请注明。

==========================================================================================================

    首先来看C语言中定义一个函数,然后去调用的例子

     void  fun(int count);

     fun(10);

    如果使用 

    void (*funPointer)(int)=&fun;

    (*funPointer)(10);

    或者

    void (*funPointer)(int);

    funPointer=fun;

    funPointer(10);

 先用指针指向这个函数,然后再传值,这样看起来就像是没经过函数名称。

=========================================================================================================

 关于block,block其实是一种特殊的数据类型,用来存储一段代码。应用场景比较广泛,例如集合遍历,网络回调,多线程等等

  block和函数一样,可以有形参有返回值,有形参没返回值,没形参有返回值,没形参没返回值。

 

 block的语法

 与一般函数类似,分为声明和具体代码的实现。

声明格式:  返回值 (^xxblock)(形参类型);//block名称一般都以block结尾,多个形参时之间用逗号隔开,形参可以只写参数类型

实现格式:  xxblock=^(返回值类型)(参数列表){……};//没有参数时,实现^后面的()可以去掉,声明的后面的()不能去掉,返回值类型一般不写。

调用      :   xxblock();

上面是声明和实现分开写,常用的写法格式是定义的时候初始化:返回值 (^xxblock)(形参类型)=^(形参类型){……};

下面给出一些具体的例子

复制代码
 1 //=====无参数无返回============
 2     //先声明后实现
 3     void (^funBlock)();
 4     funBlock=^{
 5         NSLog(@"无参无返回");
 6     };
 7     funBlock();
 8     //声明时初始化
 9     void (^fun2Block)()=^{
10         NSLog(@"无参无返回");
11     };
12     fun2Block();
13     
14 //======无参数有返回==========
15     int (^fun3Block)();
16     fun3Block=^{
17         return 1;
18     };
19     NSLog(@"无参数有返回值,返回结果:%d",fun3Block());
20     int (^fun4Block)()=^{return 1;};
21     NSLog(@"无参数有返回值,返回结果:%d",fun4Block());
22 //=====有参数无返回============
23     void (^fun5Block)(int,int);
24     fun5Block=^(int n1,int n2){
25         NSLog(@"n1+n2结果是%d",n1+n2);
26     };
27     fun5Block(10,15);
28     void (^fun6Block)(int,int)=^(int n1,int n2)
29     {
30         NSLog(@"n1+n2结果是%d",n1+n2);
31     };
32     fun6Block(10,10);
33 //======有参数有返回============
34     int (^fun7Block)(int,int);
35     fun7Block=^(int n1,int n2){
36         return n1+n2;
37     };
38     NSLog(@"调用fun7block结果是%d",fun7Block(20,20));
39     int (^fun8Block)(int ,int )=^(int n1,int n2){
40         return n1+n2;
41     };
42     NSLog(@"调用fun8block结果是%d",fun8Block(200,20));
复制代码

关键字typedef

关于typedef,从名字上来说是类型定义,其实它是为了帮助我们重新定义一种类型,解决有相同代码时的重复与累赘,先写两个用指针指向函数的例子

 

复制代码
 1 int  test1(int n1 ,int n2)
 2  {
 3      return  n1+n2;
 4 };
 5 int  test2(int n1,int n2)
 6 {
 7     return  2*(n1+n2);
 8 }
 9 int main(int argc, const char * argv[]) {
10     int (*test1Pointer)(int,int);
11     test1Pointer=test1;
12     NSLog(@"==test1Pointer===%d", test1Pointer(10,10));
13     
14     int (*test2Pointer)(int,int);
15     test2Pointer=test2;
16     NSLog(@"===test2Pointer==%d",test2Pointer(10,10));
17     return 0;
18 }
复制代码

 

我们会发现其中有些重复的代码,似乎是可以避免的,于是我们就用到了typedef。

复制代码
 1 int  test1(int n1 ,int n2)
 2  {
 3      return  n1+n2;
 4 };
 5 int  test2(int n1,int n2)
 6 {
 7     return  2*(n1+n2);
 8 }
 9 /*
10  用typedef声明一个有返回值,有两个参数的指针类型
11  用这个类型声明两个指针,分别指向test1,test2,
12  这样我们就不用去写两个指针的声明了。
13   */
14 typedef int (*newtest)(int,int);
15 int main(int argc, const char * argv[]) {
16 //    int (*test1Pointer)(int,int);
17 //    test1Pointer=test1;
18     newtest test1Pointer=test1;
19     NSLog(@"==test1Pointer===%d", test1Pointer(10,10));
20     
21 //    int (*test2Pointer)(int,int);
22 //    test2Pointer=test2;
23     newtest test2Pointer=test2;
24     NSLog(@"===test2Pointer==%d",test2Pointer(10,10));
25     return 0;
26 }
复制代码

 

我们将typedef运用到block中,下面给出两个例子。

复制代码
 1 typedef int (^newtestBlock)(int,int);//声明一个有返回值,有形参的block类型,在main方法中,用这个block类型声明两个block变量
 2 int main(int argc, const char * argv[]) {
 3 //    int (^fun7Block)(int,int);
 4      newtestBlock  fun7Block=^(int n1,int n2){
 5            return n1+n2;
 6        };
 7     NSLog(@"调用fun7block结果是%d",fun7Block(20,20));
 8 //    int (^fun8Block)(int,int);
 9      newtestBlock fun8Block=^(int n1,int n2)
10     {
11         return 2*(n1+n2);
12     };
13     NSLog(@"调用fun8block结果是%d",fun8Block(20,20));
14  
15     return 0;
16 }
复制代码

 

需要注意的是,我们定义了block类型,再去声明两个block变量,那么这两个block变量的返回值类型和参数类型都是定义好了的。我们在写例子演示的时候是先写几个block,再进行抽取式的行为,因此需要看清返回值类型,和参数类型。

block的一些用法

 

1:例如block作为属性在A类中的.h文件声明,调用是在A类的.m文件中,但是block的实现是在B类的.m文件中。

      这个是最近碰到的一个使用的例子,需求是获取当前点的颜色,用到的是网上的一个框架,框架中获取当前颜色的方法,然后调用自己类的block,传入参数color对象,但是block的实现是在另一个类中,由于block的实现中,有些发送蓝牙命令以及跟view上的控件有关的代码,所以如果把block的实现写在A类中,其实是不太合适的。

2:在A类中作为属性声明,实现也在本类。B类中调用。

3:block作为方法的参数使用

 A类中写一个将block作为参数的方法,B类中先定义初始化一个block(),然后调用A类的方法,将B类的block传进去。或者在B类中直接调用A类的方法, 然后写一个block的实现。A类收到后,再做对应的操作。

 

4:作为方法的返回值使用

 

    A类中写一个方法,方法的返回值是block对象,方法内部返回的也是一个block对象,然后B类中调用这个方法,例如下面这个例子

  

复制代码
 1 //.h文件及.m文件
 2 @interface useA : NSObject
 3 -(void(^)(int))run;
 4 @end
 5 @implementation useA
 6 -(void(^)(int))run
 7 {
 8     return ^(int a){NSLog(@"传入的值是%d",a);};
 9 }
10 @end
11 //main
12   useA *usea=[[useA alloc]init];
13   usea.run(5);
复制代码

 

block的注意事项

1:block中可以访问外部变量

   

1 int main(int argc, const char * argv[]) {
2     int a=10;
3     void (^testBlock)()=^{NSLog(@"==========%d",a);};
4     testBlock();
5     return 0;
6 }

2:block中可以建立外部同名的变量,访问的时候就近原则。

int main(int argc, const char * argv[]) {
      int a=10;
      void (^testBlock)()=^{
        int a=20;
        NSLog(@"==========%d",a);};
    testBlock();
    return 0;
}

3:block中默认不能修改外部变量的值。block如果访问了外部变量,会将外部变量的值拷贝一份到堆内存(可通过打印地址验证)。

     接着如果在block后修改变量的值,block立马的值拷贝的还是初始的,并不会因为外部之后修改了而发生变化

复制代码
 1 int main(int argc, const char * argv[]) {
 2          int a=10;
 3         NSLog(@"======%i",&a);
 4          void (^testBlock)()=^{
 5              NSLog(@"a====%d",a);
 6         NSLog(@"======%i",&a);};
 7     a=20;
 8     testBlock();
 9     return 0;
10 }
复制代码

 

 

4:如果想在block中修改外部变量的值,必须在外界变量的前面加上__block修饰,这种方式会改变外部的值。

     将该文件转成c++文件,会发现没有__block的时候,对变量操作是值传递,加了后是引用传递,因此会修改值。

1 //没有使用block,仅在block中访问了外部的变量
2 int main(int argc, const char * argv[]) {
3          int a=10;
4          void (^testBlock)()=^{
5              NSLog(@"======%i",a);};
6     testBlock();
7     return 0;
8 }

 

 

在终端中切换到当前的文件所在的路径,例如我是在main文件写的,那么切换到这个路径即可,然后使用cc -rewrite-objc  main.m,将文件转为c++文件,在文件夹中发现多了个文件,打开,找到main方法所在的地方,可以看到对a的操作是值传递

 

复制代码
1 int main(int argc, const char * argv[]) {
2          int a=10;
3  
4          void (*testBlock)()=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
5  
6     ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
7     return 0;
8 }


复制代码

 

用同样的方式,在用了__block的情况下,打开main.cpp文件,可以看见里面的有关main方法的代码

 对a的操作是引用传递

复制代码
 1 int main(int argc, const char * argv[]) {
 2          __block int a=10;
 3  
 4          void (^testBlock)()=^{
 5              a=20;
 6              NSLog(@"======%i",a);};
 7     
 8     testBlock();
 9     return 0;
10 }
复制代码
int main(int argc, const char * argv[]) {
         __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
 
         void (*testBlock)()=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
 
    ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
    return 0;
}

 

 

5:block默认情况下,存储在栈中,如果对block进行copy操作,blok会转移到堆中。

     如果block在栈中,block中访问了外部对象,那么不会对对象进行retain操作

    如果在堆中,block访问了外部对象,会进行retain操作,计数器加一。进行这个实验需要先把自动管理计数改为NO

复制代码
int main(int argc, const char * argv[]) {
    useA *a=[[useA alloc]init];
    
    NSLog(@"========%lu", [a retainCount]);
         void (^testBlock)()=^{
             
             NSLog(@"======%@",a);
    NSLog(@"block=====%lu",[a retainCount]);
         };
    testBlock();
    return 0;
}
复制代码

 

 下面我们来进行copy操作,再看看结果

复制代码
int main(int argc, const char * argv[]) {
    useA *a=[[useA alloc]init];
    
    NSLog(@"========%lu", [a retainCount]);
         void (^testBlock)()=^{
             
             NSLog(@"======%@",a);
    NSLog(@"block=====%lu",[a retainCount]);
         };
    Block_copy(testBlock);
    testBlock();
    return 0;
}
复制代码

 

posted @   DDD-SagerKing  阅读(210)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示
主题色彩