iOS最新面試題解答最全-2023-03

二十一胖秒、React Nactive與原生的交互

一、RN調用原生方法

1慕的、編寫原生的功能類阎肝,需要實現RCTBridgeModule協(xié)議,類中包含包含RCT_EXPORT_MODULE()宏用來指定React Native中可以訪問的類名肮街。另外通過RCT_EXPORT_METHOD()宏來聲明哪些方法是在ReactNative中可以調用的
定義模型

#import <React/RCTBridgeModule.h>
 
@interface ZHJSManager : NSObject <RCTBridgeModule>
@end
 
#import "ZHJSManager.h"
@implementation ZHJSManager
 
// 默認導出名為 ZHJSManager 的module暴露給JS
RCT_EXPORT_MODULE();
 
// 也可以傳值自定義moduleName  比如 RCT_EXPORT_MODULE(MyCustomJSManager);
 
@end

2风题、直接在React Native中調用就可以了。
暴露方法
模型定義好之后就可以寫方法低散,為了讓RN發(fā)現原生方法需要在.m中使用另外一個宏定義RCT_EXPORT_METHOD()來導出(包裹)你的方法:

@implementation ZHJSManager
 
 RCT_EXPORT_MODULE();RCT_EXPORT_METHOD(methodNameWithFirst:(NSString *)paramStr second:(NSString *)second){
  //OC代碼隨便寫
}
 
// 你還可以使用RN向原生端傳遞整數RCT_EXPORT_METHOD(getIntFromReactNative:(NSInteger)count) { 
 NSString *msg = [NSString stringWithFormat:@"我收到來自RN的整數:%zd", count];  [self showAlert:msg];}
// 你甚至還可以使用RN向原生傳遞NSDictionary和NSArray等復雜數據RCT_EXPORT_METHOD(getDictionaryFromRN:(NSDictionary *)dict) {  NSLog(@"RN傳遞過來的字典:%@", dict);  
// 使用字典參數做你想做的俯邓,原生iOS攻城獅}
RCT_EXPORT_METHOD(getArrayFromRN:(NSArray *)array) {  NSLog(@"RN傳遞過來的數組:%@", array); // 使用數組參數做你想做的,原生iOS攻城獅}
//還可以使用RN提供的block
RCT_EXPORT_METHOD(giveMyRNSomeStringWithBlock:(RCTResponseSenderBlock)callbackBlock) {  if (callbackBlock) {    callbackBlock(@[@"包裹在數組中的字符串"]);  }}
/// 使用block回傳字典或者數組(包裹成二維數組了)到RN端RCT_EXPORT_METHOD(giveMyRNSomeDicDataWithBlock:(RCTResponseSenderBlock)callbackBlock) {  if (callbackBlock) {    NSDictionary *dict = @{@"key1": @"strValue",                           @"key2" : @(20),                           @"key3": @(YES)};
    callbackBlock(@[dict]);  }}
//同步方法的宏,兩個參數第一個是返回值的類型熔号,第二個是方法名RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString*, canReturnSomething){    return @"SSSSS";
}
// 使用Promise回傳數據到RN端
RCT_REMAP_METHOD(findEvents,
                 findEventsWithResolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)
{
  NSArray *events = ...
  if (events) {
    resolve(events);
  } else {
    NSError *error = ...
    reject(@"no_events", @"There were no events", error);
  }
}
//或者//RCT_EXPORT_METHOD(usePromisePassToRN:(NSString *)key resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {//  if (![key isEqualToString:@""]) {//    resolve(@(YES));//  } else {//    reject(@"warning", @"key 不能為空!", nil);//  }//}@end

相關JS代碼實現如下:

//Promise方法調用原生
async function updateEvents() {
  try {
    const events = await ZHJSManager.findEvents();    this.setState({ events });
  } catch (e) {
    console.error(e);
  }
}
function methodNameWithFirst(param1,param2){
//其他調用類似
ZHJSManager.methodNameWithFirst(param1,param2);}

三種調用方法
1稽鞭、通過Callback的方式(同步調用)
callback就是在RN中寫一個回調方法,等著原生代碼執(zhí)行完成之后回調引镊。
2朦蕴、通過Promise的方式(同步調用)
3、通過通知的方式(異步調用)
上述的代碼給React Native發(fā)送了一個名為"testEventName"的通知事件弟头,并且攜帶了map作為參數吩抓。
React Native的代碼: React Native 對原生模塊名為“testEventName”的事件進行監(jiān)聽。

我們要創(chuàng)建一個 Bridge赴恨。在 React Native 中疹娶,通過 Bridge 實現了 JavaScript 與原生框架之間的通信
RCTBridge *carrierBridge = [[RCTBridge alloc] initWithDelegate:self 
                                               launchOptions:nil];
接下來,我們需要創(chuàng)建一個 RCTRootView伦连,用于展示 React Native 視圖的組件RCTRootView雨饺,在 JavaScript 代碼中 render() 部分的 UI 組件均會渲染到該 View 中顷啼,創(chuàng)建方式如下:
RCTRootView *rctView = [[RCTRootView alloc] initWithBridge:bridge 
                                             moduleName:moduleName 
                                             initialProperties:nil];
 [self.view addSubview:rctView];
整體的流程是這樣的:

*   在初始化Bridge時拆火,在 setup 的過程中拂到,首先會調用 bridge 的代理方法 (NSURL \*)sourceURLForBridge : (RCTBridge \*)bridge 方法渊抽,指定獲取 JS bundle 的路徑:

 -(NSURL *)sourceURLForBridge:(RCTBridge *)bridge{
    NSString *bundlePath = [self getCurrentBundlePath:bundleid];
    return bundlePath;
}

*   確定 URL 之后,bridge 會調用 start 方法签赃,開始加載 JS bundle 并調用以下方法:
 [self loadSource:^(NSError *error, RCTSource *source) {
    if (error) {
      [weakSelf handleError:error];
    }
    ...
 ]

*   接下來會調用 bridge 的代理方法痪欲,我們可以在該方法中手動注入一些業(yè)務參數:

- (void)loadSourceForBridge:(RCTBridge *)bridge
                 onProgress:(RCTSourceLoadProgressBlock)onProgress
                 onComplete:(RCTSourceLoadBlock)loadCallback{
  [RCTJavaScriptLoader loadBundleAtURL:bridge.bundleURL onProgress:onProgress onComplete:^(NSError *error, RCTSource *source) {
      //手動注入一些業(yè)務參數
      NSString *string = ";this.__xxx___ = 'yyy';"
      NSData *stringData = [string dataUsingEncoding:NSUTF8StringEncoding];
      
      NSMutableData *newData = [NSMutableData dataWithData:stringData];
      [newData appendData:source.data];
      
      //生成新的Source去加載
      RCTSource * newSource = [RCTJavaScriptLoader getNewRCTSourceURL:source.url data:newData];
      loadCallback(error,newSource);
  }];
}
之后咬腋,bridge 會負責執(zhí)行該 newSource绢馍,執(zhí)行 JavaScript 代碼并渲染出頁面向瓷。

二、原生調用JS方法(發(fā)送事件)

即使沒有被 JavaScript 調用舰涌,原生模塊也可以給 JavaScript 發(fā)送事件通知猖任。最好的方法是繼承RCTEventEmitter,實現suppportEvents方法并調用self sendEventWithName:舵稠。

/// 接收通知的方法,接收到通知后發(fā)送事件到RN端。RN端接收到事件后可以進行相應的邏輯處理或界面跳轉
- (void)sendCustomEvent:(NSNotification *)notification {  
[self sendEventWithName:kCustomEventName body:@"這是發(fā)給RN的字符串"];
}
/// 重寫方法哺徊,定義支持的事件(方法名)集合
- (NSArray<NSString *> *)supportedEvents {
  return @[kCustomEventName,kHaveImportListNotification,kHaveSelectedExpressImage,kHaveSelectedRbrsImages];
}

三室琢、調用原生UI組件

你甚至可以自己在iOS編寫一個組件然后提供給RN來作為render中的一部分來渲染頁面

首先創(chuàng)建一個RCTViewManager的子類。
添加RCT_EXPORT_MODULE()宏標記落追。
實現-(UIView *)view方法盈滴。
// ZHMapManager.m
// 命名規(guī)則前綴自定義,避免和RNT 或者UI這種框架自己的命名規(guī)則沖突
#import <MapKit/MapKit.h>
#import <React/RCTViewManager.h>
@interface ZHMapManager : RCTViewManager
@end
@implementation ZHMapManager
RCT_EXPORT_MODULE(ZHCustomMap)

(UIView *)view
{
//這里不能設置frame和backgroundcolor之類的屬性轿钠,會被ReactNative所覆蓋巢钓,因為他要保證整個頁面的一致性,
//你可以直接在jsx中設置style
return [[MKMapView alloc] init];
}
@end

你在原生種完成了上面的代碼疗垛,為了能讓js可以直接使用症汹,還需要作如下操作,添加MapView.js文件贷腕,然后使用RN提供的HOC方法requireNativeComponent

// MapView.js
import { requireNativeComponent } from 'react-native';/*
requireNativeComponent 自動把'ZHCustomMap'解析為'ZHCustomMapManager'
第二個參數MapView可選背镇,但建議加上
這使得 React Native 的底層框架可以檢查原生屬性和包裝類的屬性是否一致,來減少出現問題的可能泽裳。
*/export default requireNativeComponent('ZHCustomMap',MapView);//

然后你就可以在其他組件中使用這個MapView組件了瞒斩。so easy,so nice

// MyApp.jsimport MapView from './MapView.js';
...render() {//需要加上style,否則看不到哦  return <MapView style={{ flex: 1 }} />;}

這里你應該會有疑問涮总,每個組件都有自己的屬性胸囱,原生組件怎么設置某些屬性?直接在我們自定義的ZHMapManager.m文件中添加導出屬性

// ZHMapManager.m
RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL)

然后我們就可以在js代碼中愉快的使用我們原生屬性了

// MyApp.js
<MapView zoomEnabled={false} style={{ flex: 1 }} />

當然瀑梗,你應該加一些文檔以方便你組件的使用者烹笔,或者如果你使用的是ts,記得寫個interface來約束下夺克。

更復雜的屬性如下

// ZHMapManager.m
RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, MKMapView)
{
  [view setRegion:(json ? [RCTConvert MKCoordinateRegion:json] : defaultView.region) animated:YES];
}

總結

先創(chuàng)建一個 Bridge箕宙。在 React Native 中,通過 Bridge 實現了 JavaScript 與原生框架之間的通信
RCTBridge *carrierBridge = [[RCTBridge alloc] initWithDelegate:self
launchOptions:nil];
接下來铺纽,我們需要創(chuàng)建一個 RCTRootView柬帕,用于展示 React Native 視圖的組件RCTRootView,在 JavaScript 代碼中 render() 部分的 UI 組件均會渲染到該 View 中狡门,創(chuàng)建方式如下:
RCTRootView *rctView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:moduleName
initialProperties:nil];

RN調用原生端:RCT_EXPORT_MODULE()宏用來指定React Native中可以訪問的類名陷寝。另外通過RCT_EXPORT_METHOD()宏定義方法,RCT_EXPORT_VIEW_PROPERTY定義屬性其馏。
原生模塊也可以給 JavaScript 發(fā)送事件通知凤跑。最好的方法是繼承RCTEventEmitter,實現suppportEvents方法并調用self sendEventWithName:叛复。

二十二仔引、podspec作用

podsepc文件的全稱我們可以叫做 pod specification扔仓,specification是規(guī)格說明書的意思,所以顧名思義咖耘,podspec就是pod 庫的規(guī)格說明書(配置文件),這個說明書描述了pod庫的版本翘簇,包括了源文件的需要的地址,用什么樣的文件儿倒,需要什么樣的構建配置版保,還有許多普通的元數據像是庫的名稱,版本號以及描述夫否。

二十三彻犁、load方法跟main方法哪個先調用

1、load方法是在main函數執(zhí)行前執(zhí)行的凰慈;
2汞幢、+load方法是在加載類和分類時系統(tǒng)調用,一般不手動調用溉瓶,如果想要在類或分類加載時做一些事情急鳄,可以重寫類或分類的+load方法。
3堰酿、類疾宏、分類的+load方法,在程序運行過程只調用一次触创。

二十四坎藐、iOS 有什么方案讓wkwebView加載更快一些

二十五、即時通訊怎么處理心跳問題哼绑?怎么處理丟包問題呢岩馍?

TCP KeepAlive用于檢測連接的死活,而心跳機制則附帶一個額外的功能:檢測通訊雙方的存活狀態(tài)抖韩。
最簡單粗暴的方法是定時心跳蛀恩,如每隔30秒心跳一次,15秒內沒有收到心跳包則認為當前連接已失效茂浮,斷開連接并進行重連
連接可靠性的判斷也可以放寬双谆,避免一次心跳超時就認為連接無效的情況,使用錯誤積累席揽,只在心跳超時n次后才判定當前連接不可用
處理丟包問題:發(fā)送一個包 包首顽馋,包中,包尾都做了標記幌羞,并且有字節(jié)大小

二十六寸谜、怎么銷毀單例

static NGUser *sharedInstance = nil;
static dispatch_once_t onceToken;
+ (instancetype)sharedInstance{
  
  dispatch_once(&onceToken, ^{
      //調用父類的allocWithZone,不能使用self,避免循環(huán)引用
      sharedInstance = [[super allocWithZone:NULL] init];
  });
  return sharedInstance;
}

//必須要實現的属桦,當我們創(chuàng)建一個對象熊痴,alloc會給對象分配內存他爸,init初始化數據
//alloc會調用allocWithZone,如果創(chuàng)建對象沒有使用sharedInstance果善,而是使用alloc
//那么alloc就會調用allocWithZone讲逛,重寫類方法,調用sharedInstance使得alloc時創(chuàng)建的也是單例對象
+(instancetype)allocWithZone:(struct _NSZone *)zone{
  return [self sharedInstance];
}

//單例對象被copy
-(id)copyWithZone:(nullable NSZone *)zone{
  return self;
}
+(void)attempDealloc{
  onceToken = 0; // 只有置成0,GCD才會認為它從未執(zhí)行過.它默認為0.這樣才能保證下次再次調用shareInstance的時候,再次創(chuàng)建對象.
  sharedInstance = nil;
}

二十七岭埠、項目架構,MVVM有什么優(yōu)勢蔚鸥?RAC有什么優(yōu)勢惜论?兩者結合起來有什么優(yōu)勢?

MVVM(Model - View/ViewController - ViewModel)是對MVC的一種變形設計模式止喷,解決ViewController代碼臃腫馆类、View和Model模塊耦合嚴重兩個主要問題。抽出ViewModel來處理ViewController的業(yè)務邏輯弹谁,分離了UI代碼和業(yè)務邏輯乾巧,并且ViewModel監(jiān)聽model事件,一旦發(fā)生變化更新視圖预愤,很好的解決了視圖與模型的依賴性沟于。看下圖


Update.jpg

MVVM優(yōu)勢:

低耦合:View 可以獨立于Model變化和修改植康。
可重用性:可以把一些視圖邏輯放在一個 viewModel里面旷太,讓很多 view 重用這段視圖邏輯。
獨立開發(fā):開發(fā)人員可以專注于業(yè)務邏輯和數據的 viewModel開發(fā)销睁。
可測試:通常界面是比較難于測試的供璧,而 MVVM 模式可以針對 viewModel來進行測試。
兼容性:MVVM 可以兼容當下使用的MVC架構冻记, 增加應用的可測試性睡毒。
ReactiveCocoa(簡稱為RAC),是由Github開源的一個應用于iOS和OS開發(fā)的新框架冗栗。結合了幾種編程風格:函數式編程 和 響應式編程 演顾,使用RAC解決問題,就不需要考慮調用順序贞瞒,直接考慮結果偶房,把每一次操作都寫成一系列嵌套的方法中,使代碼高聚合军浆,方便管理棕洋。

函數式編程思想:是把操作盡量寫成一系列嵌套的函數或者方法調用。

響應式編程思想:不需要考慮調用順序乒融,只需要考慮結果掰盘,產生一個事件摄悯,事件傳播出去,然后影響結果愧捕。
RAC與MVVM架構設計的優(yōu)點
1奢驯、從上面RAC的用法可以看出,RAC的綁定次绘,這是常用的瘪阁。比如:用戶信息展示界面->登錄界面->登錄成功->回到用戶信息展示界面->展示用戶信息,以往我們的做法通常是通知邮偎,也可以用協(xié)議管跺、block什么的,一旦代碼量多了過后禾进,耦合度高豁跑,維護成本就會增加,而使用RAC的屬性綁定泻云、屬性聯合等一系列方法艇拍,將會有事半功倍的效果,這樣就可以解決相當多的需求了宠纯。

2卸夕、MVVM搭建思路里面會涉及大量的屬性綁定、事件傳遞來實現不同功能婆瓜,運用RAC能大量簡化代碼娇哆,充分的降低了代碼的耦合度,降低維護成本勃救,思路更清晰碍讨。

在MVC的基礎上,把C拆出一個ViewModel專門負責數據處理的事情蒙秒,就是MVVM勃黍。然后,為了讓View和ViewModel之間能夠有比較松散的綁定關系晕讲,于是我們使用ReactiveCocoa覆获,因為蘋果本身并沒有提供一個比較適合這種情況的綁定方法。iOS領域里KVO瓢省,Notification弄息,block,delegate和target-action都可以用來做數據通信勤婚,從而來實現綁定摹量,但都不如ReactiveCocoa提供的RACSignal來的優(yōu)雅,如果不用ReactiveCocoa,綁定關系可能就做不到那么松散那么好缨称,但并不影響它還是MVVM凝果。

二十八、圖形繪制

第一種繪圖形式UIBezierPath:在UIView的子類方法drawRect:中繪制一個藍色圓睦尽,使用UIKit在Cocoa為我們提供的當前上下文中完成繪圖任務器净。

-(void)drawRect:(CGRect)rect{
UIBezierPath*p=[UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
[[UIColorblueColor]setFill];
[pfill];
}

第二種繪圖形式:使用Core Graphics實現繪制藍色圓。

-(void)drawRect:(CGRect)rect{
CGContextRefcon=UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(con,CGRectMake(0,0,100,100));
CGContextSetFillColorWithColor(con,[UIColorblueColor].CGColor);
CGContextFillPath(con);
}

第三種繪圖形式:我將在UIView子類的drawLayer:inContext:方法中實現繪圖任務当凡。drawLayer:inContext:方法是一個繪制圖層內容的代理方法山害。為了能夠調用drawLayer:inContext:方法,我們需要設定圖層的代理對象沿量。但要注意粗恢,不應該將UIView對象設置為顯示層的委托對象,這是因為UIView對象已經是隱式層的代理對象欧瘪,再將它設置為另一個層的委托對象就會出問題。輕量級的做法是:編寫負責繪圖形的代理類匙赞。在MyView.h文件中聲明如下代碼:

@interface MyLayerDelegate:NSObject
@end
然后MyView.m文件中實現接口代碼:
@implementation MyLayerDelegate
-(void)drawLayer:(CALayer*)layerinContext:(CGContextRef)ctx{
UIGraphicsPushContext(ctx);
UIBezierPath*p=[UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
[[UIColorblueColor]setFill];
[pfill];
UIGraphicsPopContext();
}
@end
@interface MyView(){
MyLayerDelegate*_layerDeleagete;
}
@end
使用該圖層代理:
MyView*myView=[[MyViewalloc]initWithFrame:CGRectMake(0,0,320,480)];
CALayer*myLayer=[CALayerlayer];
_layerDelegate=[[MyLayerDelegatealloc]init];
myLayer.delegate=_layerDelegate;
[myView.layeraddSublayer:myLayer];
[myViewsetNeedsDisplay];//調用此方法佛掖,drawLayer:inContext:方法才會被調用。

第四種繪圖形式:使用Core Graphics在drawLayer:inContext:方法中實現同樣操作涌庭,代碼如下:

-(void)drawLayer:(CALayer*)layinContext:(CGContextRef)con{
CGContextAddEllipseInRect(con,CGRectMake(0,0,100,100));
CGContextSetFillColorWithColor(con,[UIColorblueColor].CGColor);
CGContextFillPath(con);
}

第五種繪圖形式:使用UIKit實現:

UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100),NO,0);
UIBezierPath*p=[UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
[[UIColorblueColor]setFill];
[pfill];
UIImage*im=UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

第六種繪圖形式:使用Core Graphics實現:

UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100),NO,0);
CGContextRefcon=UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(con,CGRectMake(0,0,100,100));
CGContextSetFillColorWithColor(con,[UIColorblueColor].CGColor);
CGContextFillPath(con);
UIImage*im=UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

二十九芥被、自動布局,通過布局優(yōu)先級來適配按鈕離底部的距離10-35坐榆?

在Autolayout中每個約束都有一個優(yōu)先級拴魄,優(yōu)先級的范圍是1 ~ 1000,默認創(chuàng)建的約束優(yōu)先級是最高的1000席镀。

三十匹中、怎么實現同步修改struct原數據的值

#include <stdio.h>

struct person {
    char *name;
    int age;
    int weight;
};

void changePersonl(struct person p);
void showPersonl(struct person p);

void showPersonl(struct person p) {
    printf("name:%s\n", p.name);
    printf("age:%d\ n", p.age);
    printf("weight:%d\n", p.weight);
}
void changePersonl(struct person p) {
    p.age = p.age - 2;
    p.weight = p.weight - 5;
    printf("in changePersonl()\n");
    showPersonl(p);
    return;
}

int main() {
    struct person bob, sue;

    sue.name = "sue";
    sue.age = 26;
    sue.weight = 50;

    bob.name = "bob";
    bob.age = 20;
    bob.weight = 60;
    showPersonl(sue);
    
    printf("********before changeperson*************\n");
    showPersonl(bob);
    
    printf("*************after changepersonl*************\n");
    changePersonl(bob);
    
    showPersonl(bob);
    return 0;
}
image.png

if 想要通過調用函數修改這個結構體中原始數據的值,需要定義指針豪诲,這個指針指向結構體
每次聲明person結構體的時候顶捷,需要寫struct person 太繁瑣了,所以可以用 typedef屎篱,typedef可以定義一個等價的量服赎,

typedef struct person Person;
struct person *sue,*bob;//兩個結構體指針
Person *sue,*bob;//兩個結構體指針
//兩句話等價                                                                                                                                                                                                                              
//一開始這兩個指針并沒有指向任何一個結構體,因為結構體還沒分配內存空間交播,
//先給結構體分配內存空間,并確定指針指向了它中的元素
//以下可以這么寫
Alloc_1D(sue,1,Person);
//它為結構體person分配內存重虑,并把指針sue與內存聯系到一起

通過指針訪問Person中的元素,和直接訪問Person中的元素不同秦士,
為了設定sue中的age元素可以有以下兩種寫法

(*sue).age=21;
sue->age=21;

三十一缺厉、 block 底層原理

一、Block定義

返回值類型 (^block變量名)(形參列表) = ^(形參列表) {
};

// 調用Block保存的代碼
block變量名(實參);

二、Block底層實現

block的底層實現是結構體芽死,和類的底層實現類似乏梁,都有isa指針,可以把block當成是一個對象关贵。下面通過創(chuàng)建一個控制臺程序遇骑,來窺探block的底層實現
block 的內存結構圖:


image.png

三、Block變量截獲

Block如何捕獲外部變量一:基本數據類型

局部變量截獲 是值截獲


image.png

一:auto變量

  auto變量:自動變量,離開作用域就會銷毀,一般我們創(chuàng)建的局部變量都是auto變量 ,比如 int age = 10,系統(tǒng)會在默認在前面加上auto int age = 10

首先我們要搞清楚,什么是捕獲,所謂捕獲外部變量,意思就是在block內部,創(chuàng)建一個變量來存放外部變量,這就叫做捕獲.先做一個小小的Test:

{
       int age = 10;
       void (^block)(void) = ^{
           NSLog(@"age is %d",age);
       };
       age = 20;
       block();
   }

輸出的age是10
二:static變量 局部靜態(tài)變量截獲 是指針截獲揖曾。

   auto int age = 10;
        static int height = 20;
        void (^block)(void) = ^{
            NSLog(@"age is %d, height is %d",age,height);
        };
        age = 20;
        height = 30;
        block();

打印的結果是age is 10, height is 30
我們看到,對于age是捕獲到內部,把外部age的值存起來,而對于height,是把外部變量的指針保存起來,所以, 我們在修改height時,會影響到block內部的值
總結:block捕獲外部基本數據類型變量: auto變量是值傳遞落萎;static變量是指針傳遞
三:全局變量
為什么全局變量不需要捕獲?
因為全局變量無論哪個函數都可以訪問,block內部當然也可以正常訪問,所以根本無需捕獲
為什么局部變量就需要捕獲呢?
因為作用域的問題,我們在一個函數中定義變量,在block內部訪問,本質上跨函數訪問,所以需要捕獲起來.

@implementation Person

- (void)test{
    void(^block)(void) = ^{
        NSLog(@"會不會捕獲self--%@",self);
    };
    block();
}

@end

答案是會捕獲self,我們看看底層代碼:

struct __Person__test_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test_block_desc_0* Desc;
  Person *self;
  __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

很顯然block內部的確聲明了一個Person *self用于保存self,既然block內部捕獲了self,那就說明self肯定是一個局部變量.那問題就來了,
為什么self會是一個局部變量?它應該是一個全局變量呀?我們看一下轉換后的test()方法:

static void _I_Person_test(Person * self, SEL _cmd) {
    void(*block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

我們 OC 中的test()方法時沒有參數的,但是轉換成 C++ 后就多了兩個參數self,_cmd,其實我們每個 OC 方法都會默認有這兩個參數,這也是為什么我們在每個方法中都能訪問self和_cmd,而參數就是局部變量,所以block就自然而然的捕獲了self.

總結:

局部變量截獲 是值截獲;局部靜態(tài)變量截獲 是指針截獲;全局變量不需要捕獲。
一:只要是局部變量,不管是auto 變量,還是static 變量,block都會捕獲. 不同的是,對于auto 變量,block是保存值,而static 變量 是保存的指針.
二:如果是全局變量,根本不需要捕獲,直接訪問
本篇只是講解了block捕獲基本數據類型的auto變量

[block如何捕獲外部變量(對象類型變量)

結論:
不管是 ARC 環(huán)境還是 MRC 環(huán)境,椞考簦空間的block是不會擁有外部對象的 ; 堆空間的block會擁有外部對象,在 ARC 環(huán)境下就是強引用,在 MRC 環(huán)境下就是 retain.

結論:
當block內部訪問了對象類型的auto變量時:
1:如果block是在棧上,將不會對auto變量產生強引用(不管是 MRC 或者 ARC)
2:如果block是在堆上,就說明block進行過copy操作,進行copy操作的block會自動調用block內部的__main_block_copy_0函數,__main_block_copy_0函數內部會根據auto變量的修飾符形成相應的強引用(retain)或者弱引用.
3:當block銷毀時,block會自動調用內部的dispose函數,dispose函數會自動調用內部的__main_block_dispose_0釋放引用的auto變量

__block 修飾的外部變量

對于用 block 修飾的外部變量引用练链,block 是復制其引用地址來實現訪問的。block可以修改block 修飾的外部變量的值奴拦。


image.png
__block int age = 10;
myBlock block = ^{
    NSLog(@"age = %d", age);
};
age = 18;
block();

輸出為:

age = 18

四媒鼓、Block的幾種形式

block有三種類型:

全局塊(_NSConcreteGlobalBlock)
棧塊(_NSConcreteStackBlock)
堆塊(_NSConcreteMallocBlock)


image.png

全局塊存在于全局內存中, 相當于單例.
棧塊存在于棧內存中, 超出其作用域則馬上被銷毀
堆塊存在于堆內存中, 是一個帶引用計數的對象, 需要自行管理其內存
1、不使用外部變量的block是全局block

NSLog(@"%@",[^{
        NSLog(@"globalBlock");
    } class]);
輸出:NSGlobalBlock

2错妖、使用外部變量并且未進行copy操作的block是棧block

NSInteger num = 10;
    NSLog(@"%@",[^{
        NSLog(@"stackBlock:%zd",num);
    } class]);
輸出:NSStackBlock

日常開發(fā)時是將block當做參數傳遞給方法:

- (void)testWithBlock:(void(^)(NSString *string))callback{
    NSString *string = @"string";
    callback(string);

    NSLog(@"%@",[callback class]);
}
NSInteger num = 10;
    [self testWithBlock:^(NSString *string) {
        NSLog(@"stackBlock:%zd",num);
    }];
輸出:NSGlobalBlock

3绿鸣、對棧block進行copy操作,就是堆block暂氯,而對全局block進行copy潮模,仍是全局block

比如堆1中的全局進行copy操作,即賦值:
void (^globalBlock)(void) = ^{
        NSLog(@"globalBlock");
    };

 NSLog(@"%@",[globalBlock class]);
輸出:NSGlobalBlock 仍是全局block
而對2中的棧block進行賦值操作:
NSInteger num = 10;

void (^mallocBlock)(void) = ^{

        NSLog(@"stackBlock:%zd",num);
    };

NSLog(@"%@",[mallocBlock class]);
輸出:NSMallocBlock

對棧blockcopy之后痴施,并不代表著棧block就消失了擎厢,左邊的mallock是堆block,右邊被copy的仍是棧block
c辣吃、block變量與forwarding

在copy操作之后动遭,既然block變量也被copy到堆上去了, 那么訪問該變量是訪問棧上的還是堆上的呢?forwarding 終于要閃亮登場了,如下圖:


image.png

通過forwarding, 無論是在block中還是 block外訪問block變量, 也不管該變量在棧上或堆上, 都能順利地訪問同一個__block變量神得。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末沽损,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子循头,更是在濱河造成了極大的恐慌绵估,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卡骂,死亡現場離奇詭異国裳,居然都是意外死亡,警方通過查閱死者的電腦和手機全跨,發(fā)現死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門缝左,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事渺杉∩呤” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵是越,是天一觀的道長耳舅。 經常有香客問我,道長倚评,這世上最難降的妖魔是什么浦徊? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮天梧,結果婚禮上盔性,老公的妹妹穿的比我還像新娘。我一直安慰自己呢岗,他們只是感情好冕香,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著后豫,像睡著了一般悉尾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上硬贯,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機與錄音陨收,去河邊找鬼饭豹。 笑死,一個胖子當著我的面吹牛务漩,可吹牛的內容都是我干的拄衰。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼饵骨,長吁一口氣:“原來是場噩夢啊……” “哼翘悉!你這毒婦竟也來了?” 一聲冷哼從身側響起居触,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤妖混,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后轮洋,有當地人在樹林里發(fā)現了一具尸體制市,經...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年弊予,在試婚紗的時候發(fā)現自己被綠了祥楣。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖误褪,靈堂內的尸體忽然破棺而出责鳍,到底是詐尸還是另有隱情,我是刑警寧澤兽间,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布历葛,位于F島的核電站,受9級特大地震影響渡八,放射性物質發(fā)生泄漏啃洋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一屎鳍、第九天 我趴在偏房一處隱蔽的房頂上張望宏娄。 院中可真熱鬧,春花似錦逮壁、人聲如沸孵坚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽卖宠。三九已至,卻和暖如春忧饭,著一層夾襖步出監(jiān)牢的瞬間扛伍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工词裤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留刺洒,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓吼砂,卻偏偏與公主長得像逆航,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子渔肩,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內容