macApp开发碰到的坑
mac软件联网问题
DNSServiceCreateDelegateConnection failed: ServiceNotRunning
mac软件联网问题
DNSServiceCreateDelegateConnection failed: ServiceNotRunning
[!] CocoaPods could not find compatible versions for pod "multi_image_picker":
In Podfile:
multi_image_picker (from `.symlinks/plugins/multi_image_picker/ios`)
Specs satisfying the multi_image_picker (from .symlinks/plugins/multi_image_picker/ios) dependency were found, but they required a higher minimum deployment target.
pod 里的platform :ios, '8.0' 版本低了,改成9.0 即可
在向队列中添加任务时,可以直接在对应的函数中添加 block。但是如果想对任务进行操作,比如监听任务、取消任务,就需要获取对应的 block。
创建block
object-c
创建 block 有两种方式,第一种方式如下:
dispatch_block_t dispatch_block_create(dispatch_block_flags_t flags, dispatch_block_t block);
在该函数中,flags 参数用来设置 block 的标记,block 参数用来设置具体的任务。flags 的类型为 dispatch_block_flags_t 的枚举,用于设置 block 的标记,定义如下:
DISPATCH_ENUM(dispatch_block_flags, unsigned long,
DISPATCH_BLOCK_BARRIER = 0x1,
DISPATCH_BLOCK_DETACHED = 0x2,
DISPATCH_BLOCK_ASSIGN_CURRENT = 0x4,
DISPATCH_BLOCK_NO_QOS_CLASS = 0x8,
DISPATCH_BLOCK_INHERIT_QOS_CLASS = 0x10,
DISPATCH_BLOCK_ENFORCE_QOS_CLASS = 0x20,);
创建 block 的另一种方式如下:
dispatch_block_t dispatch_block_create_with_qos_class(dispatch_block_flags_t flags,
dispatch_qos_class_t qos_class, int relative_priority,
dispatch_block_t block);
相比于 dispatch_block_create 函数,这种方式在创建 block 的同时可以指定了相应的优先级。dispatch_qos_class_t 是 qos_class_t 的别名,定义如下:
typedef qos_class_t dispatch_qos_class_t;
typedef unsigned int dispatch_qos_class_t;
而 qos_class_t 是一种枚举,有以下类型:
QOS_CLASS_USER_INTERACTIVE:user interactive 等级表示任务需要被立即执行,用来在响应事件之后更新 UI,来提供好的用户体验。这个等级最好保持小规模。
QOS_CLASS_USER_INITIATED:user initiated 等级表示任务由 UI 发起异步执行。适用场景是需要及时结果同时又可以继续交互的时候。
QOS_CLASS_DEFAULT:default 默认优先级
QOS_CLASS_UTILITY:utility 等级表示需要长时间运行的任务,伴有用户可见进度指示器。经常会用来做计算,I/O,网络,持续的数据填充等任务。这个任务节能。
QOS_CLASS_BACKGROUND:background 等级表示用户不会察觉的任务,使用它来处理预加载,或者不需要用户交互和对时间不敏感的任务。
QOS_CLASS_UNSPECIFIED:unspecified 未指明
事例:
dispatch_queue_t concurrentQuene = dispatch_queue_create("concurrentQuene", DISPATCH_QUEUE_CONCURRENT);
dispatch_block_t block = dispatch_block_create(0, ^{
NSLog(@"normal do some thing...");});
dispatch_async(concurrentQuene, block);
//
dispatch_block_t qosBlock = dispatch_block_create_with_qos_class(0, QOS_CLASS_DEFAULT, 0, ^{
NSLog(@"qos do some thing...");});
dispatch_async(concurrentQuene, qosBlock);
swift 3.0
swift 3.0 中使用了 DispatchWorkItem 对任务进行了封装。可以在 DispatchWorkItem 初始化方法中指定任务的内容和优先级。事例如下:
let concurrentQueue = DispatchQueue.init(label: "concurrentQueue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil);
let block = DispatchWorkItem.init(block: {
print("normal do some thing...")})
concurrentQueue.async(execute: block);
let qosBlock = DispatchWorkItem.init(qos: .default, flags: .noQoS, block: {
print("qos do some thing...")})
监听 block 执行结束
有时我们需要等待特定的 block 执行完成之后,再去执行其他任务。有两种方法可以获取到指定 block 执行结束的时机。
object-c
在 object-c 中,第一种为使用下面的函数:
long dispatch_block_wait(dispatch_block_t block, dispatch_time_t timeout);
该函数会阻塞当前线程进行等待。传入需要设置的 block 和等待时间 timeout。timeout 参数表示函数在等待 block 执行完毕时,应该等待多久。如果执行 block 所需的时间小于 timeout,则返回 0,否则返回非 0 值。此参数也可以取常量 DISPATCH_TIME_FOREVER,这表示函数会一直等待 block 执行完,而不会超时。可以使用 dispatch_time 函数和 DISPATCH_TIME_NOW 常量来方便的设置具体的超时时间。
如果 block 执行完成,dispatch_block_wait 就会立即返回。不能使用 dispatch_block_wait 来等待同一个 block 的多次执行全部结束;这种情况可以考虑使用 dispatch_group_wait 来解决。也不能在多个线程中,同时等待同一个 block 的结束。同一个 block 只能执行一次,被等待一次。
注意:因为 dispatch_block_wait 会阻塞当前线程,所以不应该放在主线程中调用。
事例
dispatch_queue_t concurrentQuene = dispatch_queue_create("concurrentQuene", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQuene, ^{
dispatch_queue_t allTasksQueue = dispatch_queue_create("allTasksQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_block_t block = dispatch_block_create(0, ^{
NSLog(@"开始执行");
[NSThread sleepForTimeInterval:3];
NSLog(@"结束执行");
});
dispatch_async(allTasksQueue, block);
// 等待时长,10s 之后超时
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC));
long resutl = dispatch_block_wait(block, timeout);
if (resutl == 0) {
NSLog(@"执行成功");
} else {
NSLog(@"执行超时");
}});
输入结果:
开始执行
结束执行
执行成功
swift 3.0
在 swift 3.0 中,只需调用 DispatchWorkItem 的实例方法即可等待任务结束,如下:
public func wait()
public func wait(timeout: DispatchTime) -> DispatchTimeoutResult
public func wait(wallTimeout: DispatchWallTime) -> DispatchTimeoutResult
事例:
let concurrentQueue = DispatchQueue.init(label: "concurrentQueue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil);
concurrentQueue.async(execute: {
let allTesksQueue = DispatchQueue.init(label: "allTesksQueue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil);
let workItem = DispatchWorkItem.init(block: {
print("开始执行")
sleep(3)
print("结束执行")
})
allTesksQueue.async(execute: workItem)
// 设置超时时间,超时时间为 10s
let timeout = DispatchTime.now() + 10
let result = workItem.wait(timeout: timeout)
if result == .success {
print("执行成功")
} else {
print("执行超时")
}})
输入结果同 object-c。
第二种监听 block 执行完成的方法如下:
object-c
在 object-c 中可用下面的函数:
void dispatch_block_notify(dispatch_block_t block,
dispatch_queue_t queue,
dispatch_block_t notification_block);
该函数接收三个参数,第一个参数是需要监视的 block,第二个参数是监听的 block 执行结束之后要提交执行的队列 queue,第三个参数是待加入到队列中的 block。 和 dispatch_block_wait 的不同之处在于:dispatch_block_notify 函数不会阻塞当前线程。
例如:
NSLog(@"---- 开始设置任务 ----");
dispatch_queue_t serialQueue = dispatch_queue_create("com.fyf.serialqueue", DISPATCH_QUEUE_SERIAL);
// 耗时任务
dispatch_block_t taskBlock = dispatch_block_create(0, ^{
NSLog(@"开始耗时任务");
[NSThread sleepForTimeInterval:2.f];
NSLog(@"完成耗时任务");});
dispatch_async(serialQueue, taskBlock);
// 更新 UI
dispatch_block_t refreshUI = dispatch_block_create(0, ^{
NSLog(@"更新 UI");});
// 设置监听
dispatch_block_notify(taskBlock, dispatch_get_main_queue(), refreshUI);
NSLog(@"---- 完成设置任务 ----");
输出:
---- 开始设置任务 ----
---- 完成设置任务 ----
开始耗时任务
完成耗时任务
更新 UI
swift 3.0
在 swift 3.0 中,可以使用 DispatchWorkItem 的实例方法进行设置,方法如下:
public func notify(qos: DispatchQoS = default, flags: DispatchWorkItemFlags = default, queue: DispatchQueue, execute: @escaping @convention(block) () -> Swift.Void)
public func notify(queue: DispatchQueue, execute: DispatchWorkItem)
事例:
print("---- 开始设置任务 ----")
let tasksQueue = DispatchQueue.init(label: "com.fyf.tasksQueue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil);
let refreshUIWorkItem = DispatchWorkItem.init(block: {
print("更新 UI")})
let tasksWorkItem = DispatchWorkItem.init(block: {
print("开始耗时任务")
sleep(3)
print("完成耗时任务")})
tasksWorkItem.notify(queue: DispatchQueue.main, execute: refreshUIWorkItem)
tasksQueue.async(execute: tasksWorkItem)
print("---- 完成设置任务 ----")
输出:
---- 开始设置任务 ----
---- 完成设置任务 ----
开始耗时任务
完成耗时任务
更新 UI
任务的取消
iOS8 后 GCD 支持对 dispatch block 的取消。方法如下:
object-c
可以使用下面的函数取消 dispatch block:
void dispatch_block_cancel(dispatch_block_t block);
这个函数用异步的方式取消指定的 block。取消操作使将来执行 dispatch block 立即返回,但是对已经在执行的 dispatch block 没有任何影响。当一个 block 被取消时,它会立即释放捕获的资源。如果要在一个 block 中对某些对象进行释放操作,在取消这个 block 的时候,需要确保内存不会泄漏。
例子:
dispatch_queue_t serialQueue = dispatch_queue_create("com.fyf.serialqueue", DISPATCH_QUEUE_SERIAL);
// 耗时任务
dispatch_block_t firstTaskBlock = dispatch_block_create(0, ^{
NSLog(@"开始第一个任务");
[NSThread sleepForTimeInterval:1.5f];
NSLog(@"结束第一个任务");});
// 耗时任务
dispatch_block_t secTaskBlock = dispatch_block_create(0, ^{
NSLog(@"开始第二个任务");
[NSThread sleepForTimeInterval:2.f];
NSLog(@"结束第二个任务");});
dispatch_async(serialQueue, firstTaskBlock);
dispatch_async(serialQueue, secTaskBlock);
// 等待 1s,让第一个任务开始运行
[NSThread sleepForTimeInterval:1];
dispatch_block_cancel(firstTaskBlock);
NSLog(@"尝试过取消第一个任务");
dispatch_block_cancel(secTaskBlock);
NSLog(@"尝试过取消第二个任务");
输出:
开始第一个任务
尝试过取消第一个任务
尝试过取消第二个任务
结束第一个任务
可见 dispatch_block_cancel 对已经在执行的任务不起作用,只能取消尚未执行的任务。
swift 3.0
在 swift 3.0 中,可以使用 DispatchWorkItem 的实例方法进行设置,方法如下:
public func cancel()
事例:
let tasksQueue = DispatchQueue.init(label: "com.fyf.tasksQueue");
let firstWorkItem = DispatchWorkItem.init(block: {
print("开始第一个任务")
sleep(2)
print("结束第一个任务")})
let secondWorkItem = DispatchWorkItem.init(block: {
print("开始第二个任务")
sleep(2)
print("结束第二个任务")})
tasksQueue.async(execute: firstWorkItem)
tasksQueue.async(execute: secondWorkItem)
// 等待 1s,让第一个任务开始运行
sleep(1)
firstWorkItem.cancel()
print("尝试过取消第一个任务")
secondWorkItem.cancel()
print("尝试过取消第二个任务")
输出内容同 object-c。
相信大家学习iOS视图部分的时候,跟它俩没少纠缠,尤其是bounds很多朋友都糊了,bounds确实比较难理解。这两个概念很重要,因为它们是奠定整个视图层次结构的基础。所以我们需要了解的透彻一点。
首先,我们来看一下iOS特有的坐标系,在iOS坐标系中以左上角为坐标原点,往右为X正方向,往下是Y正方向如下图:
bounds和frame都是属于CGRect类型的结构体,系统的定义如下,包含一个CGPoint(起点)和一个CGSize(尺寸)子结构体。
struct CGRect {
CGPoint origin;
CGSize size;};
origin决定了view的起点,size决定View的尺寸。
1.frame
frame是每个view必备的属性,表示view在父view坐标系统中的位置和大小,参照点是父视图的坐标系统。
示例代码:
UIView *viewA = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 300, 300)];
[viewA setBackgroundColor:[UIColor blueColor]];
[self.view addSubview:viewA];
NSLog(@"viewA - %@",NSStringFromCGRect(viewA.frame));
UIView *viewB = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 200, 200)];
[viewB setBackgroundColor:[UIColor yellowColor]];
[viewA addSubview:viewB];
NSLog(@"viewB - %@",NSStringFromCGRect(viewB.frame));
UIView *viewC = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
[viewC setBackgroundColor:[UIColor redColor]];
[self.view addSubview:viewC];
NSLog(@"viewC - %@",NSStringFromCGRect(viewC.frame));
打印结果:
2018-01-14 21:35:16.196389+0800 frame & bounds[1485:121325] viewA - {{50, 50}, {300, 300}}
2018-01-14 21:35:16.196647+0800 frame & bounds[1485:121325] viewB - {{50, 50}, {200, 200}}
2018-01-14 21:35:16.196802+0800 frame & bounds[1485:121325] viewC - {{100, 100}, {200, 200}}
效果图:
以上可以看出,viewB和viewC的起点重合,但是从打印结果来看,viewB的起点为(50,50),而viewCde起点为(100,100)。原因就是frame中的位置是以父视图的坐标系为标准来确定当前视图的位置,viewB的父视图为viewA,viewC的父视图为self.view,而由于viewA的起点为(50,50),所以viewB与viewC起点才会重合。
2.bounds
bounds也是每个view都有的属性,这个属性我们一般不进行设置,表示view在本地坐标系统中的位置和大小。参照点是本地坐标系统。如果我们对上例打印bounds,将会得到以下结果:
2018-01-14 22:03:44.385207+0800 frame & bounds[1635:140821] viewA - {{0, 0}, {300, 300}}
2018-01-14 22:03:44.385482+0800 frame & bounds[1635:140821] viewB - {{0, 0}, {200, 200}}
2018-01-14 22:03:44.385646+0800 frame & bounds[1635:140821] viewC - {{0, 0}, {100, 100}}
因为我们并没有设置bounds值,那么,bounds到底有什么作用呢。这里强调,每个视图都有自己的坐标系,且这个坐标系默认以自身的左上角为坐标原点,所有子视图以这个坐标系的原点为基准点。bounds的位置代表的是子视图看待当前视图左上角的位置,bounds的大小代表当前视图的大小。原则如下:
更改bounds中的位置对于当前视图没有影响,相当于更改了当前视图的坐标系,对于子视图来说当前视图的左上角已经不再是(0,0), 而是改变后的坐标,坐标系改了,那么所有子视图的位置也会跟着改变。
更改bounds的大小,bounds的大小代表当前视图的长和宽,修改长宽后,中心点继续保持不变, 长宽进行改变,通过bounds修改长宽看起来就像是以中心点为基准点对长宽两边同时进行缩放。
以下给出例子详细讨论。
3.两者的区别
3.1 origin的区别
如下图:
此时,如果我们把ViewA的bounds改为(0,100),结果如下:
我们始终要清楚,bounds的位置代表的是子视图看待当前视图左上角的位置。
bounds遵守的原则一中,更改bounds中的位置对于当前视图(ViewA)没有影响,相当于更改了ViewA的坐标系,但是子视图(ViewB)不同,对于ViewB来说ViewA的左上角已经不再是(0,0), 而是(0,100),所以对于ViewB来说,ViewA坐标系的原点其实是在红色箭头所指处的上方100处,而此时ViewB的frame.origin为(200,100),所以ViewB的上边与ViewA上边重合。
如果我们更改ViewA的bounds为(200,0),同理(可以自己思考试试),结果如下:
3.2 size的区别
frame的size直接决定了view的大小,而bounds的size修改后,view的中心点不变,长宽以中心点进行缩放。
如下例:
UIView *viewA = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 240)];
[viewA setBackgroundColor:[UIColor grayColor]];
[self.view addSubview:viewA];
UIView *viewB = [[UIView alloc] initWithFrame:CGRectMake(100, 50, 160, 120)];
[viewB setBackgroundColor:[UIColor blueColor]];
[viewA addSubview:viewB];
//viewB设置size(320,160)
[viewB setBounds:CGRectMake(0, 0, 320, 240)];
结果如下:
第二个图为设置了size之后的结果,viewB左上点距离viewA显然不为(100,50),而是进行了基于viewB视图中心点的缩放操作。
4.总结
frame不管对于位置还是大小,改变的都是自己本身。
frame的位置是以父视图的坐标系为参照,从而确定当前视图在父视图中的位置。
frame的大小改变时,当前视图的左上角位置不会发生改变,只是大小发生改变。
bounds改变位置时,改变的是子视图的位置,自身没有影响;其实就是改变了本身的坐标系原点,默认本身坐标系的原点是左上角。
bounds的大小改变时,当前视图的中心点不会发生改变,当前视图的大小发生改变,看起来效果就想缩放一样。
写这篇文章的缘由是第一次面试时被问到了block循环引用的问题,当时回答的不是很好,首先要明确的是,block是否用copy修饰决定不了循环引用的产生,在此再一次进行补强,有不对的地方还请多多指教。
1.block为什么要用copy修饰
1.1 内存堆栈理解
内存栈区
由编译器自动分配释放,存放函数的参数值,局部变量的值等,不需要程序员来操心。其操作方式类似于数据结构中的栈。
内存堆区
一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。尽管后边苹果引入了ARC机制,但是ARC的机制其实仅仅是系统帮助程序员添加了retain,release,autorelease代码,并不是说系统就可以自动管理了。他的系统管理的原理还是MRC,并没有本质区别。注意内存堆区与数据结构中的堆是两回事,分配方式倒是类似于链表。
1.2 block作用域
首先,block是一个对象,所以block理论上是可以retain/release的。但是block在创建的时候它的内存是默认是分配在栈(stack)上,而不是堆(heap)上的。所以它的作用域仅限创建时候的当前上下文(函数, 方法...),当你在该作用域外调用该block时,block占用的内存已经释放,无法进行访问,程序就会崩溃,出现野指针错误。
1.3 三种block
NSGlobalBlock:全局的静态block,没有访问外部变量,存储在代码区(存储方法或者函数)。他直到程序结束的时候,才会被被释放。但是我们实际操作中基本上不会使用到不访问外部变量的block。
void(^testOneBlock)() = ^(){
NSLog(@"我是全局的block");};
NSLog(@"testOneBlock=%@",testOneBlock);
//控制台输出
2017-06-10 09:45:09.767 ReactiveCocoa[871:14517] testOneBlock=<__NSGlobalBlock__: 0x1045982d0>
//全局block,他会随程序销毁而销毁
NSStackBlock:保存在栈中的block,没有用copy去修饰并且访问了外部变量。但是必须要在MRC的模式下控制台才会输出NSStackBlock类型。
//需要MRC模式
int a = 5;
void(^testTwoBlock)() = ^(){
NSLog(@"%d",a);};
NSLog(@"testTwoBlock=%@",testTwoBlock);
//控制台输出
2017-06-10 09:45:09.768 ReactiveCocoa[871:14517] testTwoBlock=<__NSStackBlock__: 0x7fff5b668770>
//栈区block,函数调用完毕就会销毁
NSMallocBlock:保存在堆中的block,此类型blcok是用copy修饰出来的block,它会随着对象的销毁而销毁,只要对象不销毁,我们就可以调用的到在堆中的block。
int a = 5;
self.block1 = ^(NSString str, UIColor color){
NSLog(@"%d",a);};
NSLog(@"block1=%@",self.block1);
//控制台输出
2017-06-10 10:02:35.107 ReactiveCocoa[1075:19674] block1=<__NSMallocBlock__: 0x60000004ee50>
//用copy修饰的不会函数调用完就结束,随对象销毁才销毁,这种是在开发中正确使用block的姿势
第三种block在有些情况下会造成block的循环引用,将在下面进行讨论。
1.4 另一种理解方式:函数返回
关于函数返回,在一个函数的内部,return的时候返回的都是一个拷贝,不管是变量、对象还是指针都是返回拷贝,但是这个拷贝是浅拷贝。在这里我需要理解以下两点:
对于直接返回一些基本类型的变量来说,直接返回值的拷贝就好,没有问题。
对于返回一些非动态分配(new/malloc)得到的指针就可能出现问题,因为尽管你返回了这个指针地址。但是这个指针可能指向的栈内存,栈内存在函数执行完毕后就自动销毁了。如果销毁之后你再去访问,就会访问坏内存会导致程序崩溃。
明确上边两点之后,我们再来说,在MRC下,如果一个block作为参数,没有经过copy就返回。后果是什么呢?由于return的时候返回的是浅拷贝,也就是说返回的是对象的地址,因为在返回后这个block对应的栈内存就销毁了。如果你多次调用这个block就会发现,程序会崩溃。崩溃原因就是上边所说,block占用的空间已经释放了,你不可以进行访问了。
解决方案:就是在返回的时候,把block进行拷贝作为参数进行返回。这样做的好处是返回的那个block存储空间是在堆内,堆内的空间需要程序员自己去释放,系统不会自动回收,也就不会出现访问已释放内存导致的崩溃了。也就是我们在MRC下需要使用copy修饰符的原因。(此处是否是通过深复制在堆中申请内存不求甚解,在此标记,继续深究)
1.5 ARC下block用什么修饰
首先前面讲的内容都是在MRC下,MRC下block需要用copy修饰,但是在ARC下使用copy或strong修饰其实都一样,因为block的retain就是用copy来实现的。
2.block循环引用
在开始之前我们需要明确一点:是不是所有的block,使用self都会出现循环引用?其实不然,系统和第三方框架的block绝大部分不会出现循环引用,只有少数block以及我们自定义的block会出现循环引用。而我们只要抓住本质原因就可以了,如下:
如果block没有直接或者间接被self存储,就不会产生循环引用。就不需要用weak self。(retainCount无法变为0)
2.1 直接强引用:self -> block -> self
由于block会对block中的对象进行持有操作,就相当于持有了其中的对象,而如果此时block中的对象又持有了该block,则会造成循环引用。如下
typedef void(^block)();
@property (copy, nonatomic) block myBlock;
@property (copy, nonatomic) NSString *blockString;
(void)testBlock {
self.myBlock = ^() {
//其实注释中的代码,同样会造成循环引用
NSString *localString = self.blockString;
//NSString *localString = _blockString;
//[self doSomething];
};}
注:以下调用注释掉的代码同样会造成循环引用,因为不管是通过self.blockString还是_blockString,或是函数调用[self doSomething],因为只要block中用到了对象的属性或者函数,block就会持有该对象而不是该对象中的某个属性或者函数。

2.2 间接强引用:self -> 某个类 -> block -> self
间接强引用中,self并没有直接拥有block属性。来看下面一个例子:
这是一个持有block的view: XXSubmitBottomView
typedef void(^BtnPressedBlock)(UIButton *btn);
@interface XXSubmitBottomView : UIView
@property(strong,nonatomic)UILabel *allPriceLab;
@property(strong,nonatomic)UIButton *submittBtn;
@property(nonatomic, weak)XXConfirmOrderController *currentVc;
@property(nonatomic, weak)XXConfimOrderModel *model;
@property(nonatomic, copy)BtnPressedBlock block;
-(void)submittBtnPressed:(BtnPressedBlock)block;
这是一个持有bottomView属性的控制器: XXConfirmOrderController
@interface XXConfirmOrderController ()
@property(nonatomic, strong) XXConfimOrderTableView *tableView;
@property(nonatomic, strong) XXSubmitBottomView *bottomView;
@property(nonatomic, strong) XXConfimOrderModel *confimModel;
@end
@implementation XXConfirmOrderController
-(void)viewDidLoad{
[super viewDidLoad];
self.title = @"确认下单";
self.view.backgroundColor = DDCJ_Gray_Color;
//UI
[self.view addSubview:self.tableView];
[self.view addSubview:self.bottomView];
//Data
[self loadData];}
下面是self.bottomView的懒加载以及block的回调处理
-(XXSubmitBottomView *)bottomView{
if (!_bottomView) {
_bottomView = [[XXSubmitBottomView alloc] initWithFrame:CGRectMake(0, self.view.height - 50, Width, 50)];
_bottomView.currentVc = self; WEAKSELF //ps: weakSelf的宏定义#define WEAKSELF typeof(self) __weak weakSelf = self;
[_bottomView submittBtnPressed:^(UIButton *btn) {
NSLog(@"do提交订单");
MBProgressHUD *hud = [MBProgressHUD showMessage:@"加载中..." toView:weakSelf.view];
NSMutableDictionary *dynamic = [NSMutableDictionary dictionary];
[dynamic setValue:weakSelf.confimModel.orderRemark forKey:@"orderRemark"];
if (weakSelf.agreementId) {
[dynamic setValue:weakSelf.agreementId forKey:@"agreementId"];
}
if (weakSelf.isShoppingCartEnter) {
[dynamic setValue:@"0" forKey:@"orderOrigin"];
}else{
[dynamic setValue:@"1" forKey:@"orderOrigin"];
}
[[APIClientFactory sharedManager] requestConfimOrderWithDynamicParams:dynamic success:^(NSMutableArray *dataArray) {
[hud hideAnimated:YES];
[weakSelf handlePushControllerWithModelList:dataArray];
} failure:^(NSError *error) {
[hud hideAnimated:YES];
[MBProgressHUD showError:error.userInfo[@"message"]];
}];
}];
}
return _bottomView;}
此处的控制器self并没有直接持有block属性,但是却强引用了bottomView,bottomView强引用了block属性,这就造成了间接循环引用。block回调内必须使用[weak self]来打破这个循环,否则就会导致这个控制器self永远都不会被释放掉产生常驻内存。
2.3 实际开发中的循环引用
使用通知(NSNotifation),调用系统自带的Block,在Block中使用self会发生循环引用。


注:自定义的block出现循环引用时都会出现警告,所以出问题时容易解决。但在这里,在block中的确出现了循环引用,也的确没有出现警告,这才是我们真正需要注意的,也是为什么我们需要理解block循环引用的原因。
2.4 解决办法
一般性解决办法
__weak typeof(self) weakSelf = self;
复制代码
通过__weak的修饰,先把self弱引用(默认是强引用,实际上self是有个隐藏的__strong修饰的),然后在block回调里用weakSelf,这样就会打破保留环,从而避免了循环引用,如下:
self -> block -> weakSelf
self -> 某个类 -> block ->weakSelf
提醒:__block与__weak都可以用来解决循环引用,但是,__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。__weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。__block对象可以在block中被重新赋值,__weak不可以。
@weakify
@weakify(self)
self.myBlock = ^() {
NSString *localString = self.blockString;};
2.5 weak的缺陷
缺陷
如果我想在Block中延时来运行某段代码,这里就会出现一个问题,看这段代码:
(void)viewDidLoad {
[super viewDidLoad];
MitPerson*person = [[MitPerson alloc]init];
__weak MitPerson * weakPerson = person;
person.mitBlock = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakPerson test];
});
};
person.mitBlock();}
直接运行这段代码会发现[weakPerson test];并没有执行,打印一下会发现,weakPerson已经是 Nil 了,这是由于当我们的viewDidLoad方法运行结束,由于是局部变量,无论是MitPerson和weakPerson都会被释放掉,那么这个时候在Block中就无法拿到正真的person内容了。
解决办法一
(void)viewDidLoad {
[super viewDidLoad];
MitPerson*person = [[MitPerson alloc]init];
__weak MitPerson * weakPerson = person;
person.mitBlock = ^{
__strong MitPerson * strongPerson = weakPerson;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongPerson test];
});
};
person.mitBlock();}
这样当2秒过后,计时器依然能够拿到想要的person对象。
深入理解
首先了解一些概念:
堆里面的block(被copy过的block)有以下现象:
1.block内部如果通过外面声明的强引用来使用,那么block内部会自动产生一个强引用指向所使用的对象。
2.block内部如果通过外面声明的弱引用来使用,那么block内部会自动产生一个弱引用指向所使用的对象。
这段代码的目的:
首先,我们需要在Block块中调用,person对象的方法,既然是在Block块中我们就应该使用弱指针来引用外部变量,以此来避免循环引用。但是又会出现问题,什么问题呢?就是当我计时器要执行方法的时候,发现对象已经被释放了。
接下来就是为了避免person对象在计时器执行的时候被释放掉:那么为什么person对象会被释放掉呢?因为无论我们的person强指针还是weakPerson弱指针都是局部变量,当执行完ViewDidLoad的时候,指针会被销毁。对象只有被强指针引用的时候才不会被销毁,而我们如果直接引用外部的强指针对象又会产生循环引用,这个时候我们就用了一个巧妙的代码来完成这个需求。
首先在person.mitBlock引用外部weakPerson,并在内部创建一个强指针去指向person对象,因为在内部声明变量,Block是不会强引用这个对象的,这也就在避免的person.mitBlock循环引用风险的同时,又创建出了一个强指针指向对象。
之后再用GCD延时器Block来引用相对于它来说是外部的变量strongPerson,这时延时器Block会默认创建出来一个强引用来引用person对象,当person.mitBlock作用域结束之后strongPerson会跟着被销毁,内存中就仅剩下了延时器Block强引用着person对象,2秒之后触发test方法,GCD Block内部方法执行完毕之后,延时器和对象都被销毁,这样就完美实现了我们的需求。
黑色代表强引用,绿色代表弱引用
解决办法二
(void)viewDidLoad {
[super viewDidLoad];
MitPerson*person = [[MitPerson alloc]init];
@weakify(self)
person.mitBlock = ^{
@strongify(self)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self test];
});
};
person.mitBlock();}
可以看出,这样就完美解决了weak的缺陷,我们可以在block中随意使用self。
3.参考
iOS block为什么要用copy
Block作为属性为什么要用copy修饰
【iOS 开发】为什么要用copy修饰Block
iOS小记--block的循环引用问题以及block何时需要/不需要使用[weak self]
实际开发中-Block导致循环引用的问题
循环引用,看我就对了
iOS - 深入探究Block循环引用