iOS开发 Objective-C

《iOS知识点梳理-Block》

2019-01-08  本文已影响22人  学而不思则罔思而不学则殆

知识点总结中,如有幸被您观看,更有可能看到不足,期待指出交流

前言

最近放了很多假期,感觉身体得到了很好的休息.

Block底层原理实现

首先我们看四个函数

void test1()
{
    int a = 10;
    void (^block)() = ^{
        NSLog(@"a is %d", a);
    };
    a = 20;
    block(); // 10
}
void test2()
{
    __block int a = 10;
    void (^block)() = ^{
        NSLog(@"a is %d", a);
    };
    a = 20;
    block(); // 20
}
void test3()
{
    static int a = 10;
    void (^block)() = ^{
        NSLog(@"a is %d", a);
    };
    a = 20;
    block(); // 20
}
int a = 10;
void test4()
{
    void (^block)() = ^{
        NSLog(@"a is %d", a);
    };
    a = 20;
    block();//20
}

分析:造成这样的原因是:传值和传址.为什么说会有传值和传址,把.m编译成c++代码.得到.cpp文件,我们来到文件的最后,看到如下代码

struct __test1_block_impl_0 {
    struct __block_impl impl;
    struct __test1_block_desc_0* Desc;
    int a;
    __test1_block_impl_0(void *fp,struct __test1_block_desc_0*
                         Desc,int _a,int flag=0): a(_a){
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __test1_block_func_0(struct __test1_block_imp_0
                                 *__cself)
{
    int a = __cself->a;
    NSLog(a);//a }
void test1()
{
int a = 10;
void (*block)() = (void (*)())&__test1_block_impl_0((void
*))__test1_block_func_0,&__test1_block_desc_0_DATA,a);
a = 20;
((void (*)(__block_impl *))((__block_ipml *)block)->FuncPtr)
((_block_impl *)block);
}
int main(int argc, const char * argv[])
{
/* @autoreleasepool */ { __AtAutoreleasePool
__autoreleasepool;
test1(); }
return 0; }
 48 
static struct IMAGE_INFO { unsigned version; unsigned flag; }
_OBJC_IMAGE_INFO = { 0, 2 };

现在我们来看看test2(),添加了__block会发生什么变化呢

void test2()
{
    __attribute__((_blocks__(byref))) __Block_byref_a_0 a =
    {(void*)0,(__Block_byref_a_0 *)&a,0,sizeof(__Block_byref_a_0),10};
    void(*block)() =  (void (*)())&__test2_block_impl_0((void
                                                         *))__test2_block_func_0,&__test2_block_desc_0_DATA,
    (__Block_byref_a_0 *)&a,570425344);
    (a.__forwarding->a) = 20;
    ((void (*)(__block_impl *))((__block_ipml *)block)->FuncPtr)
    ((_block_impl *)block);
}
int main(int argc, const char * argv[])
{
    /* @autoreleasepool */ { __AtAutoreleasePool
        __autoreleasepool;
        test2(); }
 49 
return 0; }
static struct IMAGE_INFO { unsigned version; unsigned flag; }
_OBJC_IMAGE_INFO = { 0, 2 };
block 定义
//  无参数无返回值
void(^block)();
//  无参数有返回值
int(^block1)();
//  有参数有返回值
int(^block1)(int number);

也可以直接打入inline来自动生成block格式

<#returnType#>(^<#blockName#>)(<#parameterTypes#>) =
^(<#parameters#>) {
    <#statements#>
};
block的内存管理
block的循环引用
__weak typeof(self) weakSelf = self;
__strong typeof(self) strongSelf = weakSelf;
描述一个你遇到过的retain cycle例子

block中的循环引用:一个viewController

blockviewController
@property (nonatomic,strong)HttpRequestHandler * handler;
@property (nonatomic,strong)NSData  *data;
 _handler = [httpRequestHandler sharedManager];
  [ downloadData:^(id responseData){
_data = responseData;
}];

self 拥有 _handler, _handler 拥有 block, block 拥有self(因为使用了self的 _data属性, block会copy 一份self)
解决方法

__weak typedof(self)weakSelf = self
 [ downloadData:^(id responseData){
weakSelf.data = responseData;
block中的weak self, 是在任何时候都需要添加吗
通过block来传值
  ModalViewController *modalVc = [[ModalViewController alloc]
init];
  modalVc.valueBlcok = ^(NSString *str){
NSLog(@"ViewController%@",str); };
 [self presentViewController:modalVc animated:YES completion:nil];
 @property (nonatomic ,strong) void(^valueBlcok)(NSString *str); 
 -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:
(UIEvent *)event
{
// 传值:调用block
        if (_valueBlcok) {
        _valueBlcok(@"123");
        }
}
block作为一个参数使用
- (void)calculator:(int(^)(int result))block; 

-(void)calculator:(int (^)(int))block
  {
  self.result = block(self.result);
  }
CalculatorManager *mgr = [[CalculatorManager alloc] init];
[mgr calculator:^(int result){
    result += 5;
    return result;
}];
block最为返回值使用
make.top.equalTo(superview.mas_top).with.offset(padding.top);

这个方法实现就是将block最为返回值来使用

make.top,make.equalTo,make.with,make.offset,

所以可以得出一个结论make.top返回了一个make,才能实现make.top.equalTo

- (CalculatorManager *(^)(int a))add;
-(CalculatorManager * (^)(int a))add
 {
     return ^(int a){
         _result += a;
     return self;
 };
}
mgr.add(1).add(2).add(3)
block的变量传递
void(*block)() = ((void (*)())&__main_block_impl_0((void
                                                    *)__main_block_func_0,         &__main_block_desc_0_DATA,
                                                   (__Block_byref_a_0 *)&a, 570425344));
void(*block)() = ((void (*)())&__main_block_impl_0((void
                                                    *)__main_block_func_0,         &__main_block_desc_0_DATA, a));
block的注意点
__strong typeof(self) strongSelf = weakSelf;
使用block有什么好处,使用NSTimer写出一个使用block显示(在UILabel)秒表上面的代码

说到block的好处,最直接的就是代码紧凑,传值,回调都很方便,省去了写代理的很多代码,
对于这里根本没有必要使用block来刷新UILabel显示,因为都是直接赋值.当然,笔者觉得这是在考验应聘者如何将一个通用的block版本

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0
                                    repeats:YES
                                   callback:^() {
  weakSelf.secondsLabel.text = ...
}
[[NSRunLoop currentRunLoop] addTimer:timer
forMode:NSRunLoopCommonModes];
block跟函数很像
使用系统的某些block api (如UIView 的block版本写动画时),是否也考虑应用循环问题

系统的某些block api中,UIView 的block版本写动画时不需要考虑,但也有一些api需要考虑.所谓"引用循环"是指双向的强引用
所以那些"单向的强引用"(block强引用self)没有问题,比如这些:

[UIView animateWithDuration:duration animations:^{ [self.superview
layoutIfNeeded]; }];
[[NSOperationQueue mainQueue]
addOperationWithBlock:^{ self.someProperty = xyz; }];
[[NSNotificationCenter defaultCenter]
addObserverForName:@"someNotification"
                           object:nil
                           queue:[NSOperationQueue mainQueue]
notification) {
usingBlock:^(NSNotification *
self.someProperty = xyz;
                         }];

这些情况不需要考虑"循环引用"
但是如果你使用一些参数中可能含有成员变量的系统api,如GCD,NSNotificationCenter就要小心一点.比如GCD内部如果引用了self,而且GCD的其他参数是成员变量,则要考虑到循环引用:

__weak __typeof(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^{
    __typeof__(self) strongSelf = weakSelf;
    [strongSelf doSomething];
    [strongSelf doSomethingElse];
});

类似的:

__weak __typeof(self) weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter]
addObserverForName:@"testKey"
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
  __typeof__(self) strongSelf = weakSelf;
  [strongSelf dismissModalViewControllerAnimated:YES];
}];
self –> _observer –> block –> self 
谈谈对Block的理解.并写出一个使用Block执行UIView动画
[UIView transitionWithView:self.view duration:0.2
ptions:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{ [[blueViewController view] removeFromSuperview];
[[self view] insertSubview:yellowViewController.view atIndex:0]; }
                                       completion:NULL];
写出上面代码的Block的定义
什么是block
block实现原理
typedef void(^didFinishBlock) (NSObject *ob);

这就声明了一个didFinishBlock类型的block;
然后便可以用

@property (nonatomic,copy) didFinishBlock finishBlock;

声明一个block对象,注意对象属性设置为copy,接到block参数时,便会自动复制一份
__block是一个特殊类型
使用这个关键字声明局部变量,可以被block所改变,并且其在原函数中的值会被改变

关于block
使用block和使用delegate完成委托模式有什么优点

首先要了解什么是委托模式,委托模式在iOS中大量应用,其在设计模式中是适配器模式中的对象适配器,Objective-C使用id类型指向一切对象,使用委托模式更为简洁.了解委托模式的细节
iOS设计模式-委托模式
使用block实现委托模式,其优点是回调的block代码块定义在委托对象函数内部,使用代码更为紧凑;
适配对象不再需要实现具体的某个protocol,代码更加简洁.

多线程和block

GCD与block
使用dispatch_async系列方法,可以以指定的方式执行block
GCD变成实例
dispatch_async的完整定义

void dispatch_async(
dispatch_queue_t queue,
dispatch_block_t block);

功能:在指定的队列里提交一个异步执行的block ,不阻塞当前线程
通过queue来控制block执行的线程.主线程执行前文定义的finishBlock对象

dispatch_async(dispatch_get_main_queue(),^(void){finishBlock();});

解释以下代码的内存泄漏原因
@implementation HJTestViewController

- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    HJTestCell *cell = [tableView
dequeueReusableCellWithIdentifier:@"TestCell"
forIndexPath:indexPath];
[cell setTouchBlock:^(HJTestCell *cell) {
        [self refreshData];
    }];
   return cell;
}

原因

[cell setTouchBlock:^(HJTestCell *cell) {
    [self refreshData];
}];

产生内存泄漏的原因是因为循环引用
在给cell设置的touchBlock中,使用了__strong修饰的self,,由于Block的原理,当touchBlock从栈复制到堆中时,self会一同复制到堆中,retain一次,被touchBlock持有,而touchBlcok又是呗cell持有的,cell又被tableView持有,tableView又被self持有,因此形成了循环引用,self间接持有touchBlock,touchBlock持有self
一旦尝试了循环引用,由于俩个object都被强引用,所以retainCount始终不能为0,无法释放,产生内存泄漏
解决办法:
使用weakSelf解除touchBlock对self的强引用


}];
__weak __typeof__(self) weakSelf = self;
[cell setTouchBlock:^(HJTestCell *cell) {
    [weakSelf refreshData];
}];
上一篇 下一篇

猜你喜欢

热点阅读