前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >手写React-Router源码,深入理解其原理

手写React-Router源码,深入理解其原理

作者头像
蒋鹏飞
发布于 2020-10-15 02:06:06
发布于 2020-10-15 02:06:06
1.6K00
代码可运行
举报
文章被收录于专栏:进击的大前端进击的大前端
运行总次数:0
代码可运行

上一篇文章我们讲了React-Router的基本用法,并实现了常见的前端路由鉴权。本文会继续深入React-Router讲讲他的源码,套路还是一样的,我们先用官方的API实现一个简单的例子,然后自己手写这些API来替换官方的并且保持功能不变。

本文全部代码已经上传GitHub,大家可以拿下来玩玩:github.com/dennis-jian…

简单示例

本文用的例子是上篇文章开始那个不带鉴权的简单路由跳转例子,跑起来是这样子的:

我们再来回顾下代码,在app.js里面我们用Route组件渲染了几个路由:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React from 'react';
import {
  BrowserRouter as Router,
  Switch,
  Route,
} from "react-router-dom";
import Home from './pages/Home';
import Login from './pages/Login';
import Backend from './pages/Backend';
import Admin from './pages/Admin';


function App() {
  return (
    <Router>
      <Switch>
        <Route path="/login" component={Login}/>
        <Route path="/backend" component={Backend}/>
        <Route path="/admin" component={Admin}/>
        <Route path="/" component={Home}/>
      Switch>
    Router>
  );
}

export default App;
复制代码

每个页面的代码都很简单,只有一个标题和回首页的链接,比如登录页长这样,其他几个页面类似:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React from 'react';
import { Link } from 'react-router-dom';

function Login() {
  return (
    <>
      <h1>登录页h1>
      <Link to="/">回首页Link>
    
  );
}

export default Login;
复制代码

这样我们就完成了一个最简单的React-Router的应用示例,我们来分析下我们用到了他的哪些API,这些API就是我们今天要手写的目标,仔细一看,我们好像只用到了几个组件,这几个组件都是从react-router-dom导出来的:

BrowserRouter: 被我们重命名为了Router,他包裹了整个React-Router应用,感觉跟以前写过的react-reduxProvider类似,我猜是用来注入context之类的。 Route: 这个组件是用来定义具体的路由的,接收路由地址path和对应渲染的组件作为参数。 Switch:这个组件是用来设置匹配模式的,不加这个的话,如果浏览器地址匹配到了多个路由,这几个路由都会渲染出来,加了这个只会渲染匹配的第一个路由组件。 Link:这个是用来添加跳转链接的,功能类似于原生的a标签,我猜他里面也是封装了一个a标签。

BrowserRouter源码

我们代码里面最外层的就是BrowserRouter,我们先去看看他的源码干了啥,地址传送门:github.com/ReactTraini…

看了他的源码,我们发现BrowserRouter代码很简单,只是一个壳:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React from "react";
import { Router } from "react-router";
import { createBrowserHistory as createHistory } from "history";

class BrowserRouter extends React.Component {
  history = createHistory(this.props);

  render() {
    return <Router history={this.history} children={this.props.children} />;
  }
}
复制代码

在这个壳里面还引用了两个库react-routerhistoryBrowserRouter仅仅是调用historycreateHistory得到一个history对象,然后用这个对象渲染了react-routerRouter组件。看起来我们要搞懂react-router-dom的源码还必须得去看react-routerhistory的源码,现在我们手上有好几个需要搞懂的库了,为了看懂他们的源码,我们得先理清楚他们的结构关系。

React-Router的项目结构

React-Router的结构是一个典型的monorepomonorepo这两年开始流行了,是一种比较新的多项目管理方式,与之相对的是传统的multi-repo。比如React-Router的项目结构是这样的:

注意这里的packages文件夹下面有四个文件夹,这四个文件夹每个都可以作为一个单独的项目发布。之所以把他们放在一起,是因为他们之前有很强的依赖关系:

react-router:是React-Router的核心库,处理一些共用的逻辑 react-router-config:是React-Router的配置处理,我们一般不需要使用 react-router-dom:浏览器上使用的库,会引用react-router核心库 react-router-native:支持React-Native的路由库,也会引用react-router核心库

像这样多个仓库,发布多个包的情况,传统模式是给每个库都建一个git repo,这种方式被称为multi-repo。像React-Router这样将多个库放在同一个git repo里面的就是monorepo。这样做的好处是如果出了一个BUG或者加一个新功能,需要同时改react-routerreact-router-dommonorepo只需要一个commit一次性就改好了,发布也可以一起发布。如果是multi-repo则需要修改两个repo,然后分别发布两个repo,发布的时候还要协调两个repo之间的依赖关系。所以现在很多开源库都使用monorepo来将依赖很强的模块放在一个repo里面,比如React源码也是一个典型的monorepo

yarn有一个workspaces可以支持monorepo,使用这个功能需要在package.json里面配置workspaces,比如这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"workspaces": {
    "packages": [
      "packages/*"
    ]
  }

扯远了,monorepo可以后面单独开一篇文章来讲,这里讲这个主要是为了说明React-Router分拆成了多个包,这些包之间是有比较强的依赖的。

前面我们还用了一个库是history,这个库没在React-Routermonorepo里面,而是单独的一个库,因为官方把他写的功能很独立了,不一定非要结合React-Router使用,在其他地方也可以使用。

React-Router架构思路

我之前另一篇文章讲Vue-Router的原理提到过,前端路由实现无非这几个关键点:

  1. 监听URL的改变
  2. 改变vue-router里面的current变量
  3. 监视current变量
  4. 获取对应的组件
  5. render新组件

其实React-Router的思路也是类似的,只是React-Router将这些功能拆分得更散,监听URL变化独立成了history库,vue-router里面的current变量在React里面是用Context API实现的,而且放到了核心库react-router里面,一些跟平台相关的组件则放到了对应的平台库react-router-dom或者react-router-native里面。按照这个思路,我们自己写的React-Router文件夹下面也建几个对应的文件夹:

手写自己的React-Router

然后我们顺着这个思路一步一步的将我们代码里面用到的API替换成自己的。

BrowserRouter组件

BrowserRouter这个代码前面看过,直接抄过来就行:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React from "react";
import { Router } from "react-router";
import { createBrowserHistory as createHistory } from "history";

class BrowserRouter extends React.Component {
  history = createHistory(this.props);

  render() {
    return <Router history={this.history} children={this.props.children} />;
  }
}

export default BrowserRouter;
复制代码

react-router的Router组件

上面的BrowserRouter用到了react-routerRouter组件,这个组件在浏览器和React-Native端都有使用,主要获取当前路由并通过Context API将它传递下去:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React from "react";

import HistoryContext from "./HistoryContext.js";
import RouterContext from "./RouterContext.js";

/**
 * The public API for putting history on context.
 */
class Router extends React.Component {
  // 静态方法,检测当前路由是否匹配
  static computeRootMatch(pathname) {
    return { path: "/", url: "/", params: {}, isExact: pathname === "/" };
  }

  constructor(props) {
    super(props);

    this.state = {
      location: props.history.location     // 将history的location挂载到state上
    };

    // 下面两个变量是防御性代码,防止根组件还没渲染location就变了
    // 如果location变化时,当前根组件还没渲染出来,就先记下他,等当前组件mount了再设置到state上
    this._isMounted = false;
    this._pendingLocation = null;

    // 通过history监听路由变化,变化的时候,改变state上的location
    this.unlisten = props.history.listen(location => {
      if (this._isMounted) {
        this.setState({ location });
      } else {
        this._pendingLocation = location;
      }
    });
  }

  componentDidMount() {
    this._isMounted = true;

    if (this._pendingLocation) {
      this.setState({ location: this._pendingLocation });
    }
  }

  componentWillUnmount() {
    if (this.unlisten) {
      this.unlisten();
      this._isMounted = false;
      this._pendingLocation = null;
    }
  }

  render() {
    // render的内容很简单,就是两个context
    // 一个是路由的相关属性,包括history和location等
    // 一个只包含history信息,同时将子组件通过children渲染出来
    return (
      <RouterContext.Provider
        value={{
          history: this.props.history,
          location: this.state.location,
          match: Router.computeRootMatch(this.state.location.pathname),
        }}
      >
        <HistoryContext.Provider
          children={this.props.children || null}
          value={this.props.history}
        />
      RouterContext.Provider>
    );
  }
}

export default Router;
复制代码

上述代码是我精简过的代码,原版代码可以看这里。这段代码主要是创建了两个context,将路由信息和history信息放到了这两个context上,其他也没干啥了。关于React的Context API我在另外一篇文章详细讲过,这里不再赘述了。

history

前面我们其实用到了history的三个API:

createBrowserHistory: 这个是用在BrowserRouter里面的,用来创建一个history对象,后面的listen和unlisten都是挂载在这个API的返回对象上面的。 history.listen:这个是用在Router组件里面的,用来监听路由变化。 history.unlisten:这个也是在Router组件里面用的,是listen方法的返回值,用来在清理的时候取消监听的。

下面我们来实现这个history:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 创建和管理listeners的方法
function createEvents() {
  let handlers = [];

  return {
    push(fn) {
      handlers.push(fn);
      return function () {
        handlers = handlers.filter(handler => handler !== fn);
      };
    },
    call(arg) {
      handlers.forEach(fn => fn && fn(arg));
    }
  }
}

function createBrowserHistory() {
  const listeners = createEvents();
  let location = {
    pathname: '/',
  };

  // 路由变化时的回调
  const handlePop = function () {
    const currentLocation = {
      pathname: window.location.pathname
    }
    listeners.call(currentLocation);     // 路由变化时执行回调
  }

  // 监听popstate事件
  // 注意pushState和replaceState并不会触发popstate
  // 但是浏览器的前进后退会触发popstate
  // 我们这里监听这个事件是为了处理浏览器的前进后退
  window.addEventListener('popstate', handlePop);

  // 返回的history上有个listen方法
  const history = {
    listen(listener) {
      return listeners.push(listener);
    },
    location
  }

  return history;
}

export default createBrowserHistory;
复制代码

上述history代码是超级精简版的代码,官方源码很多,还支持其他功能,我们这里只拎出来核心功能,对官方源码感兴趣的看这里:github.com/ReactTraini…

Route组件

我们前面的应用里面还有个很重要的组件是Route组件,这个组件是用来匹配路由和具体的组件的。这个组件看似是从react-router-dom里面导出来的,其实他只是相当于做了一个转发,原封不动的返回了react-routerRoute组件:

这个组件其实只有一个作用,就是将参数上的path拿来跟当前的location做对比,如果匹配上了就渲染参数上的component就行。为了匹配pathlocation,还需要一个辅助方法matchPath我直接从源码抄这个方法了。大致思路是将我们传入的参数path转成一个正则,然后用这个正则去匹配当前的pathname

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import pathToRegexp from "path-to-regexp";

const cache = {};
const cacheLimit = 10000;
let cacheCount = 0;

function compilePath(path, options) {
  const cacheKey = `${options.end}${options.strict}${options.sensitive}`;
  const pathCache = cache[cacheKey] || (cache[cacheKey] = {});

  if (pathCache[path]) return pathCache[path];

  const keys = [];
  const regexp = pathToRegexp(path, keys, options);
  const result = { regexp, keys };

  if (cacheCount < cacheLimit) {
    pathCache[path] = result;
    cacheCount++;
  }

  return result;
}

/**
 * Public API for matching a URL pathname to a path.
 */
function matchPath(pathname, options = {}) {
  if (typeof options === "string" || Array.isArray(options)) {
    options = { path: options };
  }

  const { path, exact = false, strict = false, sensitive = false } = options;

  const paths = [].concat(path);

  return paths.reduce((matched, path) => {
    if (!path && path !== "") return null;
    if (matched) return matched;

    const { regexp, keys } = compilePath(path, {
      end: exact,
      strict,
      sensitive
    });
    const match = regexp.exec(pathname);

    if (!match) return null;

    const [url, ...values] = match;
    const isExact = pathname === url;

    if (exact && !isExact) return null;

    return {
      path, // the path used to match
      url: path === "/" && url === "" ? "/" : url, // the matched portion of the URL
      isExact, // whether or not we matched exactly
      params: keys.reduce((memo, key, index) => {
        memo[key.name] = values[index];
        return memo;
      }, {})
    };
  }, null);
}

export default matchPath;
复制代码

然后是Route组件,调用下matchPath来看下当前路由是否匹配就行了,当前路由记得从RouterContext里面拿:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React from "react";

import RouterContext from "./RouterContext.js";
import matchPath from "./matchPath.js";

/**
 * The public API for matching a single path and rendering.
 */
class Route extends React.Component {
  render() {
    return (
      <RouterContext.Consumer>
        {context => {
          // 从RouterContext获取location
          const location = context.location;
          const match = matchPath(location.pathname, this.props);  // 调用matchPath检测当前路由是否匹配

          const props = { ...context, location, match };

          let { component } = this.props;

          // render对应的component之前先用最新的参数match更新下RouterContext
          // 这样下层嵌套的Route可以拿到对的值
          return (
            <RouterContext.Provider value={props}>
              {props.match
                ? React.createElement(component, props)
                : null}
            RouterContext.Provider>
          );
        }}
      RouterContext.Consumer>
    );
  }
}

export default Route;
复制代码

上述代码也是精简过的,官方源码还支持函数组件和render方法等,具体代码可以看这里:github.com/ReactTraini…

其实到这里,React-Router的核心功能已经实现了,但是我们开始的例子中还用到了SwitchLink组件,我们也一起来把它实现了吧。

Switch组件

我们上面的Route组件的功能是只要path匹配上当前路由就渲染组件,也就意味着如果多个Routepath都匹配上了当前路由,这几个组件都会渲染。所以Switch组件的功能只有一个,就是即使多个Routepath都匹配上了当前路由,也只渲染第一个匹配上的组件。要实现这个功能其实也不难,把Switchchildren拿出来循环,找出第一个匹配的child,给它添加一个标记属性computedMatch,顺便把其他的child全部干掉,然后修改下Route的渲染逻辑,先检测computedMatch,如果没有这个再使用matchPath自己去匹配:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React from "react";

import RouterContext from "./RouterContext.js";
import matchPath from "./matchPath.js";

class Switch extends React.Component {
  render() {
    return (
      
        {context => {
          const location = context.location;     // 从RouterContext获取location

          let element, match;     // 两个变量记录第一次匹配上的子元素和match属性

          // 使用React.Children.forEach来遍历子元素,而不能使用React.Children.toArray().find()
          // 因为toArray会给每个子元素添加一个key,这会导致两个有同样component,但是不同URL的重复渲染
          React.Children.forEach(this.props.children, child => {
            // 先检测下match是否已经匹配到了
            // 如果已经匹配过了,直接跳过
            if (!match && React.isValidElement(child)) {
              element = child;

              const path = child.props.path;

              match = matchPath(location.pathname, { ...child.props, path });
            }
          });

          // 最终组件的返回值只是匹配子元素的一个拷贝,其他子元素被忽略了
          // match属性会被塞给拷贝元素的computedMatch
          // 如果一个都没匹配上,返回null
          return match
            ? React.cloneElement(element, { location, computedMatch: match })   
            : null;
        }}
      
    );
  }
}

export default Switch;
复制代码

然后修改下Route组件,让他先检查computedMatch

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// ... 省略其他代码 ...
const match = this.props.computedMatch
              ? this.props.computedMatch
              : matchPath(location.pathname, this.props);  // 调用matchPath检测当前路由是否匹配

Switch组件其实也是在react-router里面,源码跟我们上面写的差不多:github.com/ReactTraini…

Link组件

Link组件功能也很简单,就是一个跳转,浏览器上要实现一个跳转,可以用a标签,但是如果直接使用a标签可能会导致页面刷新,所以不能直接使用它,而应该使用history APIhistory API具体文档可以看这里。我们这里要跳转URL可以直接使用history.pushState。使用history.pushState需要注意一下几点:

  1. history.pushState只会改变history状态,不会刷新页面。换句话说就是你用了这个API,你会看到浏览器地址栏的地址变化了,但是页面并没有变化。
  2. 当你使用history.pushState或者history.replaceState改变history状态的时候,popstate事件并不会触发,所以history里面的回调不会自动调用,当用户使用history.push的时候我们需要手动调用回调函数。
  3. history.pushState(state, title[, url])接收三个参数,第一个参数state是往新路由传递的信息,可以为空,官方React-Router会往里面加一个随机的key和其他信息,我们这里直接为空吧,第二个参数title目前大多数浏览器都不支持,可以直接给个空字符串,第三个参数url是可选的,是我们这里的关键,这个参数是要跳往的目标地址。
  4. 由于history已经成为了一个独立的库,所以我们应该将history.pushState相关处理加到history库里面。

我们先在history里面新加一个APIpush,这个API会调用history.pushState并手动执行回调:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// ... 省略其他代码 ...
push(url) {
  const history = window.history;
  // 这里pushState并不会触发popstate
  // 但是我们仍然要这样做,是为了保持state栈的一致性
  history.pushState(null, '', url);

  // 由于push并不触发popstate,我们需要手动调用回调函数
  location = { pathname: url };
  listeners.call(location);
}

上面说了我们直接使用a标签会导致页面刷新,但是如果不使用a标签,Link组件应该渲染个什么标签在页面上呢?可以随便渲染个spandiv什么的都行,但是可能会跟大家平时的习惯不一样,还可能导致一些样式失效,所以官方还是选择了渲染一个a标签在这里,只是使用event.preventDefault禁止了默认行为,然后用history api自己实现了跳转,当然你可以自己传component参数进去改变默认的a标签。因为是a标签,不能兼容native,所以Link组件其实是在react-router-dom这个包里面:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React from "react";
import RouterContext from "../react-router/RouterContext";

// LinkAnchor只是渲染了一个没有默认行为的a标签
// 跳转行为由传进来的navigate实现
function LinkAnchor({navigate, ...rest}) {
  let props = {
    ...rest,
    onClick: event => {
      event.preventDefault();
      navigate();
    }
  }

  return <a {...props} />;
}

function Link({
  component = LinkAnchor,  // component默认是LinkAnchor
  to,
  ...rest
}) {
  return (
    <RouterContext.Consumer>
      {context => {
        const { history } = context;     // 从RouterContext获取history对象

        const props = {
          ...rest,
          href: to,
          navigate() {
            history.push(to);
          }
        };

        return React.createElement(component, props);
      }}
    RouterContext.Consumer>
  );
}

export default Link;
复制代码

上述代码是精简版的Link,基本逻辑跟官方源码一样:github.com/ReactTraini…

到这里开头示例用到的全部API都换成了我们自己的,其实也实现了React-Router的核心功能。但是我们只实现了H5 history模式,hash模式并没有实现,其实有了这个架子,添加hash模式也比较简单了,基本架子不变,在react-router-dom里面添加一个HashRouter,他的基本结构跟BrowserRouter是一样的,只是他会调用historycreateHashHistorycreateHashHistory里面不仅仅会去监听popstate,某些浏览器在hash变化的时候不会触发popstate,所以还需要监听hashchange事件。对应的源码如下,大家可以自行阅读:

HashRouter: github.com/ReactTraini…

createHashHistory: github.com/ReactTraini…

总结

React-Router的核心源码我们已经读完了,下面我们来总结下:

  1. React-Router因为有跨平台的需求,所以分拆了好几个包,这几个包采用monorepo的方式管理:
代码语言:txt
AI代码解释
复制
1. `react-router`是核心包,包含了大部分逻辑和组件,处理`context`和路由匹配都在这里。
2. `react-router-dom`是浏览器使用的包,像`Link`这样需要渲染具体的`a`标签的组件就在这里。
3. `react-router-native`是`react-native`使用的包,里面包含了`android`和`ios`具体的项目。浏览器事件监听也单独独立成了一个包
  1. React-Router实现时核心逻辑如下:
代码语言:txt
AI代码解释
复制
1. 使用不刷新的路由API,比如`history`或者`hash`
2. 提供一个事件处理机制,让`React`组件可以监听路由变化。
3. 提供操作路由的接口,当路由变化时,通过事件回调通知`React`。
4. 当路由事件触发时,将变化的路由写入到`React`的响应式数据上,也就是将这个值写到根`router`的`state`上,然后通过`context`传给子组件。
5. 具体渲染时将路由配置的`path`和当前浏览器地址做一个对比,匹配上就渲染对应的组件。在使用
代码语言:txt
AI代码解释
复制
1. 原生`history.pushState`和`history.replaceState`并不会触发`popstate`,要通知`React`需要我们手动调用回调函数。
2. 浏览器的前进后退按钮会触发`popstate`事件,所以我们还是要监听`popstate`,目的是兼容前进后退按钮。

本文全部代码已经上传GitHub,大家可以拿下来玩玩:github.com/dennis-jian…

参考资料

官方文档:reactrouter.com/web/guides/…

GitHub源码地址:github.com/ReactTraini…

文章的最后,感谢你花费宝贵的时间阅读本文,如果本文给了你一点点帮助或者启发,请不要吝啬你的赞和GitHub小星星,你的支持是作者持续创作的动力。

作者博文GitHub项目地址: github.com/dennis-jian…

作者掘金文章汇总:juejin.im/post/684490…

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
iOS block探究(二): 深入理解你要知道的block都在这里
你要知道的block都在这里 转载请注明出处 https://cloud.tencent.com/developer/user/1605429 三种block类型 NSGlobalBlock 如果block不捕获外部变量,那么在ARC环境下就是创建一个全局block。全局block存储在全局内存中,不需要在每次调用的时候都在栈中创建,块所使用的整个内存区在编译期已经确定了,因此这种块是一种单例,不需要多次创建。 NSMallocBlock 如果block捕获外部变量,那么在ARC环境下就是创了一个堆区blo
WWWWDotPNG
2018/04/10
1K0
Block那些事
__main_block_func_0这个是block的执行函数,参数是__main_block_impl_0类型。
Helloted
2022/06/07
4340
Block那些事
3. __block  __weak  __strong   这都是做什么的
1.1 局部变量 局部自动变量,在Block中可被读取。Block定义时copy变量的值,在Block中作为常量使用,所以即使变量的值在Block外改变,也不影响他在Block中的值,Block此时对局部变量只是做了值传递的操作。 1.2 static 修饰的全局变量 因为全局变量或静态变量在内存中的地址是固定的,Block在读取该变量值的时候是直接从其所在内存地址读出,获取到的是最新值,而不是在定义时copy的常量。 1.3 对OC对象的截获 NSMutableArray *array = [NSMut
陈雨尘
2018/06/07
6010
深入研究Block捕获外部变量和__block实现原理
Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这个新功能“Blocks”。从那开始,Block就出现在iOS和Mac系统各个API中,并被大家广泛使用。一句话来形容Blocks,带有自动变量(局部变量)的匿名函数。
一缕殇流化隐半边冰霜
2018/08/30
1.2K0
深入研究Block捕获外部变量和__block实现原理
Block原理探究(下篇)-捕获变量分析及__block原理
主要内容: 1.分析Block捕获外部变量的过程 2.理解Block修改外部变量的限制 3.分析__block存储域类说明符的原理 4.理解__block变量的存储域 5.探究Block对对象的捕获过程 6.Block的循环引用问题
梧雨北辰
2019/10/08
1.7K0
Block原理探究(下篇)-捕获变量分析及__block原理
iOS-从循环引用看Block
说明:在block区内已经释放,到self.block()调用时已经被释放,所以值为null。
Wilbur-L
2020/12/15
1.2K0
iOS-从循环引用看Block
《Objective-C-高级编程》干货三部曲(二):Blocks篇
这一章讲解了Block相关的知识。因为作者将Objective-C的代码转成了C++的代码,所以第一次看的时候非常吃力,我自己也不记得看了多少遍了。
用户1740424
2018/07/23
1.1K0
《Objective-C-高级编程》干货三部曲(二):Blocks篇
利用Clang探究__block的本质
上一篇文章利用Clang探究block的本质得出的结论是block的本质是一个结构体对象,该对象包含调用block时要执行的函数指针。当我们在Objective-C层面调用block的时候,底层就可以通过调用block对应的C++结构体对象的函数指针来实现同等操作。
VV木公子
2019/11/04
7.3K0
iOS底层原理总结 - 探寻block的本质(二)
上一篇文章iOS底层原理总结 - 探寻block的本质(一)中已经介绍过block的底层本质实现以及了解了变量的捕获,本文继续探寻block的本质。 block对对象变量的捕获 block一般使用过程中都是对对象变量的捕获,那么对象变量的捕获同基本数据类型变量相同吗? 查看一下代码思考:当在block中访问的为对象类型时,对象什么时候会销毁? typedef void (^Block)(void); int main(int argc, const char * argv[]) { @autore
xx_Cc
2018/06/04
1K0
老司机出品——源码解析之从Block说开去
近来把《iOS与OS X多线程和内存管理》这本书又掏出来看了一遍,这本书前前后后加起来看了能有三四遍了,每次看都有新的理解。现在就把个人对Block的一些理解记录下来。
老司机Wicky
2018/08/22
4650
老司机出品——源码解析之从Block说开去
iOS 开发:『Blocks』详尽总结 (二)底层原理
在第一篇中我们讲解了 Blocks 的基本使用,也知道了 Blocks 是 带有局部变量的匿名函数。但是 Block 的实质究竟是什么呢?类型?变量?还是什么黑科技?
程序员充电站
2019/04/01
7080
iOS 开发:『Blocks』详尽总结 (二)底层原理
面试驱动技术 - Block看我就够了【干货】
Block 在 iOS 算比较常见常用且常考的了,现在面试中,要么没面试题,有面试题的,基本都会考到 block 的点。本文特别干!(但是初中级iOSer应该能有所收获~)
小蠢驴打代码
2019/03/15
7890
面试驱动技术 - Block看我就够了【干货】
iOS Block的本质(四)
typedef void (^Block)(void); struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(void); void (*dispose)(void); }; struct __Block_byref_age_0 { void *__isa; struct __Block_byref_age_0 *__forwarding; int __flags; int __size; int age; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; struct __Block_byref_age_0 *age; // by ref }; int main(int argc, const char * argv[]) { @autoreleasepool { __block int age = 10; Block block = ^{ age = 20; NSLog(@"age is %d",age); }; block(); struct __main_block_impl_0 *blockImpl = (__bridge struct __main_block_impl_0 *)block; NSLog(@"%p",&age); } return 0; }
用户1941540
2019/02/15
6940
iOS Block的本质(四)
【IOS开发高级系列】Block专题
http://blog.devtang.com/blog/2013/07/28/a-look-inside-blocks/
江中散人_Jun
2023/10/16
3170
【IOS开发高级系列】Block专题
iOS 进阶之 Block 的本质及原理
相信稍微有点开发经验的开发者,应该都对 block 有一定的了解。刚开始使用 block 的时候,可能觉得很方便。知道怎么去用,但是知其然知其所以然的态度来分析一下 block 的原理和内部结构是什么。
网罗开发
2021/01/29
6710
iOS 进阶之 Block 的本质及原理
block内部实现
c. 我们先看一下__ main_block_impl_0结构体和 __ main_block_func_0 还有__main_block_desc_0_DATA都代表写什么
老沙
2019/09/27
4140
《Objective-C高级编程》温故知新之"Blocks"
在计算机科学中,此概念也称为闭包(Closure)、lambda计算等。Swift中称作闭包
Dwyane
2018/09/26
6340
# iOS Block的本质(三)
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; Person *__weak person; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _person, int flags=0) : person(_weakPerson) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
用户1941540
2019/02/15
4840
# iOS Block的本质(三)
Block原理详解(一)
今天这篇文章也是在前面这三篇文章的基础上,再结合自己最近的一些工作感悟,进行内容的完善。
拉维
2021/04/16
6260
Block原理探究(上篇)-Block本质及存储域问题
主要内容: 1.理解Block的本质 2.理解Block的存储域分类 3.理解Block的Copy原理
梧雨北辰
2019/10/08
1K0
推荐阅读
相关推荐
iOS block探究(二): 深入理解你要知道的block都在这里
更多 >
LV.0
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文