iOS相关知识点整理和分析
在看下面的知识点之前,看一下这几篇文章:
1.代码风格
先看一段代码:
typedef enum{ UserSex_Man, UserSex_Woman }UserSex; @interface UserModel :NSObject @property(nonatomic, strong) NSString *name; @property (assign, nonatomic) int age; @property (nonatomic, assign) UserSex sex; -(id)initUserModelWithUserName: (NSString*)name withAge:(int)age; -(void)doLogIn; @end
上面代码存在什么问题呢?
【解说】:
- 枚举类型:建议使用
NS_ENUM
和NS_OPTIONS
宏来定义枚举类型; - 属性类型:应避免使用基本类型,建议使用 Foundation 数据类型;
- 添加前缀:如果工程大,最好是给模块也加一个前缀;工程稍微小点的时候,给工程加一个前缀;
- 开发模式:doLogIn从名字上看应该是登录,这属于业务逻辑,和上面类的定义不一样,该类一看就是model;在MVC模式下,doLogIn应该在C里面;在MVVM模式下,doLogIn应该在VM里面;
- 方法命名:do和Log是两个动词,动词多余,直接用logIn即可;方法命名中尽量避免用with或and来连接两个参数;
- 初始化方法:没有初始化sex属性,应该提供一个designated初始化方法;
- 属性关键字:提供初始化方法之后,属性应该设置成readonly;
- 代码规范:应该在一些位置加空格和换行,提高代码的规范性。
修改之后的完整代码如下:
typedef NS_ENUM(NSInteger, GofSex) { GofSexMan, GofSexWoman }; @interface GofUser : NSObject<NSCopying> @property (nonatomic, readonly, copy) NSString *name; @property (nonatomic, readonly, assign) NSUInteger age; @property (nonatomic, readonly, assign) GofSex sex; - (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(GofSex)sex; + (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(GofSex)sex; @end
2.属性关键字
在讲属性关键字之前,我们先来看看都有哪几类关键字:
- 原子性:nonatomic和atomic(默认值)。在默认情况下,由编译器合成的方法会通过锁定机制确保其原子性(atomicity)。如果属性具备 nonatomic 特质,则不使用自旋锁;
- 读/写权限:
readwrite(读写,默认值)
、readonly (只读);
- 内存管理:
assign(基本类型的默认值)
、strong(OC对象的默认值)
、weak
、unsafe_unretained
、copy、retain;
- 方法名:
getter=<name>
、setter=<name>。例如:
@property (nonatomic, getter=isOn) BOOL on;
- 其他:
nonnull
、null_resettable、
nullable。
下面主要针对内存管理相关的做一个讲解。
2.1MRC
官方文档:https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html
MRC的内存管理模式下,编译器会为带有不同关键字的属性自动生成对应的get和set方法。并且苹果十分建议在可能的情况下通过get和set方法(包含对应的点语法)来操作属性,而不是操纵它对应的实例变量。
如果需要对某些属性自定义get或set方法,则需要程序员注意这个属性的关键字。写在属性旁边的关键字,并不是真正控制着这个属性的行为,它只是对编译器自动生成的get和set方法提供了指导。
MRC下,方法retain、alloc、new 都会使引用计数加1;对应的,当使用完相应的变量要调用 release 来使引用计数减1,当调用调用release后,如果引用计数为0,系统会释放相应的内存。
因此MRC时代,最重要的就是维护好每一对的 retain和release。简单的汇总为一句话:
如果需要持有一个对象,那么对其发送retain;如果之后不再使用该对象,那么需要对其发送release(或者autorealse)。 每一次对retain、alloc或者new的调用,需要对应一次release或autorealse调用。
2.1.1assign
assign是一个属性的默认关键字,无论这个属性代表一个简单数据类型,还是一个指向对象的指针。
assign主要应用于代表简单数据类型的属性,比如int,float等。
如果这个用assign关键字修饰的属性代表一个指向对象的指针,那么当这个指针指向某个对象时,这个对象的引用计数不应该被改变。也就是说,用assign关键字修饰的属性,不应该持有一个对象。
因为这个属性不持有对象,所以它所指向的对象很可能已经在别处被释放了。这时它就有可能成为一枚悬垂指针,访问它指向的内存地址时,可能会发生意想不到的状况。
2.1.2retain
retain关键字的作用是将内存数据的所有权赋给另一指针变量,引用数加1。
retain不能修饰用来代表简单数据类型的属性,否则编译器会报错。
2.1.3copy
如果一个属性被copy关键字修饰,那么赋值到这个property的对象,应该是原有对象的一份拷贝。只有实现了NSCopying协议,并且实现了其中的copyWithZone:
方法的对象才能被拷贝。
和retain关键字一样,copy关键字也不能修饰用来代表简单数据类型的属性,否则编译器会报错。
2.1.4unsafe_unretained
同assign。
2.1.5strong
同retain。
2.1.6weak
MRC下不能使用weak关键字。
2.1.7示例
@interface GofMRCViewController () { NSMutableString *obj; } @property (nonatomic, assign) id objectAssign; @property (nonatomic, retain) id objectRetain; @property (nonatomic, copy) id objectCopy; @property (nonatomic, unsafe_unretained) id objectUnsafe; @property (nonatomic, strong) id objectStrong; //编译报错 //@property (nonatomic, weak) id objectWeak; @end @implementation GofMRCViewController - (void)viewDidLoad { [super viewDidLoad]; obj = [[NSMutableString alloc] initWithString:@"Hello, Gof"]; NSLog(@"obj: %p retainCount:%ld", obj, (unsigned long)[obj retainCount]); self.objectStrong = obj; NSLog(@"objectStrong: %p retainCount:%lu", self.objectStrong, (unsigned long)[self.objectStrong retainCount]); self.objectAssign = obj; NSLog(@"objectAssign: %p retainCount:%lu", self.objectAssign, (unsigned long)[obj retainCount]); self.objectRetain = obj; NSLog(@"objectRetain: %p retainCount:%lu", self.objectRetain, (unsigned long)[obj retainCount]); self.objectUnsafe = obj; NSLog(@"objectUnsafe: %p retainCount:%lu", self.objectUnsafe, (unsigned long)[obj retainCount]); self.objectCopy = obj; NSLog(@"objectCopy: %p retainCount:%lu", self.objectCopy, (unsigned long)[obj retainCount]); [_objectStrong release]; [_objectRetain release]; [obj release]; obj = nil; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; NSLog(@"viewWillAppear obj: %@", obj); } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; NSLog(@"viewDidAppear obj: %@", obj); } - (void)dealloc { [_objectCopy release]; _objectCopy = nil; [super dealloc]; } @end //打印结果 /* obj: 0x7fd09f620c80 retainCount:1 objectStrong: 0x7fd09f620c80 retainCount:2 objectAssign: 0x7fd09f620c80 retainCount:2 objectRetain: 0x7fd09f620c80 retainCount:3 objectUnsafe: 0x7fd09f620c80 retainCount:3 objectCopy: 0x7fd09f701f80 retainCount:3 viewWillAppear obj: (null) viewDidAppear obj: (null) */
2.2ARC
Automatic Reference Counting,自动引用计数,即ARC,可以说是WWDC2011和iOS5所引入的最大的变革和最激动人心的变化。ARC是新的LLVM 3.0编译器的一项特性,使用ARC,可以说一举解决了广大iOS开发者所憎恨的手动内存管理的麻烦。
在工程中使用ARC非常简单:只需要像往常那样编写代码,只不过永远不写retain
、release
和autorelease
三个关键字就好,这是ARC的基本原则。当ARC开启时,编译器将自动在代码合适的地方插入retain
、release
和autorelease
,而作为开发者,完全不需要担心编译器会做错(除非开发者自己错用ARC了)。
ARC是Objective-C编译器的特性,而不是运行时特性或者垃圾回收机制,ARC所做的只不过是在代码编译时为你自动在合适的位置插入release
或autorelease
,就如同之前MRC时你所做的那样。因此,至少在效率上ARC机制是不会比MRC弱的,而因为可以在最合适的地方完成引用计数的维护,以及部分优化,使用ARC甚至能比MRC取得更高的运行效率。
2.2.1strong
在ARC内存管理模式下,strong是一个代表对象类型的属性的默认关键字,并且它不能修饰用来代表简单数据类型的属性。编译器在合成实例变量时,将使用__strong
修饰符。
2.2.2weak
weak也不能修饰用来代表简单数据类型的属性。编译器将为weak修饰的属性生成带__weak
所有权修饰符的实例变量。
2.2.3copy
copy也不能修饰用来代表简单数据类型的属性。编译器将为copy修饰的属性生成带__strong
所有权修饰符的实例变量。编译器自动合成的setter方法会调用对象的copyWithZone:
方法。虽然第三方程序员可以自定义setter方法,但是为了程序的可读性,也应该在其中执行拷贝的逻辑。
2.2.4retain
同strong。
2.2.5unsafe_unretained
编译器将为unsafe_unretained修饰的属性生成带__unsafe_unretained
所有权修饰符的实例变量。与weak和strong不同的是,unsafe_unretained也可以修饰代表简单数据类型的属性。
2.2.6assign
assign在ARC内存管理模式下,是代表简单数据类型的属性的默认关键字。
2.3常见问题
2.3.1什么情况使用 weak 关键字,相比 assign 有什么不同?
在下面这两种情况下使用weak关键字:
-
在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如: delegate 代理属性
-
自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong。
它和assign的不同点:
-
weak
该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(置为nil)。 而assign
的“设置方法”只会执行针对“纯量类型” (例如CGFloat 或 NSlnteger 等)的简单赋值操作。 -
assign 可以用非 OC 对象,而 weak 必须用于 OC 对象;
【总结】:weak
比 assign
多了一个功能就是当属性所指向的对象消失的时候(也就是内存引用计数为0)会自动赋值为 nil
,这样再向 weak
修饰的属性发送消息就不会导致野指针操作crash。
示例代码如下:
@interface GofAssignVSWeakViewController () @property (nonatomic, strong) id strongObject; @property (nonatomic, weak) id weakObject; @property (nonatomic, assign) id assignObject; @end @implementation GofAssignVSWeakViewController - (void)viewDidLoad { [super viewDidLoad]; self.strongObject = [NSDate date]; self.weakObject = self.strongObject; self.assignObject = self.strongObject; self.strongObject = nil; NSLog(@"weak属性 : %@", self.weakObject); NSLog(@"assign属性 : %@", self.assignObject); //这一行可能会崩溃 } @end
【说明】:上面注释那行有可能会崩溃,这是因为当 assign
指针所指向的内存被释放(引用计数为0),但指针不会自动赋值为 nil
,这样再引用 self.
assignObject
就会导致野指针操作,如果这个操作发生时内存还没有改变内容,依旧可以输出正确的结果,而如果发生时内存内容改变了,就会崩溃。
通过上面示例的分析,我们的结论是:在 ARC
模式下编程时,指针变量一定要用 weak
修饰,只有基本数据类型和结构体需要用 assign ,例如 delegate
,一定要用 weak
修饰。
2.3.2怎么使用copy关键字?
用途一:NSString、NSArray、NSDictionary 等等经常使用copy关键字,是因为它们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary;
先看示例:
@interface GofARCViewController () @property (nonatomic, copy) id objectCopy; @property (nonatomic, strong) id objectStrong; @end @implementation GofARCViewController - (void)viewDidLoad { [super viewDidLoad]; NSMutableString *obj = [[NSMutableString alloc] initWithString:@"Hello, Gof"]; // NSMutableArray *obj = [[NSMutableArray alloc] initWithArray:@[@"aa"]]; // NSMutableDictionary *obj = [[NSMutableDictionary alloc] initWithDictionary:@{@"1": @"a"}]; NSLog(@"obj : %p", obj); self.objectStrong = obj; self.objectCopy = obj; [obj appendString:@" Lee"]; // [obj addObject:@"bb"]; // [obj setObject:@"b" forKey:@"2"]; NSLog(@"objectStrong %p : %@", self.objectStrong, self.objectStrong); NSLog(@"objectCopy %p : %@", self.objectCopy, self.objectCopy); } @end //打印结果: /* obj : 0x7fbbde035e70 objectStrong 0x7fbbde035e70 : Hello, Gof Lee objectCopy 0x7fbbde1773e0 : Hello, Gof */
【解说】:当属性类型为 NSString 时,经常用copy关键字来保护其封装性,因为传递给设置方法的新值有可能指向一个 NSMutableString 类的实例。这个类是 NSString 的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。
用途二:block使用 copy 关键字。原因见官方文档。
block 使用 copy 是从 MRC 遗留下来的“传统”。在 MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区。在 ARC 中写不写都行:对于 block 使用 copy 还是 strong 效果是一样的,但写上 copy 也无伤大雅,还能时刻提醒我们:编译器自动对 block 进行了 copy 操作。如果不写 copy ,该类的调用者有可能会忘记或者根本不知道“编译器会自动对 block 进行了 copy 操作”,他们有可能会在调用之前自行拷贝属性值,这种操作多余而低效。
2.3.3NSMutableString、NSMutableArray、NSMutableDictionary可以使用copy关键字吗?
先看示例:
@interface GofARCViewController () @property (nonatomic, copy) NSMutableArray *array; @property (nonatomic, copy) NSMutableString *string; @property (nonatomic, copy) NSMutableDictionary *dictionary; @end @implementation GofARCViewController - (void)viewDidLoad { [super viewDidLoad]; self.array = [[NSMutableArray alloc] initWithArray:@[@"11"]]; NSLog(@"array是否NSMutableArray类型:%d", [self.array isKindOfClass:[NSMutableArray class]]); //[self.array addObject:@"22"]; //会崩溃 self.string = [[NSMutableString alloc] initWithString:@"Lee"]; NSLog(@"string是否NSMutableString类型:%d", [self.string isKindOfClass:[NSMutableString class]]); //[self.string appendString:@" Gof"]; //会崩溃 self.dictionary = [[NSMutableDictionary alloc] initWithDictionary:@{@"1" : @"a"}]; NSLog(@"dictionary是否NSMutableDictionary类型:%d", [self.dictionary isKindOfClass:[NSMutableDictionary class]]); //[self.dictionary setObject:@"b" forKey:@"2"]; //会崩溃 }
【解说】:copy 是复制一个不可变的对象。因此不能使用copy关键字。
2.3.4属性的本质是什么?
“属性” 作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。 Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。总结的公式是:
@property = ivar + getter + setter;
属性在runtime中是用objc_property_t类型定义的,详见Runtime之成员变量&属性&关联对象。
我们对下面类使用指令“clang -rewrite-objc GofUser.m”进行编译:
@interface GofUser () @property (nonatomic, copy) NSString *gofName; @end @implementation GofUser @end
编译的结果摘要如下:
//属性的“偏移量” (offset),这个偏移量是“硬编码” (hardcode),表示该变量距离存放对象的内存区域的起始地址有多远 extern "C" unsigned long int OBJC_IVAR_$_GofUser$_gofName __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct GofUser, _gofName); //setter 与 getter 方法对应的实现函数 static NSString * _I_GofUser_gofName(GofUser * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_GofUser$_gofName)); } extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool); static void _I_GofUser_setGofName_(GofUser * self, SEL _cmd, NSString *gofName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct GofUser, _gofName), (id)gofName, 0, 1); } //方法列表 static struct /*_method_list_t*/ { unsigned int entsize; // sizeof(struct _objc_method) unsigned int method_count; struct _objc_method method_list[2]; } _OBJC_$_INSTANCE_METHODS_GofUser __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_objc_method), 2, {{(struct objc_selector *)"gofName", "@16@0:8", (void *)_I_GofUser_gofName}, {(struct objc_selector *)"setGofName:", "v24@0:8@16", (void *)_I_GofUser_setGofName_}} }; //成员变量列表 static struct /*_ivar_list_t*/ { unsigned int entsize; // sizeof(struct _prop_t) unsigned int count; struct _ivar_t ivar_list[1]; } _OBJC_$_INSTANCE_VARIABLES_GofUser __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_ivar_t), 1, {{(unsigned long int *)&OBJC_IVAR_$_GofUser$_gofName, "_gofName", "@\"NSString\"", 3, 8}} };
也就是说我们每次在增加一个属性,系统都会在 ivar_list
中添加一个成员变量的描述,在 method_list
中增加 setter 与 getter 方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出 setter 与 getter 方法对应的实现,在 setter 方法中从偏移量的位置开始赋值,在 getter 方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转。
2.3.5协议和分类中怎样使用属性?
在 protocol 中使用 属性 只会生成 setter 和 getter 方法声明,我们在协议中使用属性的目的,是希望遵守协议的对象能实现该属性;
category 使用属性也是只会生成 setter 和 getter 方法的声明,如果我们真的需要给 category 增加属性的实现,需要借助于运行时的两个函数:objc_setAssociatedObject和objc_getAssociatedObject,实现详见Runtime之成员变量&属性&关联对象。
2.3.6@synthesize和@dynamic分别有什么作用?
属性有两个对应的词,一个是 @synthesize,一个是 @dynamic。如果 @synthesize和 @dynamic都没写,那么默认的就是@syntheszie var = _var。
@synthesize 的语义是如果没有手动实现 setter 方法和 getter 方法,那么编译器会自动加上这两个方法。
@dynamic 会告诉编译器属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。假如一个属性被声明为 @dynamic var,如果没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar的时候
,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var
时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
2.3.7runtime如何实现weak变量的自动置nil?
runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
2.3.8KVC和KVO的keyPath一定是属性么?
KVC 支持实例变量,KVO 只能手动支持手动设定实例变量的KVO实现监听。
3.方法
3.1在OC中向一个nil对象发送消息会发生什么?
在 Objective-C 中向 nil 发送消息是完全有效的——只是在运行时不会有任何作用。这是因为objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。
runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,然后在发送消息的时候,objc_msgSend方法不会返回值,所谓的返回内容都是具体调用时执行的。 如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。
3.2在OC中向一个对象发送消息[obj foo]和objc_msgSend()
函数之间有什么关系?
[obj foo]编译之后就是objc_msgSend()
函数调用。详见Runtime之方法
3.3什么时候会报unrecognized selector的异常?
当调用该对象上某个方法,而该对象上没有实现这个方法的时候,会报unrecognized selector的异常。
可以通过“消息转发”进行解决,详见Runtime之方法的消息转发部分。
3.4runtime如何通过selector找到对应的IMP地址?
每一个类对象中都一个方法列表,方法列表中记录着方法的名称、方法实现、以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现。
3.5_objc_msgForward
函数是做什么的?
_objc_msgForward
是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward
会尝试做消息转发。关于消息转发,详见Runtime之方法的消息转发部分。
4.类和对象
4.1一个objc对象如何进行内存布局?
- 所有父类的成员变量和自己的成员变量都会存放在该对象所对应的存储空间中。
- 每一个对象内部都有一个isa指针,指向他的类对象,类对象中存放着本对象的成员变量链表、方法链表、协议链表等(详见RunTime之类与对象第一章)。
- 类对象内部也有一个isa指针指向元对象(meta class),元对象内部存放的是类方法列表,类对象内部还有一个superclass的指针,指向它的父类对象。
4.2一个objc对象的isa的指针指向什么?有什么作用?
指向他的类对象,从而可以找到对象上的方法。
4.3能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
- 不能向编译后得到的类中增加实例变量;
- 能向运行时创建的类中添加实例变量;
因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list
实例变量的链表 和 instance_size
实例变量的内存大小已经确定,同时runtime 会调用 class_setIvarLayout
或 class_setWeakIvarLayout
来处理 strong和weak 引用。所以不能向存在的类中添加实例变量;运行时创建的类是可以添加实例变量,调用 class_addIvar
函数。但是得在调用 objc_allocateClassPair
之后,objc_registerClassPair
之前。详见RunTime之类与对象。
4.4BAD_ACCESS在什么情况下出现?
- 死循环;
- 访问了野指针,比如对一个已经释放的对象执行了release、访问已经释放对象的成员变量或者发消息。
4.5使用block时什么情况会发生引用循环,如何解决?
一个对象中强引用了block,在block中又强引用了该对象,就会发生循环引用。
解决方法是将该对象使用__weak或者__block修饰符修饰之后再在block中使用。
5.Runloop
5.1runloop和线程有什么关系?
- 主线程的run loop默认是启动的;
- 其它线程的run loop默认是没有启动的,可以通过以下代码来获取到当前线程的 run loop:
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
详细关系见聊聊Runloop。
5.2objc使用什么机制管理对象内存?
通过 retainCount 的机制来决定对象是否需要释放。 每次 runloop 的时候,都会检查对象的 retainCount,如果retainCount 为 0,说明该对象没有地方需要继续使用了,可以释放掉了。
5.3不手动指定autoreleasepool的前提下,一个autorealese对象在什么时刻释放?
Autorelease对象出了作用域之后,会被添加到最近一次创建的自动释放池中,并会在当前的 runloop 迭代结束时释放。
从程序启动到加载完成是一个完整的运行循环,然后会停下来,等待用户交互,用户的每一次交互都会启动一次运行循环,来处理用户所有的点击事件、触摸事件。
我们都知道: 所有 autorelease 的对象,在出了作用域之后,会被自动添加到最近创建的自动释放池中。
但是如果每次都放进应用程序的 main.m
中的 autoreleasepool 中,迟早有被撑满的一刻。这个过程中必定有一个释放的动作。在什么时候释放呢?在一次完整的运行循环结束之前,会被销毁。
那什么时间会创建自动释放池?运行循环检测到事件并启动后,就会创建自动释放池。
@autoreleasepool 当自动释放池被销毁或者耗尽时,会向自动释放池中的所有对象发送 release 消息,释放自动释放池中的所有对象。
6.GCD
6.1如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, ^{ /*加载图片1 */ }); dispatch_group_async(group, queue, ^{ /*加载图片2 */ }); dispatch_group_async(group, queue, ^{ /*加载图片3 */ }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // 合并图片 });
详见GCD浅析。
6.2dispatch_barrier_async
的作用是什么?
在并行队列中,为了保持某些任务的顺序,需要等待一些任务完成后才能继续进行,使用 barrier 来等待之前任务完成,避免数据竞争等问题。 dispatch_barrier_async
函数会等待追加到Concurrent Dispatch Queue并行队列中的操作全部执行完之后,然后再执行 dispatch_barrier_async
函数追加的处理,等 dispatch_barrier_async
追加的处理执行结束之后,Concurrent Dispatch Queue才恢复之前的动作继续执行。
详见GCD浅析。