精华内容
下载资源
问答
  • http://www.tuicool.com/articles/fqaqqmF之所以标题是《 React 常见的面试题》,其实只是想起一个比《在 React 里面,可以知道也可以不知道的事, 但是会发现他们确实有用》要简单明了的标题而已。...

    根据记录,问这些问题可能不是深入了解他们在使用 React 方面的经验的最佳方式。http://www.tuicool.com/articles/fqaqqmF

    之所以标题是《 React 常见的面试题》,其实只是想起一个比《在 React 里面,你可以知道也可以不知道的事, 但是你会发现他们确实很有用》要简单明了的标题而已。http://johannlai.com/2017/04/09/React-Interview-Questions/

    原文链接: React Interview Questions

    作者: Tyler.Google Developer Expert and a partner at React Training where we teach React online

    翻译:Johann Lai

    当你调用setState的时候,发生了什么事?

    当调用 setState 时,React会做的第一件事情是将传递给 setState 的对象合并到组件的当前状态。这将启动一个称为和解(reconciliation)的过程。和解(reconciliation)的最终目标是以最有效的方式,根据这个新的状态来更新UI。 为此,React将构建一个新的 React 元素树(您可以将其视为 UI 的对象表示)。

    一旦有了这个树,为了弄清 UI 如何响应新的状态而改变,React 会将这个新树与上一个元素树相比较( diff )。

    通过这样做, React 将会知道发生的确切变化,并且通过了解发生什么变化,只需在绝对必要的情况下进行更新即可最小化 UI 的占用空间。

    在 React 当中 Element 和 Component 有何区别?

    简单地说,一个 React element 描述了你想在屏幕上看到什么。换个说法就是,一个 React element 是一些 UI 的对象表示。

    一个 React Component 是一个函数或一个类,它可以接受输入并返回一个 React element t(通常是通过 JSX ,它被转化成一个 createElement 调用)。

    有关更多信息,请查看 React Elements vs React Components

    什么时候在功能组件( Class Component )上使用类组件( Functional Component )?

    如果您的组件具有状态( state )或生命周期方法,请使用 Class 组件。否则,使用功能组件

    什么是 React 的 refs ,为什么它们很重要?

    refs就像是一个逃生舱口,允许您直接访问DOM元素或组件实例。为了使用它们,您可以向组件添加一个 ref 属性,该属性的值是一个回调函数,它将接收底层的 DOM 元素或组件的已挂接实例,作为其第一个参数。

    class UnControlledForm extends Component {
      handleSubmit = () => {
        console.log("Input Value: ", this.input.value)
      }
      render () {
        return (
          <form onSubmit={this.handleSubmit}>
            <input
              type='text'
              ref={(input) => this.input = input} />
            <button type='submit'>Submit</button>
          </form>
        )
      }
    }
    

    以上注意到我们的输入字段有一个 ref 属性,其值是一个函数。该函数接收我们然后放在实例上的实际的 DOM 元素,以便在 handleSubmit 函数内部访问它。经常误解的是,您需要使用类组件才能使用 ref ,但 ref 也可以通过利用 JavaScript 中的 闭包 与 功能组件( functional components )一起使用。

    function CustomForm ({handleSubmit}) {
      let inputElement
      return (
        <form onSubmit={() => handleSubmit(inputElement.value)}>
          <input
            type='text'
            ref={(input) => inputElement = input} />
          <button type='submit'>Submit</button>
        </form>
      )
    }
    

    React 中的keys是什么,为什么它们很重要?

    keys是什么帮助 React 跟踪哪些项目已更改、添加或从列表中删除。

      return (
        <ul>
          {this.state.todoItems.map(({task, uid}) => {
            return <li key={uid}>{task}</li>
          })}
        </ul>
      )
    }
    

    每个 keys 在兄弟元素之间是独一无二的。我们已经谈过几次关于和解(reconciliation)的过程,而且这个和解过程(reconciliation)中的一部分正在执行一个新的元素树与最前一个的差异。keys 使处理列表时更加高效,因为 React 可以使用子元素上的 keys 快速知道元素是新的还是在比较树时才被移动。

    而且 keys 不仅使这个过程更有效率,而且没有 keys ,React 不知道哪个本地状态对应于移动中的哪个项目。所以当你 map 的时候,不要忽略了 keys 。

    看下面的代码: 如果您在 下创建了一个 React 元素, 的组件定义将如何?

    <Twitter username='tylermcginnis33'>
      {(user) => user === null
        ? <Loading />
        : <Badge info={user} />}
    </Twitter>
    
    import React, { Component, PropTypes } from 'react'
    import fetchUser from 'twitter'
    // fetchUser接收用户名返回 promise
    // 当得到 用户的数据的时候 ,返回resolve 状态
    
    class Twitter extends Component {
      // 在这里写下你的代码
    }
    

    如果你不熟悉渲染回调模式(render callback pattern),这将看起来有点奇怪。在这种模式中,一个组件接收一个函数作为它的 child。注意上面包含在 标签内的内容。 Twitter 组件的 child 是一个函数,而不是你曾经习以为常的一个组件。 这意味着在实现 Twitter 组件时,我们需要将 props.children 作为一个函数来处理。

    以下是我的答案。

    import React, { Component, PropTypes } from 'react'
    import fetchUser from 'twitter'
    
    class Twitter extends Component {
      state = {
        user: null,
      }
      static propTypes = {
        username: PropTypes.string.isRequired,
      }
      componentDidMount () {
        fetchUser(this.props.username)
          .then((user) => this.setState({user}))
      }
      render () {
        return this.props.children(this.state.user)
      }
    }
    

    值得注意的是,正如我上面提到的,我通过调用它并传递给 user 来把 props.children 处理为为一个函数。

    这种模式的好处是我们已经将我们的父组件与我们的子组件分离了。父组件管理状态,父组件的消费者可以决定以何种方式将从父级接收的参数应用于他们的 UI。

    为了演示这一点,我们假设在另一个文件中,我们要渲染一个 Profile 而不是一个 Badge, ,因为我们使用渲染回调模式,所以我们可以轻松地交换 UI ,而不用改变我们对父(Twitter)组件的实现。

    <Twitter username='tylermcginnis33'>
      {(user) => user === null
        ? <Loading />
        : <Profile info={user} />}
    </Twitter>
    

    受控组件( controlled component )与不受控制的组件( uncontrolled component )有什么区别?

    React 的很大一部分是这样的想法,即组件负责控制和管理自己的状态。

    当我们将 native HTML 表单元素( input, select, textarea 等)投入到组合中时会发生什么?我们是否应该使用 React 作为“单一的真理来源”,就像我们习惯使用React一样? 或者我们是否允许表单数据存在 DOM 中,就像我们习惯使用HTML表单元素一样? 这两个问题是受控(controlled) VS 不受控制(uncontrolled)组件的核心。

    受控组件是React控制的组件,也是表单数据的唯一真理来源。

    如下所示, username 不存在于 DOM 中,而是以我们的组件状态存在。每当我们想要更新 username 时,我们就像以前一样调用setState。

    class ControlledForm extends Component {
      state = {
        username: ''
      }
      updateUsername = (e) => {
        this.setState({
          username: e.target.value,
        })
      }
      handleSubmit = () => {}
      render () {
        return (
          <form onSubmit={this.handleSubmit}>
            <input
              type='text'
              value={this.state.username}
              onChange={this.updateUsername} />
            <button type='submit'>Submit</button>
          </form>
        )
      }
    }
    

    不受控制( uncontrolled component )的组件是您的表单数据由 DOM 处理,而不是您的 React 组件。

    我们使用 refs 来完成这个。

    class UnControlledForm extends Component {
      handleSubmit = () => {
        console.log("Input Value: ", this.input.value)
      }
      render () {
        return (
          <form onSubmit={this.handleSubmit}>
            <input
              type='text'
              ref={(input) => this.input = input} />
            <button type='submit'>Submit</button>
          </form>
        )
      }
    }
    

    虽然不受控制的组件通常更容易实现,因为您只需使用引用从DOM获取值,但是通常建议您通过不受控制的组件来支持受控组件。

    主要原因是受控组件 支持即时字段验证 ,允许您有条件地禁用/启用按钮,强制输入格式,并且更多的是 『the React way』。

    在哪个生命周期事件中你会发出 AJAX 请求,为什么?

    AJAX 请求应该在 componentDidMount 生命周期事件中。 有几个原因:

    • Fiber,是下一次实施React的和解算法,将有能力根据需要启动和停止渲染,以获得性能优势。其中一个取舍之一是 componentWillMount ,而在其他的生命周期事件中出发 AJAX 请求,将是具有 “非确定性的”。 这意味着 React 可以在需要时感觉到不同的时间开始调用 componentWillMount。这显然是AJAX请求的不好的方式。

    -您不能保证在组件挂载之前,AJAX请求将无法 resolve。如果这样做,那意味着你会尝试在一个未挂载的组件上设置 StState,这不仅不会起作用,反而会对你大喊大叫。 在 componentDidMount 中执行 AJAX 将保证至少有一个要更新的组件。

    shouldComponentUpdate 应该做什么,为什么它很重要?

    上面我们讨论了 reconciliation ,什么是 React 在 setState 被调用时所做的。在生命周期方法 shouldComponentUpdate 中,允许我们选择退出某些组件(和他们的子组件)的 reconciliation 过程。

    我们为什么要这样做?

    如上所述,“和解( reconciliation )的最终目标是以最有效的方式,根据新的状态更新用户界面”。如果我们知道我们的用户界面(UI)的某一部分不会改变,那么没有理由让 React 很麻烦地试图去弄清楚它是否应该渲染。通过从 shouldComponentUpdate 返回 false,React 将假定当前组件及其所有子组件将保持与当前组件相同。

    您如何告诉React 构建(build)生产模式,该做什么?

    通常,您将使用Webpack的 DefinePlugin 方法将 NODE_ENV 设置为 production。这将剥离像 propType 验证和额外的警告。除此之外,还有一个好主意,可以减少你的代码,因为React使用 Uglify 的 dead-code 来消除开发代码和注释,这将大大减少你的包的大小。

    为什么要使用 React.Children.map(props.children,()=>) 而不是 props.children.map(()=>)

    因为不能保证props.children将是一个数组。

    以此代码为例,

    <Parent>
      <h1>Welcome.</h1>
    </Parent>
    

    在父组件内部,如果我们尝试使用 props.children.map 映射孩子,则会抛出错误,因为 props.children 是一个对象,而不是一个数组。

    如果有多个子元素,React 只会使props.children成为一个数组。就像下面这样:

    <Parent>
      <h1>Welcome.</h1>
      <h2>props.children will now be an array</h2>
    </Parent>
    

    这就是为什么你喜欢 React.Children.map ,因为它的实现考虑到 props.children 可能是一个数组或一个对象。

    描述事件在React中的处理方式。

    为了解决跨浏览器兼容性问题,您的 React 中的事件处理程序将传递 SyntheticEvent 的实例,它是 React 的浏览器本机事件的跨浏览器包装器。

    这些 SyntheticEvent 与您习惯的原生事件具有相同的接口,除了它们在所有浏览器中都兼容。有趣的是,React 实际上并没有将事件附加到子节点本身。React 将使用单个事件监听器监听顶层的所有事件。这对于性能是有好处的,这也意味着在更新DOM时,React 不需要担心跟踪事件监听器。

    createElement 和 cloneElement 有什么区别?

    createElement 是 JSX 被转载到的,是 React 用来创建 React Elements 的内容(一些 UI 的对象表示)cloneElement用于克隆元素并传递新的 props。他们钉住了这两个��的命名。

    可以选择性地传递给 setState 的第二个参数是什么,它的目的是什么?

    一个回调函数,当setState结束并 re-rendered 该组件时将被调用。一些没有说出来的东西是 setState 是 异步 的,这就是为什么它需要一个第二个回调函数。通常最好使用另一个生命周期方法,而不是依赖这个回调函数,但是很高兴知道它存在。

    this.setState(
      { username: 'tylermcginnis33' },
      () => console.log('setState has finished and the component has re-rendered.')
    )
    

    这段代码有什么问题?

    this.setState((prevState, props) => {
      return {
        streak: prevState.streak + props.count
      }
    })
    

    没毛病。但是这种写法很少被使用,并不是众所周知的,就是你也可以传递一个函数给setState,它接收到先前的状态和道具并返回一个新的状态,正如我们在上面所做的那样。它不仅没有什么问题,而且如果您根据以前的状态(state)设置状态,推荐使用这种写法。

    转自http://johannlai.com/2017/04/09/React-Interview-Questions/ 

    展开全文
  • YOLOv4原文翻译 - v4它终于来了!

    万次阅读 多人点赞 2020-12-20 16:58:31
    论文原文:https://arxiv.org/abs/2004.10934源码:https://github.com/AlexeyAB/darknet 写在前面 不久之前,YOLO创始人Redmon宣布退出CV界,很多人都觉得真的...那天中午,看到v4提出的消息的时候,真的高兴...

    论文原文:https://arxiv.org/abs/2004.10934
    源码:https://github.com/AlexeyAB/darknet

    写在前面

    不久之前,YOLO创始人Redmon宣布退出CV界,真的很可惜!对我而言,YOLO系列的算法是我研究的第一个目标检测的算法,我对YOLO算法的感情不言而喻。

    那天中午,当看到v4提出的消息的时候,真的很高兴!当看到第一作者不再是Redmon的时候,心中还是有点抗拒的,但是,作为Redmon认可的v4以及了解v4接棒人AB对整个YOLO算法精度和速度提升的效果之后,我第一时间将论文下载了下来,花了将近两天的时间精读论文并且翻译了原文。

    因为本人能力有限,难免会有翻译不恰当的地方,还请各位看官老爷斧正。最后,欢迎大家和我交流~

     

    想要训练自己的数据集的朋友可以关注我的这篇博客:YOLOv4训练自己的数据集

    需要在windows下进行编译的朋友,可以关注我的这篇博客:windows下编译yolov4

    需要在windows下使用yolov4的动态链接库的,可以关注我的这篇博客:windows下使用yolov4动态链接库


    0 摘要

    目前有很多可以提高CNN准确性的算法。这些算法的组合在庞大数据集上进行测试、对实验结果进行理论验证都是非常必要的。有些算法只在特定的模型上有效果,并且只对特定的问题有效,或者只对小规模的数据集有效;然而有些算法,比如batch-normalization和residual-connections,对大多数的模型、任务和数据集都适用。我们认为这样通用的算法包括:Weighted-Residual-Connections(WRC), Cross-Stage-Partial-connections(CSP), Cross mini-Batch Normalization(CmBN), Self-adversarial-training(SAT)以及Mish-activation。我们使用了新的算法:WRC, CSP, CmBN, SAT, Mish activation, Mosaic data augmentation, CmBN, Dropblock regularization 和CIoU loss以及它们的组合,获得了最优的效果:在MS COCO数据集上的AP值为43.5%(65.7% AP50),在Tesla V100上的实时推理速度为65FPS

    从摘要中我们基本上可以看出:v4实际上就是保留Darknet作为backbone,然后通过大量的实验研究了众多普适性算法对网络性能的影响,然后找到了它们最优的组合。

    先放网络架构(这张图我不记得从哪里保存下来的了)

    1 介绍

    大部分基于CNN的目标检测器主要只适用于推荐系统。举例来说,通过城市相机寻找免费停车位置的系统使用着慢速但是高精度的模型,然而汽车碰撞警告却使用着快速但是低精度的模型。提高实时目标检测器的精度不经能够应用在推荐系统上,而且还能用于独立的流程管理以及降低人员数量上。目前大部分高精度的神经网络不仅不能实时运行,并且需要较大的mini-batch-size在多个GPUs上进行训练。我们构建了仅在一块GPU上就可以实时运行的CNN解决了这个问题,并且它只需要在一块GPU上进行训练

    我们工作的主要目标就是设计一个仅在单个计算系统(比如单个GPU)上就可以快速运行的目标检测器并且对并行计算进行优化,并非减低计量计算量理论指标(BFLOP)。我们希望这个检测器能够轻松的训练和使用。具体来说就是任何一个人仅仅使用一个GPU进行训练和测试就可以得到实时的,高精度的以及令人信服的目标检测结果,正如在图片1中所示的YOLOv4的结果。我们的贡献总结如下:

    (1)我们提出了一个高效且强大的目标检测模型。任何人可以使用一个1080Ti或者2080Ti的GPU就可以训练出一个快速并且高精度的目标检测器。

    (2)我们在检测器训练的过程中,测试了目标检测中最高水准的Bag-of-FreebiesBat-of-Specials方法

    (3)我们改进了最高水准的算法,使得它们更加高效并且适合于在一个GPU上进行训练,比如CBN, PAN, SAM等。

    2 相关工作

    2.1 目标检测模型

    检测器通常由两部分组成:backbonehead。前者在ImageNet上进行预训练,后者用来预测类别信息和目标物体的边界框。在GPU平台上运行的检测器,它们的backbone可能是VGG, ResNet, ResNetXt,或者是DenseNet。在CPU平台上运行的检测器,它们的backbone可能是SqueezeNet,MobileNet或者是ShuffleNet。对于head部分,通常分为两类:one-stage和two-stage的目标检测器。Two-stage的目标检测器的代表是R-CNN系列,包括:fast R-CNN, faster R-CNN,R-FCN和Libra R-CNN. 还有基于anchor-free的Two-stage的目标检测器,比如RepPoints。One-stage目标检测器的代表模型是YOLO, SSD和RetinaNet。在最近几年,出现了基于anchor-free的one-stage的算法,比如CenterNet, CornerNet, FCOS等等。在最近几年,目标检测器在backbonehead之间会插入一些网络层,这些网络层通常用来收集不同的特征图。我们将其称之为目标检测器的neck。通常,一个neck由多个bottom-up路径和top-down路径组成。使用这种机制的网络包括Feature Pyramid Network(FPN),Path Aggregation Network(PAN),BiFPN和NAS-FPN。

    除了上面的这些模型,一些学者将重点放在为目标检测器构建新的backbone(DetNet, DetNASNet)或者是一整个新的模型(SpinNet, HitDetector)。

    综上所述,一个普通的目标检测器由下面四个部分组成:

    2.2 Bag of freebies

    通常来说,目标检测器都是进行离线训练的(训练的时候对GPU数量和规格不限制)。因此,研究者总是喜欢扬长避短,使用最好的训练手段,因此可以在不增加推理成本的情况下,获得最好的检测精度。我们将只改变训练策略或者只增加训练成本的方法称之为“bag of freebies"。在目标检测中经常使用并且满足bag of freebies的定义的算法称是①数据增广。数据增广的目的是增加输入图片的可变性,因此目标检测模型对从不同场景下获取的图片有着更高的鲁棒性。举例来说,photometric distoitions和geometric distortions是用来数据增强方法的两个常用的手段。在处理photometric distortion中,我们会调整图像的亮度,对比度,色调,饱和度以及噪声。对于geometric distortion,我们会随机增加尺度变化,裁剪,翻转以及旋转。

    上面提及的数据增广的手段都是像素级别的调整,它保留了调整区域的所有原始像素信息。此外,一些研究者将数据增广的重点放在了②模拟目标物体遮挡问题上。他们在图像分类和目标检测上已经取得了不错的结果。具体来说,random erase和CutOut可以随机选择图像上的矩形区域,然后进行随机融合或者使用零像素值来进行融合。对于hide-and-seek和grid mask,他们随机地或者均匀地在一幅图像中选择多个矩形区域,并且使用零来代替矩形区域中的像素值。如果将相似的概念用来特征图中,出现了DropOut, DropConnect和DropBlock方法。此外,一些研究者提出一起使用多张图像进行数据增强的方法。举例来说,MixUp使用两张图片进行相乘并且使用不同的系数比进行叠加,然后使用它们的叠加比来调整标签。对于CutMix,它将裁剪的图片覆盖到其他图片的矩形区域,然后根据混合区域的大小调整标签。除了上面提及的方法,style transfer GAN也用来数据增广,CNN可以学习如何有效的减少纹理偏差。

    一些和上面所提及的不同的方法用来解决数据集中的语义分布可能存在偏差的问题。处理语义分布偏差的问题,一个非常重要的问题就是在不同类别之间存在数据不平衡,并且这个问题在two-stage目标检测器中,通常使用hard negative example mining或者online hard example mining来解决。但是example mining 方法并不适用于one-stage的目标检测器,因为这种类型的检测器属于dense prediction架构。因此focal loss算法用来解决不同类别之间数据不均衡的问题。③另外一个非常重要的问题就是使用one-hot很难描述不同类别之间关联度的关系。Label smothing提出在训练的时候,将hard label转换成soft label,这个方法可以使得模型更加的鲁棒性。为了得到一个最好的soft label, Islam引入了知识蒸馏的概念来设计标签细化网络。

    最后一个bag of freebies是④设计边界框回归的目标函数。传统的目标检测器通常使用均方根误差(MSE)在Bbox的中心坐标以及宽高上进行直接的回归预测,即\left \{ x_{center}, y_{center}, w, h \right \},或者左上角和右下角的两个点,即\left \{ x_{top-left}, y_{top-right},x_{bottom-left},y_{bottom-right} \right \}。对于anchor-based方法,去预测相应的offset,比如\left \{ x_{center-offset},y_{center-offset},w_{offset},h_{offset} \right \}\left \{ {x_{top-left-offset},y_{top-left-offset},x_{bottom-right-offset},y_{bottom-right-offset}} \right \}但是,预测Bbox每个点的坐标值是将这些点作为独立的变量,但是实际上并没有将目标物体当成一个整体进行预测。为了更好的解决这个问题,一些研究者最近提出了IoU损失函数,它能够将Bbox区域和ground truth的BBox区域的作为整体进行考虑。IoU损失函数需要计算BBox四个坐标点以及ground truth的IoU。因为IoU具有尺度不变性,它可以解决传统算法比如l_{1}, l_{2}范数计算\left \{ {x,y,w,h} \right \}存在的问题,这个损失函数会随着尺度的变化而发生变化。最近,一些研究者继续提高IoU损失函数的性能。举例来说,除了覆盖范围,GIoU还包括目标物体的形状和坐标。他们提出寻找同时包括预测的BBox和ground truth的BBox的封闭区域BBox,然后使用这个BBox作为分母去代替原来Iou损失函数中的分母。DIoU损失函数额外考虑了目标物体的中心距离,CIoU另一方面同时将覆盖区域,中心点距离和纵横比考虑在内。CIoU在BBox回归问题上可以获得最好的收敛速度和准确率。

    2.3 Bag of specials

    对于那些插件模块后处理方法,它们仅仅稍微的增加了推理成本,但是可以极大地提高目标检测的准确度,我们将其称之为“bag of specials”。一般来说,这些插件模块用来提高一个模型中特定的属性,比如增加感受野,引入注意力机制或者提高特征整合的能力等等;后处理方法是用来抑制模型预测结果的一种方法。

    可以用来提升感受野的常规的方法是SPP, ASPP和RFB。SPP模型来源于空间金字塔匹配(SPM),而且SPMs原始的方法将特征图划分成很多d*d个相等的块,其中d可以是{1,2,3,…},因此可以形成空间金字塔,然后提取bag-of-word的特征。SPP将SPM应用在CNN中,然后使用max-pooling代替bag-of-word运算。因为SPP输出的是一维的特征向量,因此它不能应用在全卷积网络(FCN)中。在YOLOv3中,Redmon和Farhadi改进了SPP模块,将max-pooling输出和内核尺寸k*k连接起来,其中k={1,5,8,13},stride=1。基于这个设计,一个相对较大的k*k的max-pooling有效地提高了backbone特征的感受野。在添加了改进后的SPP模型之后,YOLO-v3-608在COCO数据集上,虽然增加了0.5%的额外计算量,但是提高了2.7%的AP50。ASPP模块和改进的SPP模块的区别主要在:原始的k*k过滤器尺寸,从stride=1到3*3内核尺寸的max-pooling,在stride=1的碰撞卷积运算中膨胀比为k。RFB模块使用一些k*k的内核,膨胀比为k,步长为1的碰撞卷积,它比ASPP获得了更全面的空间覆盖率。RFB在MS COCO数据集上仅仅增加了7%的额外推理时间,但是得到了5.7%的AP50提升。

    目标检测上经常使用的注意力模块主要分成channel-wise注意力模块和point-wise注意力模块,这两个注意力模块主要的代表分别是Squeeze-and-Excitation(SE)和Spatial Attention Module(SAM)。尽管SE模块在ImageNet图像分类工作上仅仅增加了2%的计算量而提高了1%的top-1准确率,但是在GPU上提高了10%的推理时间,因此SE模块更适合在移动设备上使用。但是对于SAM模块来说,在ImageNet图像分类任务中,它仅仅需要0.1%的额外计算量却能够提升ResNet-SE 0.5%的top-1准确率。它在GPU上并没有有效地影响推理速度。

    关于特征融合,早期的是使用skip connection或者是hyper-column将低级的特征和高级的语义特征进行融合。因为多尺度预测方法比如FPN逐渐受到追捧,因此提出了很多将不同特征金字塔融合的轻量级模型。这类别的模型包括SFAM, ASFF和BiFPN。SFAM的主要思想是在多尺度连接特征图上使用channel-wise级别的调整。对于ASFF,它使用softmax作为point-wise级别的调整,然后将不同尺度的特征图加在一起。在BiFPN中,提出使用多输入权重残差连接去执行scale-wise级别的调整,然后将不同尺度的特征图加在一起。

    在深度学习的研究中,一些人重点关心去寻找一个优秀的激活函数。一个优秀的激活函数可以让梯度更有效的进行传播,与此同时它不会增加额外的计算量。在2010年,Nair和Hinton提出了ReLU激活函数充分地解决了梯度消失的问题,这个问题在传统的tanh和sigmoid激活函数中会经常遇到。随后,LReLU,PReLU,ReLU6,Scaled Exponential Linear Unit(SELU),Swish,hard-Swish和Mish等等相继提出,它们也用来解决梯度消失的问题。LReLU和PReLU主要用来解决当输出小于零的时候,ReLU的梯度为零的问题。ReLU6和hard-Swish主要为量化网络而设计。对于神经网络的自归一化,提出SELU激活函数去实现这个目的。需要注意的是Swish和Mish都是连续可导的激活函数。

    在基于深度学习的目标检测中使用的后处理方法是NMS,它可以用来过滤那些预测统一物体、但是效果不好的BBoxes,然后仅仅保留较好的BBoxes。优化NMS和优化目标方程的方法异曲同工。NMS提出的最初的方法并没有将上下文信息考虑在内,因此Girshick在R-CNN中添加了分类置信度作为参考,然后根据置信度得分的顺序,由高到低执行greedy NMS。对于soft NMS来说,它考虑了这样一个问题:在greedy NMS使用IoU的时候,目标遮挡可能会造成置信度得分的退化。在soft NMS基础上,DIoU NMS将重心坐标的距离信息添加到Bbox的筛选处理中了。值得一提的是,上面提到的后处理方法中都不直接引用捕获的图像特征,后续的anchor-free方法开发中不再需要后处理。

    3 方法

    我们工作基本的目标就是在生产系统和优化并行预算中加快神经网络的速度,而非降低计算量理论指标(BFLOP)。我们提供了两个实时神经网络的选择:

    (1)GPU 在卷积层中,我们使用少量的组(1-8): CSPResNeXt50 / CSPDarknet53

    (2)VPU 我们使用分组卷积,但是我们不使用Squeeze-and-excitement(SE)模块,具体包括以下模型:EfficientNet-lite / MixNet / GhostNet / MobileNetV3

    3.1 网络架构的选择

    我们的目标是寻找输入网络的分辨率卷积层的个数参数的数量(filter_size^{^{2}}*filters*channel/groups)以及输出层的个数(filters)之间的最优的平衡。举例来说,大量的研究表明:在ILSVRC2012(ImageNet)的目标检测上,CSPResNext50比CSPDarket53的效果更好,但是在MS COCO的目标检测中,两个的效果恰好相反。

    下一个目标就是选择额外的模块去增加感受野以及为不同检测器不同的backbone选择参数聚合的最佳方法。比如:FPN, PAN, ASFF, BiFPN。

    在分类任务上最优的模型在检测上未必就是最优的。和分类任务相比,检测器需要以下要求:

    (1)更好的输入尺寸(分辨率)- 为了检测多个小物体

    (2)更多网路层 - 为了获得更大的感受野去覆盖不断增大的输入尺寸

    (3)更多的参数 - 提高模型的能力从而能够在一张图片上检测到不同尺寸的多个物体。

    假设来说,我们可以认为具有更大感受野(有大量的3*3的卷积层)和具有大量参数的模型应当作为检测器的backbone。表格1展示了CSPResNetXt50, CSPDarkent53以及EfficientNet B3的相关信息。CSPResNetXt50仅仅只有16个3*3的卷积层,一个425*425的感受野和20.6M个参数,然而CSPDarkent53有29个3*3的卷积层,725*725的感受野和27.6M个参数。从理论证明和大量的实验表明在这两个模型中,CSPDarkent53是作为检测器的backbone最优的选择

            不同尺寸的感受野的影响总结如下:

    (1)等于目标物体的大小时:能够看到整个物体

    (2)等于网络的尺寸:能够看到目标物体周围的上下文信息

    (3)大于网络的尺寸:增加图像点和最终激活之间连接的数量

    我们将SPP模块添加到CSPDarknet53中,因为它极大提高了感受野,能够分离出最重要的上下文特征而且没有降低网络运行的速度。我们使用PANet作为不同检测器不同backbone训练阶段聚集参数的方法,而非YOLOv3的FPN模块。

    最后,我们选择CSPDarknet53作为backbone, SPP作为附加的模块,PANet 作为neck,使用YOLOv3作为YOLOv4架构的head。

    未来,我们计划扩展检测器的Bag of freebies,它们在理论上可以解决某些问题并且能够提高检测器的精度,后续会以实验的形式探究每个算法对检测器的影响。

    我们没有使用Cross-GPU Batch Normalization(CGBN 或者 SyncBN) 或者昂贵的定制设备。这能够使得任何人在一个GPU上就可以得到最先进的结果,比如在GTX 1080Ti或者RTX 2080Ti。

    3.2 BoF 和 BoS的选择

    为了提高目标检测的训练,CNN通常使用下面一些技巧:

    对于训练激活函数,因为PReLU和SELU难以训练,并且RELU6是专门为量化网络设计的,我们因此不考虑这这三个激活函数。在正则化方法中,提出DropBlok的学者将其算法和其他算法进行了比较,然后DropBolck效果更好。因此我们毫不犹豫的选择DropBlock作为我们的正则化方法。在归一化方法的选择中,因为我们关注在一块GPU上的训练策略,因此我们不考虑syncBN。

    3.3 额外的改进

    为了让检测器更适合在单个GPU上进行训练,我们做了以下额外的设计和改进:

    (1)我们提出了数据增广的新的方法:Mosaic和Self-Adversarial Training(SAT)

    (2)在应用遗传算法去选择最优的超参数

    (3)我们改进了一些现有的算法,让我们的设计更适合高效的训练和检测 - 改进SAM, 改进PAN以及Cross mini-Batch Normalization(CmBN)

    Mosaic是一种新型的数据增广的算法,它混合了四张训练图片。因此有四种不同的上下文进行融合,然而CutMix仅仅将两张图片进行融合。此外,batch normalization在每个网络层中计算四张不同图片的激活统计。这极大减少了一个大的mini-batch尺寸的需求。

    自适应对抗训练(SAT)也表示了一个新的数据增广的技巧,它在前后两阶段上进行操作。在第一阶段,神经网络代替原始的图片而非网络的权重。用这种方式,神经网络自己进行对抗训练,代替原始的图片去创建图片中此处没有期望物体的描述。在第二阶段,神经网络使用常规的方法进行训练,在修改之后的图片上进检测物体。

    正如图4中显示,CmBN(Cross mini-Batch Normalization)代表CBN改进的版本。它只收集了一个批次中的mini-batches之间的统计数据。

    我们将SAM的spatial-wise注意力变成了point-wise注意力机制,然后将PAN中的shortcut连接变成了concatenation连接,正如图5和图6所表示的那样。

    3.4 YOLOv4

    在这个部分,我们会详细介绍YOLOv4的细节:

    YOLOv4网络架构的细节

    网络组成

    Backbone

    Neck

    Head

    采用模块

    CSPDarknet53

    SPP, PAN

    YOLOv3

    BoF

    CutMix and Mosaic data augmentation

    DropBlock regularization

    Class label smoothing

     

    CIoU-loss

    CmBN

    DropBlock regularization

    Mosaic data augmentation

    Self-Adversarial Training

    Eliminate grid sensitivity

    Using multiple anchors for a single ground truth

    Cosine annealing scheduler

    Optimal hyperparameters

    Random training shapes

    BoS

    Mish activation

    Cross-stage partial connections(CSP)

    Multi-input weighted residual connections (MiWRC)

     

    Mish activation

    SPP-block

    SAM-block

    PAN path-aggregation block

    DIoU-NMS

    模块作用

    在ImageNet上进行预训练

    融合不同位置上的特征图

    进行预测

    4 实验

    我们测试了不同训练提升技巧在ImageNet(ILSVRC2012 val)数据集上的精度影响,然后又验证了检测器在MS COCO(test-val 2017)数据集的准确率。

    4.1 实验参数配置

    在ImageNet图像分类实验中,默认的超参数如下:训练步长为8,000,000;batch size和mini-batch size分别为128和32;polynominal decay learning rate scheduling strategy初始的学习率为0.1;warm-up步长为1000;momentum和weight decay分别设置为0.9和0.005。所有的BoS实验使用相同的、默认的超参数,在BoF实验中,我们增加了一半的训练步长。在BoF实验中,我们验证了MixUp, CutMix, Mosaic, Bluring数据增加一节label smoothing regularization方法。在BoS实验中,我们比较了LReLU,Swish和Mish激活函数的影响。所有的实验都在1080Ti或者2080Ti GPU上进行训练。

    在MS COCO目标检测实验中,默认的超参数如下:训练步长为500,500;the step decay learning rate scheduling strategy初始化学习率为0.01在步长为400,000和450,000的时候乘以0.1;momentum和weight decay分别设置为0.9和0.0005。所有的架构在一块GPU进行多尺度训练,它的batch size为64,然而它的mini-batch为8还是4取决于网络架构和GPU的内存限制。除了对寻找最优的超参数使用遗传算法之外,其他所有的实验都使用默认的设置。遗传算法和GIoU使用YOLOv3-SPP进行训练,并且为5k个min-val进行300个epochs。对我们采用搜索的学习率为0.00261,momentum为0.949,IoU阈值为设置为0.213,遗传算法实验的损失标准化为0.07。我们还验证了大量的BoF算法,包括grid sensitivity elimination, mosaic数据增广,IoU阈值化,遗传算法,class label smoothing, cross mini-batch normalization,self-adversarial training,cosine anneling scheduler, dynamic mini-batch size, DropBlock, Optimized Anchors, 不同的IoU损失函数。我们也在不同BoS算法上进行了实验,包括Mish,SPP,SAM,RFB,BiFBN以及Gaussiian YOLO。所有的实验我们仅仅使用一个GPU进行训练,因此比如syncBN的优化多个GPU的技巧我们并没有使用。

    4.2 不同算法在分类器训练上的影响

    首先,我们研究了不同算法在分类器训练上的影响;具体来说,Class label smoothing的影响,不同数据增广技巧,bilateral blurring,MixUp, CutMix和Mosaic的印象在图7中显示,以及不同激活函数的影响,比如Leaky-ReLU(默认的),Swish和Mish。

    在表2中所示,在我们的实验中,通过引入一些算法,分类器的准确率得到了提升,这些算法包括:CutMix和Mosaic数据增广,Class label smoothing和Mish激活函数。结果,我们的用于分类器训练的BoF-backbone(Bag of Freebies)包括:Cutmix 和Mosaic数据增广算法以及Class labelsmoothing。正如表2和表3所示,我们将Mish激活函数作为补充的选项。

    4.3  不同算法在检测器训练上的影响

    进一步的研究关注不同Bag-of-Freebies(BoF-detector)在检测器训练准确度的影响,正如表4所示。通过研究能够提高检测器准确度的算法,我们极大地扩展了BoF的算法选项,而且并没有影响FPS:

    • S:消除栅格的敏感度   这个方程在YOLOv3中用于评估目标物体的坐标,自重cx和cy通常是整数,因此,当bx的值非常接近cx或者cx+1的时候,tx的绝对值会非常大。我们通过给sigmoid函数乘以一个大于1的因子来解决这个问题,因此,这样就消除了栅格对不可检测物体的影响。
    • M:Mosaic数据增广 - 在训练过程中,使用四张图片而非一张进行增广处理
    • IT:IoU阈值 - 为一个ground truth的IoU使用多个anchors,ground truth IoU(truth, anchor) > IoU 阈值
    • GA:遗传算法 - 在前10%的训练时间内使用遗传算法选择最优的超参数
    • LS:Class label smoothing - 为sigmoid激活函数使用class label smoothing。
    • CBN:CmBN - 在整个批次中通过使用Cross mini-Batch Normalization收集统计数据,而非在单独的mini-batch中收集统计数据。
    • CA:Cosine annealing scheduler - 在sinusoid训练中改变学习率
    • DM:动态的mini-batch尺寸 - 在低分辨率的训练过程中,通过随机训练形状自动的改提高mini-batch的尺寸。
    • OA: 优化Anchors - 使用优化的anchors进行训练,网络的分辨率为512*512
    • GIoU, CIoU, DIoU, MSE - 为边界框回归使用不同的损失函数。

    下一步的研究关心在检测器训练准确度上,不同Bag-of-Specials(BoS-detector)的影响,包括PAN, RFB, SAM, Gaussian YOLO(G),以及ASFF,正如表5所示。在我们的实验中,当使用SPP, PAN和SAM的时候,检测器得到了最好的性能。

    4.4 不同backbones和预训练权重在检测器训练中的影响

    下一步我们研究不同backbones模型在检测器准确率上的影响,正如表6所示。我们注意到拥有最佳分类准确率的模型,检测器的准确度未必是最佳的。

    首先,尽管使用不同算法训练得到的CSPResNeXt-50模型的分类精度比CSPDarknet53模型的要高,但是CSPDarknet53模型的检测精度更高。

    再者,CSPResNeXt-50分类器训练使用BoF和Mish提高了它的分类准确率,但是检测器训练使用的预训练权重的进一步使用减少了检测器的精度。但是,CSPDarknet53分类器训练使用BoF和Mish提高了分类器和检测器的准确率,它使用分类器预训练权重。这表示CSPDarknet53比CSPResNeXt-50更适合作为检测器。

    我们观察到,由于各种改进,CSPDarknet53模型显示出了更大的提高的检测器精度的能力

    4.5 不同mini-batch尺寸在检测器训练上的影响

    最后,我们分析了不同mini-batch尺寸的训练的模型的结果,并且结果在表7中显示出来。从表7中我们发现在添加了BoF和BoS训练策略之后,mini-batch尺寸几乎对检测器的性能没有影响。结果显示在引入了BoF和BoS之后,就不需要使用昂贵的GPUs进行训练。换句话说,任何人可以仅仅使用一个GPU去训练一个优秀的检测器。

    5 结果

    使用最先进的目标检测器得到的对比结果在图8中显示(具体请看原文)。我们的YOLOv4坐落在帕累托最优曲线上,而且在精度和速度上都优于目前最快的和最准确的检测器。

    6 结论

    我们提出了一个最先进的目标检测器,它比所有检测器都要快而且更准确。这个检测器可以仅在一块8-16GB的GPU上进行训练,这使得它可以广泛的使用。One-stage的anchor-based的检测器的原始概念证明是可行的。我们已经验证了大量的特征,并且其用于提高分类器和检测器的精度。这些算法可以作为未来研究和发展的最佳实践。

    展开全文
  • Martin Fowler关于微服务的原文翻译(一)

    万次阅读 多人点赞 2016-11-25 14:23:27
    原文如下:http://martinfowler.com/articles/microservices.html微服务一个新的架构术语“微服务架构”一词是在过去几年里涌现出来的,它用于描述一种独立部署的软件应用设计方式。这种架构方式并没有非常明确的...

    原文如下:http://martinfowler.com/articles/microservices.html

    微服务

    一个新的架构术语

    “微服务架构”一词是在过去几年里涌现出来的,它用于描述一种独立部署的软件应用设计方式。这种架构方式并没有非常明确的定义,但有一些共同的特点就是围绕在业务能力、自动化布署、端到端的整合以及语言和数据的分散控制上面。

    “微服务”- 这是在软件架构领域这个非常拥挤的街道上,冒出的一个新名词而已。虽然我们对这个新出的名词不屑一顾,但是它所描述的软件系统的风格越来越吸引我们的注意力。在过去的几年里,我们发现越来越多的项目开始使用这个风格,并且到目前为止得到的反馈都是积极的,以至于我身边的许多同事在设计企业架构时,都把它作为默认的构建方式,然而很不幸,到底什么是微服务,我们又如何来使用它,外界并没有太多的信息可供参考。

    总之,微服务这种架构风格就是把一组小服务演化成为一个单一的应用的一种方法。每个应用都运行在自己的进程中,并通过轻量级的机制保持通信,就像HTTP这样的API。这些服务要基于业务场景,并使用自动化布署工具进行独立的发布。可以有一个非常轻量级的集中式管理来协调这些服务,可以使用不同的语言来编写服务,也可以使用不同的数据存储。

    在开始解释什么是微服务之前,先介绍一下单体应用还是很有用的:把一个单体应用构建成为一个部分。企业应用通过是由三个重要部分组成:客户端界面(由HTML、Javascript组成,使用浏览器进行访问)、数据库(由许多的表组件构成一个通用的、相互关联的数据管理系统)、服务端应用。服务端应用处理HTTP请求、执行领域逻辑、检索并更新数据库中的数据、选择和填充HTML视图发送给客户端。这个服务端应用是一个单块结构也就是一个整体,这是一个可执行的单一逻辑,系统中的任何修改都将导致服务端应用重新编译和布署一个新版本。

    就这样一个单体应用很自然的被构建成了一个系统,虽然可以使用开发语言基本特性会把应用封装成类、函数、命名空间,但是业务中所有请求都要在单一的进程中处理完成,在某些场景中,你可以在开发人员的笔记本电脑中运行和测试,并且通过布署通道将测试通过的程序布署到生产环境中,你还可以水平扩展,利用负载均衡将实例布署到多台服务器中。

    的确,单体应用也是很成功的,但是越来越多的人感觉到了不妥,特别是应用程序被发布到了云的时候,变更发布周期被绑定了 —- 原来可以划分成小的应用、小的需要的变更,需要统一的进行编译和发布。随着时间的推移,软件开发者很难保持原有好的模块架构,使得一个模块的变更很难不会影响到其它的模块,而且在扩展方面也只能进行整体的扩展,而不能根据进行部分的扩展。
    这里写图片描述

    这些原因导致了微服务架构风格的出现:以服务构建应用。这些服务还可以被独立布署、独立扩展,每个服务也都提供了清晰的模块边界,甚至不同的服务都可以使用不同的编程语言来实现,也可以由不同的团队进行管理。

    微服务的概念不是我们发明的,它至少起源于Unix时代的设计原则,我们认为这种风格所带来的好处,并没有引起足够多人的重视。

    微服务架构特征

    我们没有办法对微服务有一个正式的定义,但我们可以尝试表述适合这种架构的共同特征来给它打上特性标签,共同特性并不代表每个服务都具备这些特点,但是我们真的期望大多数微服务架构能具备其中大部分特点。虽然我们的作者已经是松散社区的核心成员,但是我们也在尝试描述我们工作中或者我们了解的组件中所理解的微服务。我们并不依赖于那些已经明确过的定义。

    组件化与服务

    只要我们一直在从事软件行业,我们的愿望就是,软件由很多组件组装在一起,如同物理现实世界中类似的构造方式。在过去的几十年里,我们已经看到了大部分语言平台公共库有了长足的进步。

    当我们在谈论组件时,我们遇到了组件定义方面的困难,我们给定的定义是:一个组件是软件中的一个部分,可以独立的替换和升级。

    微服务也会使用组件库,将一个软件组件化的主要方式就是将其分解成服务,我们定义的库是可以连接到程序并使用内存函数的的组件库,服务是进程外的组件,如Web请求服务或者远程调用来相互通信的组件。(这种定义的方式与其它面向对象程序中服务对象的概念是不一样的。)

    把服务当成组件(而不是组件库)的一个原因是服务可以独立布署,如果你有一个应用是由多个库组成并且运行在一个进程中,那么任何一点的改变都会引起整个应用的重新发布,但是将这个应用拆解为多个服务,你可以期待每个服务的变更仅需要发布相应的服务就可以,当然这也不是绝对的,比如导致服务接口变更的更新就需要相应服务的变化,但是良好的架构设计是通过聚合服务边界并且按照合约实现服务演化,最大限度地减少因为改变影响其他地方。

    把服务当成组件的另一个考虑是这会拥有更加清晰的接口,大多数的语言并没有一个很好的机制来定义一个明确显式的发布接口,通常只有文档和规范说明,让用户避免组件间过度紧密而导致高耦合,通过显示的远程调用机制,可以避免这种情况。

    使用服务也有其自身的缺点,远程调用比进程内部调用更加消耗性能,而且远程的API往往是粗粒度的,用起来不是很友好,对组件的职责进行变更,也会影响到进程间的交互,那么操作起来也比较困难。

    第一个可能性,我们看到每个服务是运行在独立的进程上的。注意,这只是第一个可能性。服务也可以由多个进程组成,它们是同时开发和部署的,如果一个应用进程和一个仅由该服务使用的数据库。

    围绕业务能力进行组织

    当我们把一个大的应用拆分成小的部分时,我们的注意力主要集中在技术层面,拆分成UI团队、服务端的逻辑团队和数据库团队。当使用这种标准对团队进行划分时,甚至一个非常小的更变都将导致跨团队间项目协作,从而消耗时间和预算审批。一个高效的团队会针对这种情况进行改善,关注它们所涉及的应用逻辑,并从中做出较好的选择。换句话说,逻辑无处不在。康威定律就是一个例子。

    一个组织的沟通结构反映了其设计的系统的结构
    
    -- Melvyn Conway, 1967

    这里写图片描述

    微服务的划分方法有所不同,它更倾向于围绕业务功能对服务结构进行划分、拆解,这些服务可以采用不同的技术栈来实现,包括用户界面,持久层存储,或任何对外协作,因此团队应该是跨职能的,包括开发所需要的全部技术:用户体验、数据库和项目管理。

    这里写图片描述

    按照这种方式组织的公司是 www.comparethemarket.com,跨职能团队负责建立和操作每个产品并且每个产品都被分成若干单独的服务通过消息进行通信。

    大型的单体应用也可以按照业务功能进行模块化的,尽管这种例子不常见。当然,我们也会敦促一个大型团队在构建一个单体应用时按照业务线来进行划分,我们能看到主要问题在于,这种组件形式会导致很多的上下文依赖,如果这个系统跨越很多模块边界,对于一个单独团队是很难在短时间解决问题的。此外,我们发现模块化方式需要大量的规范去强制执行,而服务组件明确的划分,使得团队间的边界也变得清晰起来。

    产品不是项目

    大多数的开发工作是使用这样一种模型:其目的是完成可以交付的软件,软件开发完成就交给了维护团队,该项目组也就解散了。

    微服务的支持者建议避免这种模型,认为一个团队应该负责产品的整个生命周期,一个很通用的概念就是Amazon’s的“you build, you run it”,它要求开发团队对软件产品的整个生命周期负责,这使得开发人员可以每天都关注产品的运行情况,而且也能够与用户保持紧密的联系,做一些必要的支持工作。

    产品方式开发意味着与业务能力紧紧捆绑在一起,而不是将软件看成是一系列完成的功能,他们会关注如何让软件帮助其用户提升业务能力。

    单体应用也可以采用上述产品的理念,但是更小粒度的服务可以更容易的创建开发者与用户之间的关系。

    智能终端与弱管道

    当在不同的进程之间构建各种通信结构时,我们已经看到许多产品和方法,来强调将大量的智能特性融入到通信机制本身,这种情况的一个典型例子就是“企业服务总线”(Enterprise Service Bus,ESB)。ESB产品经常包括高度智能的设施来进行消息的路由、编排、转换,并应用业务规则。

    微服务社区主张采用另一种做法:智能终端和弱管道。使用微服务所构建的各个应用的目标都是尽可能实现“高内聚和低耦合”–他们拥有自己的领域逻辑,并且更多的是经典的UNIX的“过滤器”那样工作–即接收请求、处理逻辑、返回响应,这些应用通过使用简单的REST风格的协议来进行编排,而不去使用复杂的协议,比如:WS、BEPL或者集中式工具进行编排。

    有两种协议最经常被使用到:包含资源API的HTTP的请求-响应和轻量级消息通信协议。最为重要的建议为:

    微服务团队采用这样的原则和规范:基于互联网(广义上,包含Unix系统)构建系统。这样经常使用的资源几乎不用什么的代价就可以被开发者或者运行商缓存。

    第二种做法是通过轻量级消息总线来发布消息。这种的通信协议非常的单一(单一到只负责消息路由),像RabbitMQ或者ZeroMQ这样的简单的实现甚至像可靠的异步机制都没提供,以至于需要依赖产生或者消费消息的终端或者服务来处理这类问题。

    在一个单块系统中,各个组件在同一个进程中运行。它们相互之间的通信,要么通过方法调用,要么通过函数调用来进行。将一个单块系统改造为若干微服务的最大问题,在于对通信模式的改变。仅仅将内存中的方法调用转换为RPC调用这样天真的做法,会导致微服务之间产生繁琐的通信,使得系统表现变糟。取而代之的是,需要用更粗粒度的协议来替代细粒度的服务间通信。

    展开全文
  • 程序崩溃的时候怎么办 Part-2

    千次阅读 2012-08-22 11:05:36
    欢迎回到程序崩溃的时候怎么办 教程! 在这个教程的第一部分,我们介绍了SIGABRT和EXC_BAD_ACCESS错误,并且举例说明了一些使用xcode调试器(Xcode debugger)和异常断点(Exception Breakpoints

    原文地址:http://www.raywenderlich.com/10209/my-app-crashed-now-what-part-2

    欢迎回到当程序崩溃的时候怎么办 教程!

    在这个教程的第一部分,我们介绍了SIGABRT和EXC_BAD_ACCESS错误,并且举例说明了一些使用xcode调试器(Xcode debugger)和异常断点(Exception Breakpoints)解决问题的策略。

    但是我们的app仍然有一些问题!就像我们看到的,他工作的并不是很好,并且这里仍然有许多潜在的可能崩溃的问题。

    幸运的是,在这个教程的第二部分,也是最后一部分,我们可以学习更多的技术来处理这些问题。

    所以我们就不在啰嗦了,让我们回到继续修正这个充满bug的app中吧!

    Learn how to debug and fix dreaded app crashes!

    Getting Started: When What’s Supposed to Happen, Doesn’t

     

    在第一部分我们停止的地方,经过许多的调试工作之后,我们运行这个程序他是不会崩溃的。但是他却展现了一个没有预料到的空的table,就像下面一样:

    The table view doesn't show any rows.

    当你觉得一些事情应该发生,但是却没有发生的时候,这里有些你可以使用一些技巧来排除问题。在这个教程里面,我们首先是学习使用NSlog来解决这个问题。

    这个table view controller的类是ListViewController。在一系列的任务执行之后,这个app应该装载ListViewController,并且在屏幕上面显示出来。你可以做一个测试,来确定view controller的方法是执行了的。所以viewDidLoad这个方法看起来应该是一个好地方来做测试。

    在ListViewController.m,增加一个NSLog()到viewDidload,就像下面一样:

    - (void)viewDidLoad
    {
    	[super viewDidLoad];
    	NSLog(@"viewDidLoad is called");
    }

    当你运行这个app时,你应该期望当我们点击了“Tap Me”按钮后在调试窗口看到“viewDidLoad is called”这样文字。现在就来试试,点都不惊讶,在调试窗口什么也没有出现。那就意味着ListViewController类根本没有被使用!

    这个多半意味着,你可能忘记了告诉storyboard你想要为table view controller场景使用ListViewController类。

    Setting the class for the table view controller.

    由上图我们可以看出,在身份检查器(Identity Inspector)的类属性区域是设置的默认值UITableViewController。改变这个Custom Class下面的class为ListViewController,然后再一次运行这个app。现在在调试窗口应该就会出现“viewDidLoad is called”文字:

    PProblems[18375:f803] You tapped on: <UIRoundedRectButton: 0x6894800; frame = (119 189; 82 37);
    opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x68948f0>>
    Problems[18375:f803] viewDidLoad is called

    但是这次app将会再一次崩溃,但是却是一个新的问题。

    注意:一旦你的代码好像没起什么什么作用的话,放置一些NSLog()在确切的地方,来看看是否这个方法是被执行了的和cpu通过怎么样路径执行这个方法。使用NSLog()来测试你假设将会执行的代码。

    Assertion Failures

    这个新的有趣的崩溃。它是一个SIGABRT,并且在调试窗口打印出来的是以下消息:

    Problems[18375:f803] *** Assertion failure in -[UITableView _createPreparedCellForGlobalRow:
    withIndexPath:], /SourceCache/UIKit_Sim/UIKit-1912.3/UITableView.m:6072

    我们得到的是一个执行UITableView的一些方法的一个“断言错误(assertion failure)”。当某些东西出错了之后,一个断言是一个内部相容性的检查器,并且会抛出一个异常。你也可以放置断言在你的代码里。例如:

    - (void)doSomethingWithAString:(NSString *)theString
    {
    	NSAssert(string != nil, @"String cannot be nil");
    	NSAssert([string length] >= 3, @"String is too short");
    	. . .
    }

    在上面的方法里面,我们让一个NSString对象作为这个函数的变量,但是代码却不允许调用者传递一个nil或者长度小于3的字符串。假如这些条件中的一个不匹配的话,这个app将会终止,并且抛出一个异常。

    你可以使用断言来作为一个防御性编程技术,因此你应该确定这个就是我们想要的代码行为。断言通常只在调试编译下有用的,因此他们对发布到app store的最终的app是没有运行时的影响的。

    在这个情况下,某些情况触发了一个UITableView的断言错误,但是你并没有完全确定在那个地方。App也是停止在main.m里面,并且在执行堆栈里面只包含了框架(framework)的方法。

    从这些方法的名字,我们可以猜测这个错误发生在重画这个tableview的某些地方。例如,我们可以看到layoutSubviews和_updateVisibleCellsNow:这些名字的方法。

    The call stack for the assertion failure on the table view.

    继续运行这个app来看看是否可以得到一些比较好的错误消息—–记住,现在只是在抛出异常的时候暂停了程序,并没有崩溃。点击继续程序按钮,或者在调试窗口键入下面的命令:

    (lldb) c

    你可能不得不多点击几次继续按钮,“c”命令也是一个简短的继续指令,和点击继续按钮一个效果,并不是就直接执行到最后。

    现在这个调试窗口喷发出一些比较有用的信息:

    *** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
    reason: 'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:'
    *** First throw call stack:
    (0x13ba052 0x154bd0a 0x1362a78 0x99a2db 0xaaee3 0xab589 0x96dfd 0xa5851 0x50301
    0x13bbe72 0x1d6492d 0x1d6e827 0x1cf4fa7 0x1cf6ea6 0x1cf6580 0x138e9ce 0x1325670
    0x12f14f6 0x12f0db4 0x12f0ccb 0x12a3879 0x12a393e 0x11a9b 0x2722 0x2695)
    terminate called throwing an exception

    太好了,这是一个相当好的一个线索。显然这个UITableView的数据源没有从tableView:cellForRowAtIndexPath:方法返回一个有效的cell,因此在ListViewController.m方法里面增加一些调试输出信息来看看:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    	static NSString *CellIdentifier = @"Cell";
    	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    	NSLog(@"the cell is %@", cell);
    
    	cell.textLabel.text = [list objectAtIndex:indexPath.row];
    
    	return cell;
    }

    你增加一个NSLog()标记。再一次运行这个app,看看输出了什么:

    Problems[18420:f803] the cell is (null)

    从以上信息我们可以看出,调用dequeueReusableCellwithIdentifier:返回的却是nil,这就意味着使用“Cell”作为标识符的cell可能不存在(因为这个app使用的是标准的cell的storyboard)。

    当然,这也是愚蠢的bug,并且毫无疑问的是,在以前解决这个需要很长的时间,但是现在却不是了,因为xcode已经通过静态编译警告了你:“Prototype cells must have reuse identities。(标准的cell必须有重用的标识)”。这个是不能忽视的警告:

    Xcode warns about a missing prototype cell identifier.

    打开storyboard,选择这个标准的cell(在tableview的顶端,并且显示的是“Title”的单独的一个cell),并且设置cell的标识符为“Cell”:

    Giving the prototype cell a reuse identifier.

    将那个修复了之后,所以的编译警告应该没有了。运行这个app,现在这个调试窗口应该会打印出来:

    Problems[7880:f803] the cell is <UITableViewCell: 0x6a6d120; frame = (0 0; 320 44); text = 'Title'; layer = <CALayer: 0x6a6d240>>
    Problems[7880:f803] the cell is <UITableViewCell: 0x6877620; frame = (0 0; 320 44); text = 'Title'; layer = <CALayer: 0x6867140>>
    Problems[7880:f803] the cell is <UITableViewCell: 0x6da1e80; frame = (0 0; 320 44); text = 'Title'; layer = <CALayer: 0x6d9fae0>>
    Problems[7880:f803] the cell is <UITableViewCell: 0x6878c40; frame = (0 0; 320 44); text = 'Title'; layer = <CALayer: 0x6878f60>>
    Problems[7880:f803] the cell is <UITableViewCell: 0x6da10c0; frame = (0 0; 320 44); text = 'Title'; layer = <CALayer: 0x6d9f240>>
    Problems[7880:f803] the cell is <UITableViewCell: 0x6879640; frame = (0 0; 320 44); text = 'Title'; layer = <CALayer: 0x6878380>>

    Verify Your Assumptions

    你的NSLog()打印出来的消息,已经告诉我们6个table view cell被创建了,但是在table上面什么都看不见。怎么回事呢?假如你在模拟器里面到处点击一下,你将会注意到tableview中6个cell中的第一个却能够被选中。所以,显然cells都是存在的,只是他们都是空的:

    The table appears empty but cells can actually be selected.

    是时候需要更多的调试记录了。将先前的NSLog()标记改变一下:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    	static NSString *CellIdentifier = @"Cell";
    	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    	cell.textLabel.text = [list objectAtIndex:indexPath.row];
    
    	NSLog(@"the text is %@", [list objectAtIndex:indexPath.row]);
    
    	return cell;
    }

    现在你打印出来就是你的数据模块的内容。运行这个app,看看显示出来的是什么:

    Problems[7914:f803] the text is (null)
    Problems[7914:f803] the text is (null)
    Problems[7914:f803] the text is (null)
    Problems[7914:f803] the text is (null)
    Problems[7914:f803] the text is (null)
    Problems[7914:f803] the text is (null)

    上面的很好的解释了为什么在cell里面什么都没有看到的原因:因为这个文字(text)始终是nil。然而,假如你检查你的代码,并且在initWithStyle:方法里面显示的添加了很多的字符串到list array里面:

    		[list addObject:@"One"];
    		[list addObject:@"Two"];
    		[list addObject:@"Three"];
    		[list addObject:@"Four"];
    		[list addObject:@"Five"];

    就像上面那样,这是测试你的假设是不是正确的一个很好的方法。可能你还想更准确的看看这个array里面到底有什么东西。改变先前在tableView:cellForRowAtIndexPath:里面的NSLog()为这样:

    NSLog(@"array contents: %@", list);

    至少这样可以给你展示一些东西。运行这个app。假如你还没准备好猜测会发生什么情况,调试窗口已经给你打印出来了:

    Problems[7942:f803] array contents: (null)
    Problems[7942:f803] array contents: (null)
    Problems[7942:f803] array contents: (null)
    Problems[7942:f803] array contents: (null)
    Problems[7942:f803] array contents: (null)
    Problems[7942:f803] array contents: (null)

    哈哈,你的脸色瞬间阴沉下来。上面的代码居然没有起作用,因为你可能忘了在首先为这个array对象申请内存空间。这个“list”所以一直为nil,因此调用addObject: 和objectAtIndex:不会起任何的作用。

    你应该在你的view controller被装载的时候为这个list对象分配空间,因此在initWithStyle:方法里面应该是一个不错的选择。修改那个方法为:

    - (id)initWithStyle:(UITableViewStyle)style
    {
    	if (self == [super initWithStyle:style])
    	{
    		list = [NSMutableArray arrayWithCapacity:10];
    
    		[list addObject:@"One"];
    		[list addObject:@"Two"];
    		[list addObject:@"Three"];
    		[list addObject:@"Four"];
    		[list addObject:@"Five"];
    	}
    	return self;
    }

    试一试。我晕,依然什么都没有!调试窗口输出依然是:

    Problems[7971:f803] array contents: (null)
    . . . and so on . . .

    经过了这么多假设和修改,但是还是什么都没有,这些真的是非常令人沮丧啊,但是请记住你可能会一直继续到最后,直到你弄清楚了所有的假设。所以现在的问题就是难道initWithStyle:没有被调用?

    Working With Breakpoints

    你可能又会在代码里面放置另外一个NSLog()标志,但是其实你完全可以使用另外的工具:断点( breakpoints)。你已经看到过无论什么时候只要有异常抛出的时候,程序就会终止的异常断点(Exception Breakpoint)了。你其实也可以增加其他的断点,并且可以放置到代码的任何地方。一旦你的程序运行到断点的地方,这个断点就会被触发,并且程序就会进入调试模式。

    你可以通过点击代码编辑区前面的行号来放置特殊的断点:

    Setting a breakpoint on a line of code.

    这个蓝色的箭头所指示的那一行就有一个断点了。你也可以在断点导航器(Breakpoint Navigator)里面看到这个新的断点:

    The new breakpoint in the Breakpoint Navigator.

    再一次运行这个app。假如initWithStyle:确实是会被调用的话,那么你点击了“Tap Me!”按钮之后,当这个ListViewController被装载的时候,这个app将会暂停,并且会进入调试器。

    可能正如你所料的,什么事情也没有发生。initWithStyle:没有被调用。其实这个是可以讲得通的,因为view controller是从storyboard(或者xib)中装载的,所以使用的应该是initWithCoder:方法。

    将之前initWithStyle:方法替换为initWithCoder::

    - (id)initWithCoder:(NSCoder *)aDecoder
    {
    	if (self == [super initWithCoder:aDecoder])
    	{
    		list = [NSMutableArray arrayWithCapacity:10];
    
    		[list addObject:@"One"];
    		[list addObject:@"Two"];
    		[list addObject:@"Three"];
    		[list addObject:@"Four"];
    		[list addObject:@"Five"];
    	}
    	return self;
    }

    并且保持断点在这个方法上面,来看看它是怎么工作的:

    Setting the breakpoint on initWithCoder.

    一旦你点击了那个按钮,这个app将会进入调试器:

    The debugger is paused on the breakpoint.

    以上的情况并不是意味着这个app崩溃了!它只是在这个断点处暂停了。在左边的执行堆栈里面(假如你没有看到执行堆栈的话,你可能需要切换到调试导航器),你可以看到你是从buttonTapped:到这里的。这个调试导航器里面,我们看到执行了一系列的UIKit的方法,并且装载了一个新的view controller。(顺便说句,断点是一个非常好的工具来指出这个系统是怎么工作的。)

    如果想要离开你之前停留的地方,继续运行这个程序,简单的就是点击继续程序运行按钮,或者在调试控制台中输入“c”。

    显然的是,一切并没有如我们料想的一样,这个app又奔溃了。我告诉过你,它有很多bug的。

    注意:在你继续之前,在initWithCoder:移除断点或者使断点无效。因为他已经展现了他的目的,所以现在它可以离开了。

    你可以在显示行号的的地方右击断点,并且在弹出的菜单中选择删除断点。你也可以拖出这个断点离开窗口,或者在断点调试器里面移除。

    假如你并不想移除这个断点,你可以简单的使断点无效。为了达到这个目的,你可以使用右击弹出菜单,或者左击一次这个断点。判断这个断点是否有效,你可以看看这个断点的颜色,当为浅蓝色了就是无效了,深蓝色就是有效的。

    Zombies!

    回到这个崩溃。它是一个EXC_BAD_ACCESS,幸运的是调试器指到了他发生在那里,在tableView:cellForRowAtIndexPath:

    EXC_BAD_ACCESS error on cellForRowAtIndexPath.

    这是一个EXC_BAD_ACCESS崩溃,意味着在你的内存管理里面有bug。不像SIGABRT,你将不会得到很明朗的错误消息。然而你可以使用一个让你看到曙光的调试工具:Zombies!

    打开这个项目的scheme editor:

    The Edit Scheme menu option.

    选择Run 选项,然后选择Diagnosics标签。勾上Enable Zombie Objects选项:

    Enabling the Zombie Objects diagnostic option.

    现在运行这个app。这个app仍然崩溃,但是现在你将会得到下面的错误消息:

    Problems[18702:f803] *** -[__NSArrayM objectAtIndex:]: message sent to deallocated instance 0x6d84980

    上面这个就是zombie enable 工具所做的,做个小概括:无论什么时候你创建了一个新对象(通过发送“alloc”消息),一块内存将会为这个对象的实例变量保留。当这个对象被释放,他的保留计数(retain count)变成0,这块内存将会被释放。

    但是,你可能仍然有许多的指针指向这个已经失效的内存,这些都是建立在假设这里有一个有效的对象存在的情况下。假如你程序的某些部分试着使用这个野指针,这个app将会伴随着EXC_BAD_ACCESS的错误崩溃掉。

    (假如你是很幸运的话,这个程序将会崩溃。假如你没那么幸运的哈,这个app将会使用这个死亡的对象,各种各样的破坏可能相继发生,特别是某个指针所指向的这个内存区域已经被一个新的对象重新分配了。)

    当这个zombie工具被启用之后,即使这个对象被释放了,这个对象的内存也不会被清理。所以,那块内存将会被标记为“长生不死的”。假如你试着之后又去使用这块内存,这个app能够意识到你的错误操作,并且app将会抛出“message sent to daellocated instance”错误并且终止运行。

    因此这就是之前发生的事。这行就是使用了不死的对象:

    	cell.textLabel.text = [list objectAtIndex:indexPath.row];

    这个cell对象和他的textLabel应该是好的,那么indexPath也应该是正确的,因此我猜测在这个问题下,这个不死的对象应该是“list”。

    你多半其实已经有个很好的线索来怀疑这个“list”,因为这个错误消息说:

    -[__NSArrayM objectAtIndex:]

    这个不死的对象的类是__NSArrayM。假如你已经有一段时间的cocoa编程经验,你应该就会知道一些基本的类,就像NSString和NSArray实际上是“class clusters”,这就意味着就像NSString或者NSArray这些原始的类在一些底层的地方会被特殊的类代替。所以在这里你可以看到一些NSArray类型的对象,也就是这个“list”其实应该是一个NSMutableArray。

    假如你却是想要确认一下,你可以增加一个NSLog()在分配了“list”数组那行代码之后:

    NSLog(@"list is %p", list);

    这里将会打印出和错误消息一样的内存地址(在我这里的情况下是0x6d84980,但是你自己测试的时候,地址就会不一样的)。

    你也可以在调试器里面使用“p”的命令来打印出这个“list”变量的地址(和这个相对的命令就是“po”,这个命令将会打印出这个实际的对象,而不是地址)。这样方便的地方就是你可以省略很多额外增加NSLog()的步骤和从新编译这个app、

    (lldb) p list

    注意:非常不幸的是,上面这些命令在xcode4.3里面并没有执行的很好。由于一些原因,这个地址一直都是展示的0×00000001,可能是因为这个class cluster吧。

    在GDB调试器下面,那些命令就执行的很好,在调试器的变量窗口展示出“list”都是zombie。因此我觉得这个是LLDB的bug。

    The GDB debugger points out which object is the zombie.

    为这个list 数组分配空间的地方就在initWithCoder:,就是下面这样:

    		list = [NSMutableArray arrayWithCapacity:10];

    由于这里不是ARC(Automatic Reference Counting)(自动引用计数)项目,所以是人工管理内存,所以这里你需要retain这个变量:

    // in initWithCoder:
    		list = [[NSMutableArray arrayWithCapacity:10] retain];

    为了避免内存泄露,你也不得不在dealloc函数中释放这个对象,就像下面这个:

    - (void)dealloc
    {
    	[list release];
    	[super dealloc];
    }

    再一次运行这个app。它又崩溃在这同样的一行,但是注意这个调试窗口输出的东西改变了:

    Problems[8266:f803] array contents: (
        One,
        Two,
        Three,
        Four,
        Five
    )

    由上面信息可以知道这个array已经分配了内存空间和包含了字符串的。这个崩溃的提示不再是EXC_BAD_ACCESS,而是SIGABRT,所以你需要再一次设置这个Exception Breakpoint。将这个解决了,继续找其他的bug!

    注意:即使你使用了ARC,在这样的内存管理错误下也是一个非常大的事,你也会崩溃,得到一个EXC_BAD_ACCESS的错误,特别是假如你使用了不安全保留属性。

    我的小提议:无论你什么时候得到一个EXC_BAD_ACCESS错误,你都可以开启zombie objects,然后再试试。

    注意一点:你不应该一直启用zombie objects。因为这个工具将永远不会释放内存,只是简单标记一下这个内存是不死的,你最终将会在某个时候耗尽所有的内存。因此你应该在排查内存相关的错误的时候才开启zombie objects,其他时候应该关闭它。

    Stepping Through the App(单步调试)

    使用断点来解决这个新的问题。将断点放置在刚刚崩溃那一行:

    Setting the breakpoint on cellForRowAtIndexPath.

    重新运行这个程序,点击按钮。你将会在第一次执行tableView:cellForRowAtIndexPath:的时候进入调试器。注意啊,这个时候,app只是因为断点暂停了,并没有崩溃。

    你想要准确的知道这个程序崩溃时的一些细节。请点击继续执行按钮,或者在(lldb)的提示后输入“c”来继续执行。程序将会从暂停的地方继续执行。

    什么事情也没有发生,你仍然暂停在tableView:cellForRowAtIndexPath:这个函数的断点处。但是在调试窗口却显示:

    Problems[12540:f803] array contents: (
        One,
        Two,
        Three,
        Four,
        Five
    )

    这就意味着tableView:cellForRowAtIndexPath:在第一次执行的时候没有任何问题,因为NSLog()在断点之后执行了。因此这个app能够很好地创建第一个cell。

    假如你键入以下的到调试提示之后:

    (lldb) po indexPath

    在调试窗口应该可以输出下面的:

    (NSIndexPath *) $3 = 0x06895680 <NSIndexPath 0x6895680> 2 indexes [0, 1]

    以上重要的部分是[0, 1]。就是这个NSIndexPath对象为section 0和row 1。换句话说,这个tableview现在就在请求第二行。从这里我们可以推测这个app在第一次创建cell的时候没有任何问题,正如刚刚这里就没有发生崩溃。

    多点几次这个继续按钮。在某一个特定的时候,这个程序崩溃了,并且输出一下错误消息:

    Problems[12540:f803] *** Terminating app due to uncaught exception 'NSRangeException',
    reason: '*** -[__NSArrayM objectAtIndex:]: index 5 beyond bounds [0 .. 4]'
    *** First throw call stack:
    . . . and so on . . .

    假如你检查这个indexpath对象的话,你可以看到:

    (lldb) po indexPath
    
    (NSIndexPath *) $11 = 0x06a8a6c0 <NSIndexPath 0x6a8a6c0> 2 indexes [0, 5]

    Section依然是0,但是这个row的索引是5。注意哦,这个错误的消息也是说“index 5”。因为计数是从0开始的,当到5的时候实际上意味着已经是6的位置了。但是这里只有5项。显然这个tableview认为这里实际上有更多的行。

    所以这个犯人就是下面的方法:

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
    	return 6;
    }

    这个方法其实应该被写成这样的:

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
    	return [list count];
    }

    删除断点或者使断点无效,然后再次运行这个程序。终于这个tableview显示出来了,并且没有了崩溃!

    注意:这个“po”命令对于检查你的对象是非常有用的。你可以在程序暂停在调试器的时候,或者在设置一个断点的时候,或者在崩溃的时候,使用这个命令。你需要确定的是这个方法当前在调用堆栈里面是高亮的,否则这个调试器将找不到这个变量。

    你也可以在调试窗口的左边看到这些变量,但是就算看到了也不是很方便就能知道细节的:

    The debugger shows the content of your variables.

    Once more, with feeling

    我刚刚说了没有崩溃的现象了?好,现在我们来试试滑动删除。这个app又终止了在tableView:commitEditingStyle:forRowAtIndexPath:

    Swipe-to-delete will make the app crash.

    错误消息是:

    Problems[18835:f803] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:],
    /SourceCache/UIKit_Sim/UIKit-1912.3/UITableView.m:1046

    这个错误看起来像是来自UIKit,并不是来自app的代码。多次输入几次“c”来让系统抛出异常,这样可以你可以得到更多有用的信息:

    *** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
    reason: 'Invalid update: invalid number of rows in section 0.  The number of rows
    contained in an existing section after the update (5) must be equal to the number
    of rows contained in that section before the update (5), plus or minus the number
    of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or
    minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
    *** First throw call stack: . . .

    经过这些,上面给你一个非常漂亮的解释。这个app告诉这个tableview里面一行要删除,但是某人却忘记从数据源里面移除这行的数据。因此这个table view看起来没有什么改变。修改这个这方法:

    - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
    {
    	if (editingStyle == UITableViewCellEditingStyleDelete)
    	{
    		[list removeObjectAtIndex:indexPath.row];
    		[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
    	}
    }

    太好了,看起来这样做起效了,你终于有一个不会崩溃的app了。

    Where to go from here?(何去何从)

    记住下面几点:

    假如你的app崩溃了,第一件事就是找到是哪里崩溃了,为什么崩溃了。一旦你知道了这两点,修复这个崩溃就很简单了。调试器可以帮助你,但是你需要知道怎么样让他帮助你。

    有些崩溃可能是随机出现的,这个也是最困难的一个,特别是当你正在使用多线程。但是大多数,你可以试试,会发现一些固定的方法来让你的程序每次崩溃。

    你可以想出怎么使用最少的步骤来减少崩溃的现象,这样你将找到一个好的方法来修复这个bug(也就是说他将不会发生)。但是假如你没有确定不会再生了这个错误,你就绝不能确定你的修改已经修复了这个bug。

    秘诀:

    1.假如崩溃在main.m里面,就可以设置全局异常断点(Exception Breakpoint)。

    2.在异常断点开启的状态下,你也没有得到得到有用的信息。在这种情况下,多继续几次运行这个app,或者在调试提示后面输入“po $eax”命令。

    3.大多数崩溃的一般原因和一些bug都是在你的xib中或者storyboard中的连接丢失了或者是错误的连接。这些情况不会在编译错误里面显示,因此你一般不知道。

    4.不要忽略编译警告。假如你有编译警告,就说明你有些东西可能会出错。假如你不知道为什么你会到一个编译警告,最好去搞明白它. 这些都是安全的做法!

    5.在设备上调试可能会和在模拟器上面有些微的不同。这两个环境不是完全一样,你将会得到不同的结果。

    例如,当你运行一个有问题的程序在iphone4上的时候,这第一个崩溃就会发生在NSArray初始化的时候,因为你缺少一个nil标记,而不是会因为当这个app执行setList:的时候的时候崩溃。所以说上面那个原则方法就可以帮你找到崩溃问题的根源本质。

    不要忘记静态分析工具(static analyzer tool),这个工具将会捕获更多的错误。假如你是一个初学者,推荐你开启它。你可以在Build Settings界面上为你的工程设置:

    Setting the static analyzer to run on each build.

    调试愉快吧!

    泰然教程组出品,转载请保留出处并通知泰然!翻译:大侠自来也;校对:Iven

    展开全文
  • 乔布斯斯坦福大学演讲英文原文

    万次阅读 2017-09-24 15:13:19
    乔布斯斯坦福大学演讲英文原文: Stanford Report, June 14, 2005 ‘You’ve got to find what you love,’ Jobs says This is the text of the Commencement address by Steve Jobs, CEO of Apple Computer ...
  • yolov1原文地址以及论文翻译

    万次阅读 2018-07-18 09:47:45
    论文原文:https://arxiv.org/pdf/1506.02640.pdf Tensorflow版本yolo v1:https://github.com/gliese581gg/YOLO_tensorflow Abstract 摘要  本文提出了一种新的物体检测方法YOLO。YOLO之前的物体检测方法主要....
  • QQ可以说是很多人都在使用的一款聊天工具,但是有多的人在使用不当的时候导致了自己的QQ号被永久冻结,但是又不想放弃这个QQ号,这时候就需要去申诉了,编的QQ号码也曾被永久冻结过一次,大概是在2019年的6月份...
  • SURF的优点以及与其他特征提取算法的比较前面总结过了,而且通过实际的使用发现算法的稳定性着实好,这里分享一下对Speeded Up Robust Features原文的翻译和理解,文章是去年翻译的,那时候刚开始看特征提取所以...
  • 一碗阳春面(原文

    千次阅读 2006-05-25 13:50:00
    平时直到深夜十二点还很热闹的大街,大年夜晚上一过十点,就宁静了。北海亭面馆的顾客,此时也象是突然都失踪了似的。 就在最后一位顾客出了门,店主要说关门打烊的时候,店门被咯吱咯吱地拉开了。一个女人带着两...
  • 那么在数据体积激增的当下,究竟哪个数据库才会适合的场景,下面看Moshe的分享。 【编者按】随着数据的爆发性增长,NoSQL得到的关注已越来越多,然而的用例真正需要使用NoSQL数据库吗?又真的适合使用NoSQL...
  • 并且当你的并发高,硬盘IO跟不上的时候,就把图片转换成二进制存内存里边。更强的就是可以给图片加参数,如高宽、格式、滤镜、比例、旋转等,然后拉取图片后按要求处理,再实时返回需要的图片并缓存到服务器。...
  • P.S. 之前在一篇文章中说过VS2005下面_countof的实现,最后提到过为什么使用模板而抛弃之前的实现,后来在网上看到了这篇文章,感觉讲得比较清楚,就翻译了一下,算简单清楚。
  • 生成对抗网络(GAN)论文原文详解

    千次阅读 多人点赞 2019-12-13 16:44:48
    最近在学习生成对抗网络的相关知识,首先接触到的当然是Ian Goodfellow的原始论文,文章中作者简要的阐明了GAN的基本算法,同时也给出该算法可行的理论证明。该模型通俗点说可以利用已有的数据对模型进行训练,...
  • mysql limit查询优化[转载],由于limit经常用到,却没有注意,因为平时做的项目都比较,所以也没有考虑去怎么优化,今天看了一篇关于mysql limit优化的文章,感觉好 于是转载过来 原文地址在这里 ...
  • Semi-Global Matching(SGM)算法原文理解

    万次阅读 多人点赞 2017-06-17 19:16:52
    参考:@迷雾forest ...本文的不同在于加入了一些我自己的理解,并且调整了一下整个算法阐述的思路,是自己的一个阅读笔记。后边打算再做一下SGM原始算法与OpenCV的SGBM算法实现的对
  • 当你无聊时可以做这15件高效的事

    万次阅读 多人点赞 2015-04-14 07:18:35
    如何面对无聊时刻,怎样有效地告别无聊,这里有15件事可以帮助……
  • 后处理参数,分割后会有区域,区域 像素点的个数 小于 min 时,选择与其差异最小的区域 合并即 。 性质讨论 结果虽然不是好,但有好的全局性质,结论有意思,有兴趣的可以看看。 首先要...
  • 作者:Sam哥哥聊技术原文链接:https://my.oschina.net/samgege/blog/1808622 Intellij IDEA真是越用越觉得它强大,它...
  • 当你想不起来某个功能怎么用的时候 修改快捷键 需要了解的自动补全 在自动提示以后使用 Tab 键替换当前的方法或值 当你写完了一行代码 三个定位的技巧 根据后缀自动生成的代码模板 Debug 时自定义对象显示的技巧...
  • 灰兔遗憾的说,哎,还要努力啊,今天又欠了我1次,我想了足足10次。 白兔笑着说,想我的质量肯定没有我想你的质量高? 灰兔问,为什么? 白兔说,打开窗户,我在家门口,想你就要来见见呀。 2 ...
  • 经典中的经典,古龙名著《陆小凤》决战紫禁之巅--叶西大战原文 作者: 古龙  (致心目中最出色的武侠小说家,古龙,文风洒脱,侠义豪情)  叶孤城再次叹息,手中的剑忽又化作飞虹。   一剑西来,天外飞仙。...
  • 原文:http://igoro.com/archive/what-really-happens-when-you-navigate-to-a-url/  作为一个软件开发者,一定会对网络应用如何工作有一...本文将更深入的研究当你输入一个网址的时候,后台到底发生了一件件什么
  • 离梦想最近的时候是什么时候

    千次阅读 多人点赞 2018-06-20 09:44:01
    1 如果问我,十几年的学习生涯最刻骨铭心的是什么?答案只有一个:高考。 我上的高中是我们那边的重点学校。当年,我在的理科班本科上线率达到98%,也就是说,一个班级只...
  • PS:在转载此文之前,小弟先上Develop(http://www.develop-online.net)看了原文,以下排名绝对不分先后(英文首字母排序法,In alphabetical order……),大家千万不要受国内某些异化的转载标题误导,误将APP GAME ...
  • 2020美赛成绩什么时候出?

    万次阅读 热门讨论 2020-04-18 17:13:58
    目录00引言1、官网消息2、安排图片3、总结参考文献 00引言 今天是4月18日,16:41分。去年的今天还有一天就出美赛成绩了。下面是上一年当天公众号截图。 但是由于今年的形势复杂到底啥时候出...显示官网的原文及翻...
  • 不管是现在是不是智能机用户,不论是用iphone,还是用android,或者还是用塞班,我相信这篇文章都会帮助更加理性的理解智能手机,选择智能手机~ 前言: 自iPhone在07年初次登台将智能手机直接带向移动互联...
  • 手把手教实现程序中的自定义组件

    万次阅读 多人点赞 2017-12-14 11:47:35
    之前做程序开发的时候,对于开发来说比较头疼的莫过于自定义组件了,当时官方对这方面的文档也只是寥寥几句,一笔带过而已,所以写起来真的是非常非常痛苦!! 好在微信程序的库从 1.6.3 开始,官方对于自定义...
  • 不知道大家有没有经常遇到这样的一个困扰,为什么同样的算法,的程序却一直超时?大家用的都是暴力大法,为什么别人的能过所有数据,...同样都是使用dp,为什么的比别人的慢了那么多,有时候最后一个测试点都超时!

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 353,227
精华内容 141,290
关键字:

当你还很小的时候原文