2019 iOS 面試 -中級篇之 Block

1. block的實質是什麼?一共有幾種block?都是什麼狀況下生成的?

block的實質是什麼?objective-c

  • block本質上也是一個OC對象,它內部也有個isa指針
  • block是封裝了函數調用以及函數調用環境的OC對象
  • block是封裝函數及其上下文的OC對象

查看block源碼:編程

  • 打開終端,在main.m所在目錄下鍵入clang -rewrite-objc main.m便可在當前目錄下生成一個main.cpp文件;
  • 當引用了OC中的Foundation或者UIKit框架時,經過 clang -rewrite-objc 指定文件名 命令將指定文件轉換成C++代碼會報錯;
  • 可經過 clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk 指定文件名

block定義:性能優化

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};
 
struct Block_layout {
    void *isa;
    int flags;
    int reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
複製代碼

一共有幾種block?bash

  • _NSConcreteGlobalBlock 全局靜態
  • _NSConcreteStackBlock 保存在棧中,出函數做用域就銷燬
  • _NSConcreteMallocBlock 保存在堆中,retainCount == 0銷燬而ARC和MRC中,還略有不一樣;

都是什麼狀況下生成的?微信

  • 一、在全局區域聲明定義一個block
  • 二、block表達式中沒有使用捕獲的自動變量時
  • 以上狀況生成的block都是NSConcreteGlobalBlock類型,只生成一個結構體實例;除此狀況下,生成的都是NSConcreteStackBlock類型的block,且都是存儲在棧上。
* [參考1:block 系列文章之一](https://www.jianshu.com/p/852eb39c0fd2)
* [參考2:又見block系列](https://blog.csdn.net/GeekLee609/article/details/82250664)(值得再看一遍)
* 參考3:《Objective-C高級編程 iOS與OS X多線程和內存管理》電子書參考第二章
複製代碼

2. 爲何在默認狀況下沒法修改被block捕獲的變量? __block都作了什麼?

Block只捕獲Block中會用到的變量。因爲只捕獲了自動變量(自動變量是以值傳遞方式傳遞到Block的構造函數裏面)的值,並不是內存地址,因此Block內部不能改變自動變量的值。Block捕獲的外部變量能夠改變值的是靜態變量,靜態全局變量,全局變量。多線程

如何在block中修改外部自動變量的值,主要有如下2種方法:app

  • a、修改C語言中的靜態變量、靜態全局變量和全局變量
  • b、使用__block修飾須要修改的自動變量

3. 模擬一下循環引用的一個狀況?block實現界面反向傳值如何實現?

循環引用:框架

#import <Foundation/Foundation.h>
typedef void(^Study)();  
@interface Student : NSObject
@property (copy , nonatomic) NSString *name;
@property (copy , nonatomic) Study study;
@end


#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
  
    Student *student = [[Student alloc]init];
    student.name = @"Hello World";
 
    student.study = ^{
        NSLog(@"my name is = %@",student.name);
    };
複製代碼

block界面反向傳值實例:
一、在第二個視圖控制器的.h文件中定義聲明Block屬性:函數

// NextViewController.h
// 定義block
@property (nonatomic,copy) void (^NextViewControllerBlock)
(NSString *tfText);
// NextViewController.m
@interface NextViewController ()
@property (weak, nonatomic) IBOutlet UITextField *inputTF;
@end
 
- (IBAction)BtnAction:(id)sender {
    //判斷block是否爲空
    if (self.NextViewControllerBlock) {
        self.NextViewControllerBlock(self.inputTF.text);   
    }
    [self.navigationController popViewControllerAnimated:YES];
}
複製代碼

二、在第一個視圖中得到第二個視圖控制器,而且用第二個視圖控制器來調用定義的屬性:性能

// AViewController.m
@interface AViewController ()
@property (weak, nonatomic) IBOutlet UILabel *nextVCInfoLabel;
@end
 
- (IBAction)btnClicked:(id)sender {
    NextViewController *nextVC = [[NextViewController alloc]init];
    nextVC.NextViewControllerBlock = ^(NSString *tfText){
        self.nextVCInfoLabel.text = tfText;
    };
    [self.navigationController pushViewController:nextVC animated:YES];
}
複製代碼

4. NSTImer的循環引用問題

使用NSTimer可能會碰到循環引用的問題。特別是當類具備NSTimer類型的成員變量,而且須要反覆執行計時任務時。例如

_timer = [NSTimer scheduledTimerWithTimeInterval:5.0
                                          target:self
                                        selector:@selector(startCounting)   userInfo:nil
                                         repeats:YES];
複製代碼

類有一個成員變量_timer,給_timer設置的target爲這個類自己。這樣類保留_timer,_timer又保留了這個類,就會出現循環引用的問題,最後致使類沒法正確釋放。

解決這個問題的方式也很簡單,當類的使用者可以肯定不須要使用這個計時器時,就調用

[_timer invalidate];
_timer = nil;
複製代碼

這樣就打破了保留環,類也能夠正確釋放。可是,這種依賴於開發者手動調用方法,才能讓內存正確釋放的方式不是一個很是好的處理方式。因此須要另一種解決方案。
以下所示:

@interface NSTimer (JQUsingBlock)
+ (NSTimer *)jq_scheduledTimerWithTimeInterval:(NSTimeInterval)ti
                                     block:(void(^)())block
                                   repeats:(BOOL)repeats;
@end
 
@implementation NSTimer (JQUsingBlock)
 
+ (NSTimer *)jq_scheduledTimerWithTimeInterval:(NSTimeInterval)ti
                                     block:(void(^)())block
                                   repeats:(BOOL)repeats{
    return [self scheduledTimerWithTimeInterval:ti
                                     target:self                                                                        selector:@selector(jq_blockInvoke:)
                                   userInfo:[block copy]
                                    repeats:repeats];
}
 
+ (void)jq_blockInvoke:(NSTimer *)timer{
    void(^block)() = timer.userInfo;
    if (block) {
        block();
    }
}
 
@end
複製代碼

定義一個NSTimer的類別,在類別中定義一個類方法。類方法有一個類型爲塊的參數(定義的塊位於棧上,爲了防止塊被釋放,須要調用copy方法,將塊移到堆上)。
使用這個類別的方式以下:

__weak ViewController *weakSelf = self;
_timer = [NSTimer jq_scheduledTimerWithTimeInterval:5.0
                        block:^{
                                __strong ViewController *strongSelf = weakSelf;
                                [strongSelf startCounting];
                                }
                      repeats:YES];
複製代碼

使用這種方案就能夠防止NSTimer對類的保留,從而打破了循環引用的產生。__strong ViewController *strongSelf = weakSelf主要是爲了防止執行塊的代碼時,類被釋放了。在類的dealloc方法中,記得調用[_timer invalidate]。

最後

參考文章:iOS底層原理之性能優化

給你們推薦一個500人優秀的iOS交流平臺,平臺裏的夥伴們都是很是優秀的iOS開發人員,咱們專一於技術的分享與技巧的交流,你們能夠在平臺上討論技術,交流學習。歡迎你們的加入(想要進入的可加小編微信1367798518)





做者:赫子豐
連接
相關文章
相關標籤/搜索