iOS组件化实践

最近在学习vue.js的时候发现,vue的组件化的思想对于编写代码是一个非常有用的事情。

首先为什么需要组件化?

下面我列举了一些比较常见的问题

  • 业务模块划分不清楚,各模块之间耦合度很大,难以维护
    • 例如我们公司的商城项目随着功能和时间,代码量越来越大,早期的一些代码已经不需要了,但是不敢轻易删除,不敢保证该文件是够有被使用。
    • 就算手动去查找删除,也是一件治标不治本的方法,等过一段时间还会出现这种问题
  • 所有模块代码都编写在一个项目中,测试某个模块或功能的时候,需要编译运行整个项目
    • 这一点在做自动化测试的时候尤其明显,如果某个地方出现问题,现在就无法再继续进行测试了
  • 复用性不好,需要经常造轮子
    • 在我们的项目中,如果之前的开发人员离职了,那么他造过哪些轮子,经常后续的开发人员是不了解不清楚的。
    • 如果又需要做一些类似的功能,新的开发人员有需要重复造轮子
  • 开发效率很低,分工不明确
    • 如果有新人加入项目,不能保证可以立刻投入开发。需要较长的时间来熟悉代码,且新人在熟悉代码的过程也容易走弯路。

组件化解决问题的思路

利用中间层来做到:只让别的模块对中间层产生耦合,中间层不对其他模块产生耦合
关于这个中间层,这个问题在这里我们就不深究了,大家可以参考蘑菇街解决思路

组件化的目的

我们可以讲每一个模块作为一个组件。并且建立一个主项目,这个朱羡慕负责集成所有组件。
好处:

  • 业务划分清晰,新人接受项目容易,不同的组件是不同的开发任务,大组件下的小组件是一个小的开发任务
  • 项目维护性更强,需要什么功能,很好找到,提高了开发的销量。
  • 能够更好的发现问题并找到问题,当某个地方出现问题了,可以直接对该组件进行处理,处理好了更新组件就好。
  • 开发和测试的时候,可以只编译自己写的代码,不需要编译全部项目。测试人员也可以测试单个组件。

在这里需要解释一下什么是组件?

一个分类可以是一个组件,一个轮播图也可以是一个组件,一个Controller也是一个组件,甚至一个首页模块(包含内部的所有Controller和业务),都可以是一个组件。

像分类这种被称为基础组件,轮播图这种被称为UI组件,Controller和首页模块这种被称为业务组件。从前到后,从简单到复杂,都是组件。

我们应该怎么做?

在组件化的架构中,有一个主项目用来负责集成其余组件。每一个组件又是一个单独的工程。

组件只需要对外提供服务,通过中间件来调用这些服务。

在这里将通过iOScocoapods私有库的形式来完成一个组件化的示例。

cocoapodsAFNetworking为例。

Jietu20190222-102245.jpg

当我们使用pod search AFNetworking的时候,就是去cocoapodsSpecs索引中找到AFNetworking的配置信息,然后通过这个配置信息,配置信息包含的在github上的代码地址,去获得对应的代码。

现在我们要做的就是按照cocoapods官方的这一套来生成我们自己的一套,我们自己生成一个非官方的Specs索引,在这个索引里只存放公司的库索引,也就是一个个组件,这些组件的具体地址,放到我们想放到的地址下面。如coding私有库,码云,公司部署的gitlab等。

简单的来说就是仿照上图的这三个东西。

准备工作

本地的cocoapods必须要安装好,如何安装cocoapods,这里就不写了

成员划分

组件化主要负责人:负责建立最基本的Spec索引,搭建宿主项目,合并业务组件到宿主工程(理想状况下)
团队开发者:编写组件,更新组件。

详细步骤

  1. 编写组件代码,比如一个轮播图,一个数据库工具等,具体写代码的过程就不说了,这里我使用小码哥的代码来做示范,因为这个实践的文档也是基于一位讲师的文章来做的。

    下面是编写好的代码,在这里分了两个模块,一个是Base,一个是Category。这两个模块不依赖于任何别的第三方文件和库。

    Image Copied on 2019-02-21 at 15.15.35 PM.png
  1. 建立自己的Spec索引,这里我们建立在码云上,因为码云免费且没有项目个数的限制。在这里我的Spec项目名为LJDemoSpecs,这里我没有添加ReadMe.gitignore文件,因此会提示添加readme和lisence。
    f9140582-fd88-4190-8e4b-e2478856ab3b.png.jpeg

现在我们将整个建立的podSpecs添加到我们本地的pod索引中,这里会验证一下用户名和密码。

0a1f9c5c-5f3b-4aba-ae08-12a5cde8d615.png

现在前往电脑中的个人下的.cocoapods文件夹,可以看到repos下面有两个文件夹,一个是master,一个是LJDemoSpecs

4c10983e-0049-4725-b6c6-2d717ee01cca.png

mastercocoapods的官方索引库,而LJDemoSpecs是刚才我们建立的私人的索引库,现在里面还什么都没有。

这里想要查看.cocoapods文件夹需要开启显示隐藏文件的权限

  1. 现在利用pod来生成一个组件化的项目,这个项目的名字叫做LJDemoBase

    这里不通过xcode来建立project,而是使用pod命令来直接生成组件化的项目。

    这里的命令就是pod lib create LJDemoBase,它会从cocoapods的模板中给你clone一个项目。

    • 询问你使用的语言
    • 是否需要demo工程
    • 是否使用测试framework
    • 是否基于View测试
    • 类的前缀

    输入完毕后稍等片刻,xcode会自动打开这个项目,我把全部的命令贴一下

    ac80d2ec-62d0-4d88-84b1-10ba5f1c37c9.png

  1. 下面是自动生成的pod项目的目录结构
    • Example主要存放对应的demo
    • LJDemoBase主要存放代码和需要的资源
    • podspec文件则是记录着当前组件的相关信息,包括git地址,对应的描述,需要依赖的库等等。
    • 右侧的信息不用填写,后面再填写
      89386329-6665-49aa-b982-38e867140538.png
  1. 现在把第一步中的代码添加到这个pod项目中,添加代码的方法有好几种,这里我仅仅写我使用的方法。
    1. 在对应的Example中编写调试代码,编写完成后如下图所示
1a910a84-7623-46e1-bda3-ad44fb53d00e.png
  1. 代码已经写完了,也调试好了。现在将对应的代码放到LJDemoBase下的Classes文件夹下,将Classes下已有的Relpace文件删除。

    这里我们直接在finder里操作,不是在Exampleworkspace操作。

    08eb10e5-ab9c-4828-8252-1c8808798d53.png

  1. 现在删除Example项目中的的BaseCategory,然后在Example目录下使用命令pod install。对应的结果如下
    7d573656-0777-4390-aa74-362c33fd0307.png.jpeg
  1. 再次查看LJDemoBase.xcworkspace,可以看到对应的代码已经自动被放到Development Pods下面了。
    7818a553-4aef-4424-99de-b7bf342799dc.png
  1. 现在如果这就是我们这个小组件的第一个版本,当前代码只是写在本地,需要将他放到远程仓库上。我们在gitoschina建立一个对应的仓库。

    建立好以后如下图所示:


    1435f846-56dd-4c6b-b45f-e02b9d1c0f31.png

这里任然不添加ReadMe.gitignore文件,因为pod lib create创建的项目里已经有这两个文件了

  1. 代码虽然已经写完了,但是对应的配置信息却一点都没有写,现在来把配置信息补齐。
Pod::Spec.new do |s|
  s.name             = 'LJDemoBase'                         #名称
  s.version          = '0.1.0'                              #版本号
  s.summary          = 'A short description of LJDemoBase.' #简短介绍,下面是详细介绍

# This description is used to generate tags and improve search results.
#   * Think: What does it do? Why did you write it? What is the focus?
#   * Try to keep it short, snappy and to the point.
#   * Write the description between the DESC delimiters below.
#   * Finally, don't worry about the indent, CocoaPods strips it!

  s.description      = <<-DESC
TODO: Add long description of the pod here.
                       DESC
  #主页,这里要填写可以访问到的地址,不然验证不通过
  s.homepage         = 'https://gitee.com/lj360691676/LJDemoBase'
  #截图
  # s.screenshots     = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
  #开源协议
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  
  #作者信息
  s.author           = { 'liangjian' => '360691676@qq.com' }
  #项目地址,这里不支持ssh的地址,验证不通过,只支持HTTP和HTTPS,最好使用HTTPS
  s.source           = { :git => 'https://gitee.com/lj360691676/LJDemoBase.git', :tag => s.version.to_s }
  
  #多媒体介绍地址
  # s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'

  #支持的平台及版本
  s.ios.deployment_target = '8.0'

  #代码源文件地址,**/*表示Classes目录及其子目录下所有文件,如果有多个目录下则用逗号分开,如果需要在项目中分组显示,这里也要做相应的设置
  s.source_files = 'LJDemoBase/Classes/**/*'
  
  
  #资源文件地址
  # s.resource_bundles = {
  #   'LJDemoBase' => ['LJDemoBase/Assets/*.png']
  # }

  #公开头文件地址
  # s.public_header_files = 'Pod/Classes/**/*.h'
  
  #所需的framework,多个用逗号隔开
  # s.frameworks = 'UIKit', 'MapKit'
  
  #依赖关系,该项目所依赖的其他库,如果有多个需要填写多个s.dependency
  # s.dependency 'AFNetworking', '~> 2.3'
end

在上面已经将对应的参数通过备注的形式解释了,下面我贴一下我这个组件的podspec的截图,划线部分是修改过的地方。

4e6070de-aba3-4fa0-bbf8-c5e042b8c238.png

由于不要依赖第三方库和别的资源文件,因此s.dependencys.resource_bundles都没有打开注释。

  1. 现在本地LJDemoBase代码和对应LJDemoBasepodspec都搞好了,LJDemoBase的远程仓库也建立好了,现在把本地和远程的库关联起来。

    下面的操作基于LJDemoBase主文件夹:

    da93e30d-82ca-4fcb-98b1-47ee4be76bfc.png

  1. 做完上面的这些操作,再来看oschina上的对应仓库,对应的内容已经全部同步了

    a696054c-f7e0-46b9-8a58-268ce669b13e.png

现在本地和远程仓库已经关联了

  1. 在我们使用cocoapods的过程中,常常会发现当搜索某个库的时候会有对应的Version,可以使用指定版本的代码来集成到项目中,如下图所示

    -> AFNetworking (3.1.0)
    A delightful iOS and OS X networking framework.
    pod 'AFNetworking', '~> 3.1.0'
    - Homepage: https://github.com/AFNetworking/AFNetworking
    - Source:   https://github.com/AFNetworking/AFNetworking.git
    - Versions: 3.1.0, 3.0.4, 3.0.3, 3.0.2, 3.0.1, 3.0.0, 3.0.0-beta.3, 3.0.0-beta.2, 3.0.0-beta.1, 2.6.3, 2.6.2, 2.6.1,
    2.6.0, 2.5.4, 2.5.3, 2.5.2, 2.5.1, 2.5.0, 2.4.1, 2.4.0, 2.3.1, 2.3.0, 2.2.4, 2.2.3, 2.2.2, 2.2.1, 2.2.0, 2.1.0,
    2.0.3, 2.0.2, 2.0.1, 2.0.0, 2.0.0-RC3, 2.0.0-RC2, 2.0.0-RC1, 1.3.4, 1.3.3, 1.3.2, 1.3.1, 1.3.0, 1.2.1, 1.2.0, 1.1.0,
    1.0.1, 1.0, 1.0RC3, 1.0RC2, 1.0RC1, 0.10.1, 0.10.0, 0.9.2, 0.9.1, 0.9.0, 0.7.0, 0.5.1 [master repo]
    - Subspecs:
      - AFNetworking/Serialization (3.1.0)
      - AFNetworking/Security (3.1.0)
      - AFNetworking/Reachability (3.1.0)
      - AFNetworking/NSURLSession (3.1.0)
      - AFNetworking/UIKit (3.1.0)
    

    Version对应相关版本的代码,通过git tag的形式来做到区分版本。

    下面我们也给当前的代码制定一下版本,命令非常简单,如下所示就是打了个0.1.0的tag

    93f0543c-982a-443b-8fc8-22e62feeef8f.png

查看远程仓库,可以看到这就是刚才的tag

3109828a-2bd9-4d74-9fe4-6868788cdf3b.png

  1. 现在一切准备就绪,需要将我们写的这个组件的配置信息加入到私有的pod索引里去,也就是第二步中那个Spec索引。
    还是在当前打tag的目录执行下面的命令
    93961340-0612-4b04-a2e4-32103ad796a3.png

下面来看看本地和远程的索引库,发现现在都有啦

上面的命令会自动帮我们同步到本地和远程

cf891fb1-cfae-41f2-b592-d5edef7b531e.png
3e73c812-3439-4219-b286-abcc0f22c58f.png
  1. 下面来检索这个LJDemoBase,可以看到下面的。
    8e1cfa1d-8414-46b7-8f8e-b19eda899347.png

发现搜索不到,这是缓存的问题

  输入`rm ~/Library/Caches/CocoaPods/search_index.json`然后重新搜索
  1. 现在通过xcode建立主工程LJDemoProject,建立好项目之后使用pod初始化后如图
    fabbe9ba-4504-4436-8ae0-eb48ec6168fd.png
  1. 打开LJDemoProject.xcworkspace,编辑Podfile文件,添加source

    添加source的目的:默认情况下cocoapods指定使用官方的索引,这里我们不仅要使用官方,也要使用个人私有的索引

    修改后的文件内容如下:


    3fab3438-e326-4e98-a55c-ba683348b38a.png

至于怎么看source的地址,使用下列命令pod repo

      liangdeiMac:LJDemoBase apple$ pod repo
      LJDemoSpecs
      - Type: git (master)
      - URL:  https://gitee.com/lj360691676/LJDemoSpecs.git
      - Path: /Users/devliang/.cocoapods/repos/LJDemoSpecs
      master
      - Type: git (master)
      - URL:  https://github.com/CocoaPods/Specs.git
      - Path: /Users/apple/.cocoapods/repos/master
      2 repos
  1. 现在在Podfile文件的的父目录下pod insatll
    打开LJDemoProject.xcworkspace会看到如下图所示,这样我们的组件就被集成进项目里了

    da2cd47c-0413-4e8c-8d8d-e390c90fc98e.png

到这里我们制作集成组件就讲的差不多了,下面是补充一些相关的东西

  • 如何细分组件,给组件划分模块
  • 如何给组件添加依赖
  • 如何更新组件
  • 如何给组件添加资源文件
  • 多人协作开发时,如何做到组件化开发

细分组件

在组件的开发过程中,常常会对组件进行结构划分,例如上面的组件划分为BaseCategory,虽然在物理文件夹上将这两个模块的代码分开了。但是在使用代码的过程中却并没有分开,就像15步的最后那张图,这两部分的代码最后被一起放到了LJDemoBae下。

其实在使用cocoapods的过程中,会发现有一个Subspecs字段,这就是对应的组件细分,如下面的AFNetworking所示。

-> AFNetworking (3.1.0)
      A delightful iOS and OS X networking framework.
      pod 'AFNetworking', '~> 3.1.0'
      - Homepage: https://github.com/AFNetworking/AFNetworking
      - Source:   https://github.com/AFNetworking/AFNetworking.git
      - Versions: 3.1.0, 3.0.4, 3.0.3, 3.0.2, 3.0.1, 3.0.0, 3.0.0-beta.3, 3.0.0-beta.2, 3.0.0-beta.1, 2.6.3, 2.6.2, 2.6.1,
      2.6.0, 2.5.4, 2.5.3, 2.5.2, 2.5.1, 2.5.0, 2.4.1, 2.4.0, 2.3.1, 2.3.0, 2.2.4, 2.2.3, 2.2.2, 2.2.1, 2.2.0, 2.1.0,
      2.0.3, 2.0.2, 2.0.1, 2.0.0, 2.0.0-RC3, 2.0.0-RC2, 2.0.0-RC1, 1.3.4, 1.3.3, 1.3.2, 1.3.1, 1.3.0, 1.2.1, 1.2.0, 1.1.0,
      1.0.1, 1.0, 1.0RC3, 1.0RC2, 1.0RC1, 0.10.1, 0.10.0, 0.9.2, 0.9.1, 0.9.0, 0.7.0, 0.5.1 [master repo]
      - Subspecs:
        - AFNetworking/Serialization (3.1.0)
        - AFNetworking/Security (3.1.0)
        - AFNetworking/Reachability (3.1.0)
        - AFNetworking/NSURLSession (3.1.0)
        - AFNetworking/UIKit (3.1.0)

下面要做到导入组件的时候会自动将已经划分好的目录结构导入,并且还能够按需导入,比如说我只需要Base那么可以只导入该组件下的Base,而不会将其他文件导入。

其实文件的目录划分在上面已经做好了(这里我是之前划分好了,你如果没划分是要划分的),现在这里只需要配置一下Specs就可以了。

  1. 如下图所示,修改的地方我都已经标注好了


    4b0e8f98-3321-4ff8-84a2-158451dd008c.png
  1. 现在提交新的代码,提交代码加打tag都放在下面了
    3cf502d8-7a99-4b45-ae1c-f7272e15815c.png

    41c1b201-48a4-4d33-bb9b-04c3f807ef1c.png

    53daf856-0c48-46cc-9150-26bb4e8e5b6b.png
  1. 更新spec索引pod repo push LJDemoSpecs LJDemoBase.podspec
    538f1bcb-71e8-41d1-bb4e-bf0491d2d2a2.png
  1. 搜索组件pod search ljdemobase,如下图所示,现在的Versions有0.1.0和0.2.0,且SubspecsBaseCategory了。
    754693ea-1026-449e-b93c-dfb2d65dde89.png
  1. 现在更新一下LJDemoProject项目中使用到的组件,pod update --no-repo-update,至于为什么要加后面的参数是因为不如不加的话会更新pod官方索引,需要的时间比较长,我们不想更新官方的索引。
    869cf4a6-fd9e-4fd3-800d-d45123a0a4b3.png
  1. 现在再看一下项目中用到的这个组件,发现现在对应的目录结构已经分好了。
5e7e4d4a-eba5-4f35-b81a-60e2baa49669.png

如果现在只想使用组件中的一个子组件,那么在Podfile中可以写作如下所示

ae28daa0-cff9-4ab8-a8b2-d585e61e1c45.png

修改完毕以后再使用pod update --no-repo-update重新安装一下依赖组件即可

如何给组件添加依赖

在制作组件的过程中,常常需要依赖一些第三方的框架,常见的如AFNetworkingSDWebimage等,下面讲解一下如何添加依赖。

  • 添加依赖的是公开的代码,如AFNetworking
    1. 添加的配置信息是写在组件的podspec文件里,很简单如下所示
      560b5a9c-fd4d-4e05-a754-af97530ff052.png
  1. 更新到远程仓库,打tag并更新Spec索引
    0013c54a-1190-4317-9b1a-421d986bf407.png
  1. 在项目中使用新版本的组件,下面的命令是在LJDemoProject项目下
    385f10c1-a6c7-46f5-9c10-50cff508f10c.png

执行完上面的命令,现在来看一下项目中的依赖库


ddb2a5f8-8c47-437d-b4e8-2d5c3925ba91.png
  1. 如果需要给某个组件的子组件添加依赖库,那么podspec文件写作如下所示

    这里我只截图了两部分的内容,上图为代码添加,下图为spec的修改


    313c5f8e-9e65-4278-ac8e-4cb2a03abe10.png

    Image Copied on 2019-02-21 at 18.01.52 PM.png
  1. 上面的工作都做完以后,现在执行我们的更新组件三部曲
    • 推送组件代码到远程,打tag,这里我直接贴图了,下面的命令在上面的讲解中我已经讲过很多次了

      927f5c17-e215-46d0-b7fd-c6189fa2ccdc.png
  • 更新spec索引pod repo push LJDemoSpecs LJDemoBase.podspec
108255de-85a5-41da-afad-21818e8f2c65.png
  • 先修改LJDemoProject项目中的Podfile。

    这里分两种,一种子组件不依赖第三方,一种的子组件依赖第三方

    • 不依赖第三方,pod 'LJDemoBase/Category'
    • 依赖第三方,pod ‘LJDemoBase’, :subspaces =>[‘Category’, ‘Network’]

    在项目中使用新版本的组件,下面的命令是在LJDemoProject项目下

    5e16ea7c-a5c1-4e16-9df4-e6840281a827.png
  • 子组件不依赖第三方

    f39b9ea6-2cd9-4ccb-a28c-bc87fef11082.png
  • 子组件依赖第三方


    5318c5d9-af9c-4724-aa75-e22d8d500eea.png

可以很清晰的看到,子组件如果依赖第三方库,当使用子组件的时候会自动帮我们安装依赖的三方库,而如果没有使用依赖三方库的子组件,并不会安装对应的第三方库

  • 添加依赖的是私有库,比如前面写了个LJDemoBase,现在又要创建一个新的组件叫LJDemoView,它依赖于LJDemoBase
    先创建对应的pod lib
    21ce98c2-58ba-4a33-a243-ca68e4e7af05.png
  1. LJDemoViewPodfile中添加对应的私有pod索引,如下图所示

    看清楚,这里是LJDemoViewPodfile

    69e1c212-a13e-4425-9b8b-826a69e293e0.png

  1. LJDemoViewLJDemoView.podspec下添加依赖,下图中不仅添加了依赖,描述,git地址等配置信息都填好了(因为这是一个新的组件)
    19209d8a-1008-41e1-82ee-44e62e7f67cb.png
  1. 推送代码到远程仓库,打tag并更新Spec索引,下面我只贴命令了,相信大家已经知道用什么命令了(前面敲了n次)
    liangdeiMac:LJDemoView apple$ git add .
    liangdeiMac:LJDemoView apple$ git commit -m"initial LJDemoView with first version"
    liangdeiMac:LJDemoView apple$ git remote add origin https://gitee.com/lj360691676/LJDemoView.git
    liangdeiMac:LJDemoView apple$ git push origin master
    liangdeiMac:LJDemoView apple$ git tag
    liangdeiMac:LJDemoView apple$ git tag 0.1.0
    liangdeiMac:LJDemoView apple$ git push --tags
    liangdeiMac:LJDemoView apple$ pod repo push LJDemoSpecs LJDemoView.podspec --allow-warnings
    
  2. LJDemoProject下的Podfile中添加新的组件,这里为了区别之前的LJDemoBase组件,把之前的组件注释掉
    3e247d93-39e2-4b4c-aff4-07092cf820f6.png

重新安装组件

   liangdeiMac:LJDemoView apple$ cd /Users/apple/Documents/LJDemo/LJDemoProject
   liangdeiMac:LJDemoProject apple$  pod update --no-repo-update

重新安装后的项目pod依赖如下


cfdc7389-4c90-4383-a4e9-6ca888bf75b6.png

组件直接的相互依赖就说到这里,如果还有问题可以参看下面的官方文档的介绍

https://guides.cocoapods.org/syntax/podspec.html

如何更新组件

  1. 添加对应的代码,修改podspec文件,推送到远程

  2. tag并推送tag

  3. 更新spec索引,如pod repo push LJDemoSpecs LJDemoBase.podspec

  4. 使用组件,在项目的Podfile中添加

    我这里只是写的大体步骤,具体步骤上面做了很多次了,相信都已经很了解了

如何给组件添加资源文件

在开发组件的过程中,常常需要用到一些资源文件,常见的如图片,xib等。

这里我就不花大篇幅来讲了,大家可以看这个老师写的添加组件资源的博客

教你如何从0到1实现组件化架构

多人协作开发时,如何做到组件化开发

经过上面的讲解,相信大家应该知道怎么来制作更新一个组件了,但是上面的一切都是基于个人的操作,如何在团队中能够开发并使用是接下来讨论的问题。

这里我是基于oschina建立了一个组织,组织里就是公司的同事。

完成后的组织结构如下图所示


Image Copied on 2019-02-21 at 19.22.17 PM.png

重要:下面的操作是在另一位同事的电脑上,这个同事叫MT

  1. LJDemoSpecs的内容clone到本地。

    这里的本地地址为:前往电脑中的个人下的.cocoapods文件夹,可以看到repos下面有一个文件夹master,master里存放的官方的库索引,我们现在要加入组织的私有库索引LJDemoSpecs

    下面我贴下命令

    
    MTdeiMac:~ apple$ cd /Users/apple/.cocoapods/repos
    MTdeiMac:repos apple$ git clone https://gitee.com/j1-iOS/ljdemospecs.git
    

    做完上面的操作,如下图所示

    3eaaaf74-c232-4147-a0ab-b9bccc989cac.png

现在通过pod repo可以看到有两个库索引,如下图

fa39137a-9ef4-4f41-b8e0-a7e16bdf4a9e.png

  1. 现在可以把放在oschina上的组件和项目都clone下来,然后和文章前面所讲的一样来进行组件的开发和项目的开发了。

参考资料:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345