• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

编写高质量iOS代码

武飞扬头像
黑发要知勤学早
帮助2

编写高质量iOS代码的52个方法

方法1:OC和C 的区别

  • 前者方法的调用都是动态绑定的,和后者的多态时的调用机制一样;后者主要依靠编译时的代码,碰到多态时才会动态绑定。前者如果要提高代码的性能,只要提升实现的性能即可,因为你要使用的方法等直到运行时才会确定,编译期确定的一般都不是影响性能的最主要的部分,你只要将这部分的代码优化,提升性能,那么整个软件的性能就提升了;后者的话就必须重新编译,因为编译器确定的代码决定了大部分的性能,动态绑定的反而对性能影响较小。
  • oc将几乎所有的东西都包装成对象,让所有创建的东西几乎都在堆中去分配。只有一些对性能影响比较大的才会使用C语言结构体。

基本数据类型一定是存储在栈中吗?

  • 当然是错误的。当它声明在方法中的时候,的确是,会自动创建一个方法栈,将局部变量,即某些基本数据类型存储在栈中,方法结束后自动释放。
  • 但是如果它是声明在对象中的,那么它就是算是全局变量,不会自动释放,被定义在堆中。或者你可以人为未初始化之前,它是BSS段的一部分,初始化之后算作静态区间的一部分。使用对象中的基本类型数据时应该注意,该变量是否已经初始化,否则可能得到一个你不想要的随机数字。

方法2:在类的头文件中尽量少的引入其他头文件

注意:此处的头文件指的是 .h文件

  • 如果在头文件中有需要外部定义的类的文件,建议把头文件的引用延迟
  • 比如在a类中有一个b类的属性,此时只是定义了b类的属性,并不需要知道b类的实现细节。只用一个
@类名   //将此代码放在a类的声明之前即可
  • 然后在类的实现中需要知道具体的实现细节,才引入b类的.h文件

    这样做的原因:头文件时编译的时候就会确定,如果b类的头文件在a类中,编译的时候就需要编译两个头文件,但是可能有的内容根本就不会用到。这样增加了编译的时间。实现文件即@implement文件是在动态编译的时候才会使用到,所以在实现文件中再引入头文件可以减少编译的时间。

  • 这就叫向前声明。这也解决了头文件循环引用的问题。如果a的头文件中包含b的头文件,而b也包含a的。虽然使用import不会导致死循环,但是也会使某个文件无法正确编译。

但是有时候不能使用前向声明

  • 如果你的当前类a时b类的子类;或者你当前的类a是遵循b协议的,都必须在头文件中引入响应的头文件,不能使用前向声明。对于类的头文件,最主要是确定类对象的存储结构(比如有几个属性,有几个方法。对于非子类的,只要让编译器知道它是一个属性,留下一个指针的位置就可以实现类对象存储结构的确定了,所以可以使用前向声明。但是如果子类声明的时候不知道父类的具体实现,即有几个方法,几个属性,将无法确定类对象结构),对于前者,a是b的子类,那么必须知道实现细节(几个属性,几个方法);对于后者,因为协议本质上就是声明在其他文件中的本类的方法,必须知道协议的具体方法有几个才可以确定类对象的结构。
  • 最后将协议中的方法提炼成一个单独的文件,如果这些协议定义在在其他各个头文件中,在引入的时候就会将用不到的其他头文件加入了。
  • 对于代理委托协议则不然,最好将协议定义在 代理类中,便于使用,就不用单独写一个协议文件

总结:在引入头文件的时候,一定要仔细考虑文件的关系,避免性能损失,编译不使用的头文件,降低彼此的依赖程度,否则依赖程度过大将导致维护成本过高

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

字面量,即时省去创建对象,然后赋值等步骤,直接给定义的变量赋予特定的值。由于变量全是对象,因此内存分配在堆区中。使用字面量更加安全和简便。
能用字面量就用,可以减少很多不必要的麻烦

//字面量数值
NSString * name = @"zhangsan";
NSNumber *num = @2;
NSNumber *floatnum = @2.3f;
NSNumber *doublenum = @2.434324;
NSNumber *boolnum = @YES;
NSNumber *charnum = @'a';
int a = 19;
float b = 3.3f;
NSNumber *number = @(a * b);

//字面量数组
//不使用字面量语法时
NSArray *array = [NSArray arrayWithObjects:@"dog", @"cat"];
NSString *dog = [NSArray objectAtIndex:0];

//直接使用字面量
NSArray *num = @[@1, @3, @3];
NSArray *strs = @[@"dog", @"cat"];
NSString *dog = array[0];
//比较可知字面量使用数组更加简单
//但是注意字面量不能处理nil,但是非字面量法可以处理nil;但是前者效果更好,因为得不到预
//期结果就会报错,避免了后续调试的麻烦。如果你使用非字面量语法插入元素,遇到nil就会停
//止,nil后面的变量全部丢弃,这会导致不想要的结果,但是编译器并不会报错。
//。因此使用字面量更安全一些。

//字面量字典
NSDictionary *dic = [NSDictionarydictionaryWithObjectsAndKeys:@"Mat", @"first name", @"duo",@"last name", [NSNumber numberWithInt:28], @"age"];
//非字面量初始化顺序时:值、键,值、键。的顺序,容易迷糊,并且由年龄可知,只能是放入oc对象才可以,不能直接放几本数据,语法比较繁琐。
NSString *name = [dic objectForKey:@"first name"];
//取值也麻烦


NSDictionary *dic = @{@"first name":@"Mat", @"last name":@"duo", @"age":@28};
//字面量更加简洁,并且关于nil的处理和array时一样的,更加安全,插入初始值的顺序也和我们平时的思维吻合。
NSString * name = dic[@"first name"];
//字面量取值简单


//可变数组和字典的非字面量做法是
[mutarray replaceObjectAtIndex:1 WithObject:@"dog"];
[mutdic setObject:@"laoba" ForKey:@"firstname"]; 

//字面量做法
mutarray[1] = @"dog";
mutdic[@"firstname"] = @"laoba";

学新通

局限性

  • 首先,这种字面量的做法局限在不可变数组或者不可变字典中,如果要转化为可变量,可以使用如下的方法
NSMutableArray *mut = [@[@1, @2] mutbleCopy];
  • 其次,这些字面量做法仅限于系统内的对象类型,如果放入的oc对象是你自定义的,那么是不能使用字面量的方法的,仅可使用非字面量方法。

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

  • 使用define太多,只要引入了定义了define的文件,那个文件中的所有的和define相同的变量名都会变,容易产生意想不到的bug。变量名还可能发生冲突。
  • define往往不能非常清楚的描述好变量的含义

要尽可能使用const常量来代替。并搭配static来修饰常量。
前者保证常量无法被修改,后者保证该常量只在该文件中起作用,不会像define一样
命名的时候不同的类文件中的常量要加上类名前缀,以防出现重名
如果不打算公开这个常量,应该定义在实现文件中

如果不加static,编译器会给常量在外部也创建一个符号,如果其余文件中也声明了这个名称的变量就会报错。

如果确实需要声明一个在外部也要能使用的符号,就使用下列的定义方法:

//.h文件中
extern NSString *const EOCStringConstant;
//.m文件中
NSString *const EOCStringConstant = @"xxx";

注意:定义和初始化是分开的,并且上面的方法句子中,const只需要前面加星号即可保证这个常量的内容不变,因为在c语言的底层中,字符数组的值初始化后就不能改变了,在const前面加星号,表示地址不变,其中的内容也就无法改变了,其实这句和下面这句的效果一样:

NSString *const * EOCStringConstant = @"xxx";

结论:定义在某个文件中生效的常量和在全局中生效的定义方法现在都知道了,不要使用define。

第五条、用枚举表示状态、选项、状态码

枚举:同一个变量表示不同的值。默认的初始值从0开始,每往后一个变量自动 1。定义时,可以通过指定第一个状态变量的方式,改变每一个枚举变量代表的值。

一般是用来表示某个选项的选择,比如设备是否支持横屏等

定义的类型有两种

NS_ENUM和NS_OPTION
//前者不使用 | & 等运算符;后者使用

定义枚举的时候,可以前向枚举,最新的版本支持指定底层数据类型。

  • 枚举:用同一个变量表示多个值就是枚举。它有点类似于标志位,就是每次要运行某个特定的东西,或者使用某个硬件的时候,会查看这个标志位,看以什么状态去运行它。一般都是必须做选择的才会有枚举变量,也就是代码必须有的功能,但是功能比较多样就要用到枚举,并且一般都有默认值。

第六条、理解属性这一概念

和c 这类以编译器为主要类型的语言不同,oc使用的是动态编译的方式。
假设定义了如下的类

@interface  myObject : NSObject{
	NSString *_name;
	NSString *_company;
}

@end

@implement myObject

@end

这类语言有一个缺点,就是存在已经编译的版本和新改动的版本代码指向不一的问题
假设
在刚才的类中重新添加一属性,就会报错

@interface  myObject : NSObject{
	NSString *_father;
	NSString *_name;
	NSString *_company;
}

@end

@implement myObject

@end

因为访问属性的时候,往往是通过和这个对象起始地址的相对偏移量来进行的,改动后的father偏移量,其实和改动前的name是一样的,这会导致访问到错误的属性

在objectivec中就不会存在上述的问题,因为类的属性是在运行时才编码,不会出现不一致的情况。

使用@property来定义属性的时候,会自动生成getter和setter方法,当然,可以通过设置,不自动生成方法。

  • 一般是通过@dynamic来修饰。编译器看到之后,就不会自动生成存取方法和实例变量,编译器认为,一定能够在运行期间找到相关的方法。之所以这么做,是因为这些值可能不是实例变量,而是来自后端等其他端发来的数据。

  • 不应该在init方法中调用存取方法,即使用点语法对类的属性进行改变。原因解释如下:
    –>调用点语法,相当于遵守了内存管理的语义,比如,你的属性是copy修饰的,在使用点语法的时候,底层就帮你做好的拷贝的工作,但是如果你直接使用类对象中的属性,可能就无法保证内存管理的语义了。
    –>建议大家在读取的时候,直接读取对象的属性,直接读取那块区域的内存当然比较快;但是在设置属性的时候,set属性,遵守语义,即使用点语法
    ->但是由于没有使用点语法,键值观测(KVO)就不能作用了。这样是否会产生问题,就要看对象的具体行为了。

下面具体解释下KVO

–>简单说KVO解释 例子就是在你检测的可能变化的值变化的时候,主动通知你,然后你再在显示的内容上进行更新的一种机制。这个涉及到oc语言的msg特性。就是人为地在要发生改变的属性上设置观测者,当你观测的值发生变化的时候,就发送消息给观测者,然后观测者及时的更新相关数据。一般这个观测者时controller。

步骤分为一下几步:

  • 1.设计想要的显示样式
  • 2.给你的显示图像配置相应的Model
  • 3.给Model相应属性设置观测者
  • 4.观测到变化的时候要做的事写到相应方法中,改变显示中的属性值
  • 5.不需要观测的时候取消相应的观测,移除观察

对于copy属性的解释:
–>为什么要使用copy来修饰NSString?因为NSString是一个可变更的字符串对象,为了在他变更之前拷贝一份不可变字符数组,如果使用strong来修饰,那么由于string是可变的,在使用这个字符串的中间,可能源字符串就被改变了,得到一个不想要的结果。因此,类推所有的容易改变的数据类型,或者中途使用不想让其改变的属性都要使用copy来修饰。

- (void)setFoo:(id)foo{
	[foo retain];
	[_foo release];
	_foo = foo;
}
//必须是这个顺序,先保存新值,然后释放旧值,最后将新值对象赋予旧对象。
//过程:foo是临时的对象,先将它retain,然后计数为1。_foo对象release,然后将foo给_foo对象。这样foo临时对象就是2个引用了。然后超过括号范围,释放一个引用,返回引用计数为1 的对象。

第七条、在对象内部,应该尽可能直接访问实例对象

既保证性能又保证安全性的折中方法:在写入属性(实例变量)的时候,使用点语法,即使用setter方法,确保内存管理不会出问题。在读取属性的时候则直接读取相关内存,即 _属性名

直接访问的缺点:

  • 比如copy修饰的属性,如果不用点语法,直接访问实例变量,可能这个属性底层就未复制一份数据,类似于strong,那么后续如果源头的值被修改,会产生意想不到的错误。
  • 如果直接访问实例变量,无法触发‘键值观测’,如果该属性是你要观测的属性,会造成麻烦。
  • 因为直接访问相关内存,如果set方法有错将无法排查。

必须使用点语法的情况:

  • 懒加载的时候,如果直接访问变量,懒加载的变量会存在未初始化的问题。
  • 如果你要初始化的变量在超类中,不在当前子类,无法直接访问此实例变量,使用点语法。因为它会遵守内存管理规则

第八条、关于对象的等同性判断

NSString相同性判断

  • 使用的主要是两个方法:
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;

两个都是要自己实现的,前者根据具体的类自己实现,具体步骤:

  • 1.判断地址是否相同,相同则直接返回相同
  • 2.判断是否是同一个类,不是返回NO
  • 3.依次判断所有字段是否是相同的,不是就返回NO
  • 4.否则返回YES

取得hash值的方法:要尽可能少创建对象,少做运算,防止性能出现瓶颈

NSArray等同性判断

  • 如果是从同一个数据源来的array,判断里面的唯一标志位:主键是否相同,否则,就要自己写一个判断方法,类似于NSString的等同性判断,将每一个元素都判断一次,全部相等时array相等,否则不同。

注意容器中可变类的等同性。

  • 容器中保存对象是使用的拉链法,也就是对于一个对象的hash值是一定的,不能改变,否则它的位置就错了。如果你要进行等同性判断,在你将要判断的对象放入容器之后,就不要再改变其内容,否则会产生意想不到的后果。
    如果非要对可变类进行等同性判断,要注意期存在的风险。

第九条、以类族模式隐藏实现细节

  • 在oc中是没有抽象类这种说法的,但是确有这种类存在。一般来说,没有init方法的基类就是抽象类。
  • 创建类族的时候一般都要依靠枚举类型来实现

第十条、在已有类中使用关联对象存储定义的自定义数据

一般协议中是只有方法声明的,如果要在其中使用属性,直接定义属性是无法实现的,即使定义了也无法使用,这个时候就要使用到关联对象。使用关联对象的时候主要使用的是一下三个函数。

objc_setAssociatedObject
objc_getAssociatedObject
objc_removeAssociatedObject

如果你的方法在编译的时候就已经确定,那么就是静态编译,编译器在编译的时候就已经知道了方法;如果编译的时候方法不能够确定,比如要运行的方法是多个方法中的一个,需要在运行的时候才确定,这就是动态编译。

//注意,someObject是接收者,接收到这个消息调用,然后接收者才调用这个方法,返回相应的值。
id returnValue = [someObject messageName:para];
//当使用上述语句,其实在底层调用的c语言相关语句
void objc_msgSend(id self, SEL cmd, ...)
//之后的语句按照参数的出现顺序转化为
id returnValue=objc_msgSend(someObject, @selector(messageName:), para);

//在运行的时候,为了提高效率,每一个类中都会创建一个方法列表,把类中的所有的方法存储
//进去,也有一块缓存专门存储信息,以便快速反应,因此第二次使用同一方法,速度很快。如
//果在当前类找不到相应的方法,就会接着向父类寻找,直到找到相关的设置为止。一旦找到对应的函数就会跳转过去。

//方法列表使用键值对来存储

尾调用优化:就是当执行函数的最后是调用另一个函数的时候,编译器自动生成到另一函数的指令码,而不用将该函数的地址加入地址栈中。
注意:只有当调用的函数没有返回值的时候才可以使用尾调用优化

  • 优点:就是不会有这种函数的调用痕迹,并且减少地址栈的溢出概率。

第12条、理解消息转发机制

类如何处理不能解读的信息?

  • 1.询问接收者,是否可以动态的添加某个方法,以处理这些信息,即动态方法解析。
  • 2.询问接收者,是否有其他对象可以处理此信息,若有,转发给他,消息转发结束;否则则将所有的信息细节由运行期系统封装至NSInvocation对象中,再给接收者最后一次机会,设法处理此信息。

主要实现是通过以下的函数:

//当类无法处理收到的信息,会尝试自己解决,调用以下方法
  (BOOL)resolveInstanceMethod:(SEL)selector;
//注意上述方法是用来处理实例方法时使用。

//如何通过类自动实现getter和setter方法
//通过class_addMethod方法
class_addMethod(self, selector, (IMP)autoDictionarySetter, "v@:@");
class_addMethod(self, selector, (IMP)autoDictionaryGetter, "@@:");

//如果不能自己添加方法,就向接收者询问是否可以找到可以处理此方法的类。此时,应该调用
- (void)forwardingTargetForSelector:(SEL)selector;

//若还不能够处理,只有运行时系统封装相应的选择子和相应的数据到NSInvocation对象中,再给类最后一次解决机会。解决的时候,该类和其所有的基类都有处理的资格,直到NSObject类处理不了,抛出异常。
-  (void)forwardInvocation:(NSInvocation *)invocation;

关键在于,要在类中重写autoDictionary Getter和autoDictionarySetter

也就是说:编译器是不知道对象究竟能相应多少种选择子的,因为有些方法可以在运行期间,动态添加,

第十三条、用方法调配技术调试黑盒方法

  • 由于oc的程序在运行时才确定相应的方法,而只要在运行的时候改变相应的方法,所有的实例对象就都会因此改变行为。我们应该学会在程序运行时,更改方法,或者更改方法的实现,
  • 类的方法列表会吧选择子的名称映射到相关的方法实现之上,使动态消息派发系统能够通过表格找到相对应的方法。
  • 通过oc运行期系统提供的方法,就可以用来操作这个表格
  • 这个方法一般只是用来调试的,没什么用。

第十四条、理解“类对象”的用意

当你第一次编译到某个类的时候,类对象就载入进了内存;

每一个类对象结构体包含的内容:

  • isa指针,定义了对象所属类
  • super_class指针
  • name
  • version
  • info
  • 对象大小
  • 属性表指针
  • 方法表的指针
  • 协议表指针
    。。。

消息的接收者:类对象
类对象再告知实例对象,调用所知道的方法。

第十五条、用前缀避免命名空间冲突

  • 再创建类的时候,命名方法,变量,都尽量加上前缀
  • 避免调用的c语言函数与objectivec中的名字重名,因此自己实现的c语言的函数,一定要加上前缀,避免和系统中的已用名称重叠

第十六条、提供 全能初始化方法

  • 其实就是所有的初始化方法最终都要通过这个全能初始化方法来创建新的对象,其余的方法都是拐弯抹角地调用这个方法。因此只有这个方法是根上的,只有它才会存储相应的数据。改变初始化方法的时候,也只需要改变它一个就可以了。
  • 除此之外,他的子类也要维系相关的全能初始化产业链。
  • 如果某个类有多种创建的方式,那么全能初始化方法可能有多个
  • 如果子类和父类的初始化无法保持一致,总是重写覆盖掉父类的方法,并可以在无法避免冲突的时候,在重写方法时直接抛出异常。不过,在objectivec中,一般严重错误的时候才会抛出异常。

第十七条、实现description方法

  • 一般自定义的类,在调试的时候为了查看实例对象中的数据,都会自己实现该方法
  • 在使用断点进行调试,即调试器进行调试的时候,重写debugDescription方法。一般是通过代码来调试,po的时候的显示信息。

第十八条、尽量使用不可变变量

第十九条、命名的时候多使用and 、in、with等连词使方法功能简单明了

第二十条、私有方法和共有方法

  • 共有方法由于是暴露出来给别人使用的,一般不会修改;但是私有方法只是实现文件自己内部使用,修改也无妨。为了更快的更改相关的私有方法,可以给私有方法添加前缀以更快区分于共有方法。
  • 一般通过以下的方式区别
//通过在私有方法前面加上p_, 即:
p_私有方法

在更改私有方法的时候,可以通过搜索前缀的方式快速确定私有方法的名称

第二十一条、oc中的错误

  • 首先除非发生足使程序崩溃的严重错误,否则一般是不会抛出错误的
  • 错误的信息是存储在NSError对象中的。

第二十二条、理解NSCoping协议

在使用对象的过程中,经常使用到copy方法,他的底层是copyWithZone方法,拷贝出来的对象一般是不可变对象。

要使自己的类支持copy方法,就必须要实现NSCopying协议的方法:

- (id)copyWithZone:(NSZone *) myzone;

实现该方法的时候,其中的zone参数不用担心,现在的zone参数只有一个默认区。

在其中的有的类直接将实现的 交给了全能初始化方法,在创建拷贝体的同时,将这个类进行了初始化,但是有的类拷贝后马上,就会使用新的数值覆盖,可能导致 初始化浪费性能。

这个copy方法拷贝出来的是不可变对象,如果要使用可变对象,就要实现另一个协议NSMutableCopying协议的copy方法。

- (id)mutableCopyWithZone:(NSZone *)zone;

它的实现和copy基本一致,但是创建的是可变数组。
下列方法调用的结果是确定的。

[NSMutableArray copy] ==> NSArray
[NSArray mutableCopy] ==>NSMutableArray
  • 一般对于Foundation框架下的collection中的拷贝都是浅拷贝。也就是只拷贝对象本身,但是其中的数据不拷贝。也就是说,拷贝后的指针指向和之前的对象相同的内容。
  • 如果要深拷贝的copy方法需要自己实现,

协议与分类

第二十三条、通过委托与数据源协议实现对象间的通信

现提出一个问题:在UITableViewController中,数据源和代理是如何协作,实现信息的展示的?

  • 在设计一个类的时候,如果要实现数据与业务逻辑解耦,就需要使用到委托模式。数据相关的类只需要拿到数据即可,业务逻辑只需要关心在什么情况使用哪些数据,对业务作正确的判断接口。
  • 在数据源载入数据后,告知controller现在需要显示什么数据,业务逻辑是通过外来的类遵守相关协议,然后实现协议中的指定方法来完成。
  • 在UITableViewController中,在controller中通过实现数据源方法,拿到相关的数据,然后通过Model类中的delegate属性,即controller来调用显示数据源中的相关数据。大逻辑清楚了,但是调用的时机如何掌握,这些小逻辑还不清楚。
  •  

1.UITableViewController底层是UIViewController,它是遵守了数据源方法和代理方法的
2.contrller作为一个中介,当数据源拿到了相关数据的时候,会给controller发消息。controller在通过调用代理方法的方式显示相关的数据

命名为delegate的协议,就是你想做该代理索要实现的方法,是硬性条件。
因此,controller了才能够连接Model和显示View,总是view是委托人,controller是被委托人,因为controller总是可以较为方便的拿到数据,拿数据途中,可以使用model作为容器。然后想要显示某个View,就定义一个delegate属性。但是一般都是在controller中去写相关的代码,所以也就有了在controller中定义View,然后将该controller作为View的delegate属性。

所以这也就是保留环的形成原因。
假设在某个controller中定义了一个View属性,然后为了在view中显示某些东西,就要在View中定义一个delegate属性,将该controller设置为该属性。这样,当controller下载完或者加载完相关数据之后,才可以及时的提醒View。
在这里存在一个互相引用,因此在View中定义一个delegate属性的时候才会设置为weak,这样可以防止保留环的形成。

为了不浪费性能,就要在数据加载好后马上通知view,然后显示相关的图像。

这样设计之后,数据的加载总是在controller中进行,而是否显示的业务相关的逻辑则全部在View的类中,甚至可以放一部分在装数据的容器Model里面。

其实就是互相持有对方

块与GCD

第37条、理解块的概念

  • 块在oc中是一个变量,并且可以存储代码段。有点类似于c语言中的函数指针。注意函数指针只是块的一个参数。
  • 注意,块的特点:它所声明的范围内,即声明它的那个函数中所有的变量都是它可以捕获并且使用的。
int additional = 5;
int (^myblock)(int a, int b)=int ^ (int a, int b){
	return a   b   additional;
}

使用的话,无论对全局变量还是静态变量都是一样的。但是如果要改变抓取的值得时候是不同的。此时,如果这个additional是全局变量或者是静态变量,可以直接改变;但如果是定义的局部变量的话,改变该值的时候,还要对addtional变量做一个调整。将该变量前加上__block转换

经过实验证明:所有的被block抓取的值都可以直接使用

//
//  ViewController.m
//  test
//
//  Created by 史宣龙 on 2022/3/27.
//

#import "ViewController.h"

typedef int (^myblock)(int a, int b);

@interface ViewController ()

@property (nonatomic, strong)myblock testblock;

@end

@implementation ViewController

- (void)testBlock{
    int a = self.testblock(5, 5);
    NSLog(@"%d", a);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    int additional = 5;
    myblock block = ^(int a ,int b){
        return a   b   additional;
    };
    self.testblock = block;
    /**
            主要是看能不能把A地的局部变量,带到B地里面并且参与计算
     */
    int res = block(1 , 2);
    NSLog(@"%d", res);
    
    [self testBlock];   
}
@end
学新通
  • 默认情况下,为块捕获的变量是不能在块里面修改的,除非在变量前面加上“__block”
  • 如果块捕获的变量是对象,那么变量会自动保留它,即会retain一下。当块被释放的时候,该对象也会release一下。块本身可以视为对象。块也有引用计数,当计数为0,则回收块的空间。
  • 如果将块定义在oc的类的实例方法中,即“-”号打头的方法中,除了可以访问类的所有实例变量之外,还可以使用self变量。并且块总是能够修改实例变量,且声明时无需添加“__block”变量。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgbkiea
系列文章
更多 icon
同类精品
更多 icon
继续加载