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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端