Three-terminal Router for iOS Hot Repair Architecture

Keywords: Vue Mobile Swift axios

MVP is a very good design mode, which can decouple code, layered business and componentized products, but can it be further improved? Coupling in the project is very few now, but the switch before the controller still exists. What way can zero-coupling of the controller be achieved? That's to say, this system needs to be sacrificed. The core Router schema of the column. See the code: github

Zero-coupling has always been a myth, but the negative energy around us is always in the ears. Don't design patterns for the sake of using design patterns. Well, let's go back to Router. How can we build a Router architecture? No hurry, let's finish the front-end project first.

First, we change the port number to 3000, corresponding port front-end: 3000, interface server: 3001, picture server: 3002.

├── config
├── dev.env.js
├── index.js
└── prod.env.js

index.js

  dev: {
    env: require('./dev.env'),
    port: 3000, //update
    autoOpenBrowser: true,
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {},
    // CSS Sourcemaps off by default because relative paths are "buggy"
    // with this option, according to the CSS-Loader README
    // (https://github.com/webpack/css-loader#sourcemaps)
    // In our experience, they generally work as expected,
    // just be aware of this issue when enabling this option.
    cssSourceMap: false
  }
}

We first add Axios to package.json, the official recommendation from Vue2.

package.json

  "dependencies": {
    "axios": "^0.15.3",
    "element-ui": "^1.1.2",
    "stylus": "^0.54.5",
    "stylus-loader": "^2.4.0",
    "vue": "^2.1.0",
    "vue-router": "^2.1.1"
  },

And create two directories under src: javascripts and stylesheets:

.
├── App.vue
├── assets
│ └── logo.png
├── components
├── javascripts
├── main.js
├── router
│ └── index.js
└── stylesheets

And add http.js to javascripts to execute the network request script:

├── javascripts
├── http.js

http.js

import axios from 'axios'

export function GET(url) {

  return new Promise((resolve, reject) => {
    axios.get(url).then((response) => {
      resolve(response.data.data);
    }).catch((error) => {
      reject(error);
    })
  });
}

export const URL = {
  getJ1List: 'http://localhost:3001/api/J1/getJ1List'
}

The code is also simple. Like Swift's Alamoire, it encapsulates a layer outside, uses Proise in ES6 grammar, and defines the interface URL.

Then we changed Hello.vue to J1.vue

├── src
├── components
└── J1.vue

J1.vue

<template lang="html">
  <div>
    <div class="cell" v-for="model in models">
        ![](model.imageUrl)
        <span>{{model.text}}</span>
        <span>{{model.detailText}}</span>
    </div>
  </div>
</template>

<script>

import { GET,URL } from '../javascripts/http'

export default {
  data () {
    return {
      title: '',
      models: []
    }
  },
  methods: {
    request: function() {
      GET(URL.getJ1List).then((data) => {
          this.models = data.models;
      }).catch((error) => {

      })
    }
  },
  mounted:function () {
    this.title = document.title = "J1";
    this.request();
    // alert(window.location.href);
  }
}
</script>

<style scoped>

.cell {
    height: 100px;
    position: relative;
    border-bottom: 1px solid lightgray;
}

.cell img {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    padding-left: 10px;
}

.cell span:first-of-type {
    position: absolute;
    top: 35%;
    transform: translateY(-35%);
    padding-left: 84px;
    font-size: 15px;
}

.cell span:last-of-type {
    position: absolute;
    top: 65%;
    transform: translateY(-65%);
    padding-left: 84px;
    font-size: 12px;
}

</style>

Next we modify the routing:

├── src
├── router
└── index.js

import Vue from 'vue'
import Router from 'vue-router'
import J1 from '../components/J1'

Vue.use(Router)

export default new Router({
  routes: [{
      path: '/',
      component: J1
    },
    {
      path: '/J1',
      component: J1
    },
    {
      path: '*',
      redirect: '/'
    }
  ]
})

Execution, why nothing, this is about the cross-domain problem of ajax, we do not expand here, for cross-domain students who have doubts, please Google.

To address cross-domain issues, we load cross-domain permissions at the back end:

Add in server/app.js:

const cors = require('koa-cors');

app.use(cors());

Of course, you need to install NPM so that we can see the display on the web side.

The front-end is a little bit more unclassified, but it's still necessary for hot fix. Let's go back to the mobile project and change the port number to 3000 when loading webView, so that we can load the same version of the web page we just created on the app side.

Up to now, we have found that the interface in Koa is based on Router, and the page Jump in Vue is based on Router. Can we also have our own Router jump mechanism in mobile terminal? In fact, some developers who follow the trend know that component is popular nowadays, and the representative of component in mobile terminal is Router. Some third-party frameworks and so on, which I have not used, is not clear, here I just build a mobile routing based on the understanding of the front-end routing, first we add a Router.swift.

Router.swift

class Router {
    static let shareRouter = Router()
    var params: [String : Any]?
    fileprivate let map = ["J1" : "Controller"]
}


extension Router {

    func addParam(key: String, value: Any) {
        params?[key] = value
    }

    func clearParams() {
        params?.removeAll()
    }

    func push(_ path: String) {
        guard let nativeController = NSClassFromString("RouterPatterm.\(self.map[path]!)") as? UIViewController.Type else { return }
        currentController?.navigationController?.pushViewController(nativeController.init(), animated: true)
    }
}

First of all, Router must be a singleton. Swift's singleton is much simpler than Objective-C. Let's look at our Router class. map is the corresponding matching jump controller. It can add parameters, delete parameters, and jump methods. Of course, it can also add pop and other functions. I will not show you here. Interested students will study it by themselves. Investigate.

When the definition is complete, we can use it. We modify the Presenter.swift of the business logic.

Presenter.swift

extension Presenter: ViewOperation {

    func pushTo() {
        Router.shareRouter.params = [
            "text" : "app End-to-end incoming data",
            "code" : 1001
        ]
        Router.shareRouter.push("J1") //update
    }
}

Change to Router jump directly here, so that we can do zero-coupling page Jump with more map-defined matching. If you need to pass parameters, you can also store them in the form of a dictionary in the route. When you use them, you can empty the parameters.

Now that we have the routing jump in place on the mobile side, let's talk about the hot update idea. We use a field read from the server to match whether the page is jumping to the mobile side or the front end. Simply speaking, how to degrade the mobile side's page to the mobile side when the code logic of the mobile side is wrong? Front-end page. Here we get the jump interface under the back-end definition.

controllers
└── J1.js

J1.js

exports.getRouters = async(ctx, next) => {

    ctx.body = {
        routers: {
            J1: 'web'
        }
    }
}

And mount it on the route:
.
├── api
│ ├── J1_router.js
│ └── index.js
├── index.js
└── users.js

J1_router

var router = require('koa-router')();
var J1 = require('../../app/controllers/J1');

router.get('/getRouters', J1.getRouters); //update
router.get('/getJ1List', J1.getJ1List);

module.exports = router;

Through the interface access, we can get the return data:

{
  "code": 0,
  "message": "success",
  "data": {
    "routers": {
      "J1": "web"
    }
  }
}

Back on the mobile side, we can judge the route jump based on this interface.

Router.swift

class Router {
    static let shareRouter = Router()
    var params: [String : Any]?
    var routers: [String : Any]? //update
    fileprivate let map = ["J1" : "Controller"]

    func guardRouters(finishedCallback : @escaping () -> ()) { //update

        Http.requestData(.get, URLString: "http://localhost:3001/api/J1/getRouters") { (response) in
            guard let result = response as? [String : Any] else { return }
            guard let data:[String : Any] = result["data"] as? [String : Any] else { return }
            guard let routers:[String : Any] = data["routers"] as? [String : Any] else { return }
            self.routers = routers
            finishedCallback()
        }
    }
}

extension Router {

    func addParam(key: String, value: Any) {
        params?[key] = value
    }

    func clearParams() {
        params?.removeAll()
    }

    func push(_ path: String) {

        guardRouters { //update
            guard let state = self.routers?[path] as? String else { return }

            if state == "app" {
                guard let nativeController = NSClassFromString("RouterPatterm.\(self.map[path]!)") as? UIViewController.Type else { return }
                currentController?.navigationController?.pushViewController(nativeController.init(), animated: true)
            }

            if state == "web" {
                let host = "http://localhost:3000/"
                let webViewController = WebViewController("\(host)")
                currentController?.navigationController?.pushViewController(webViewController, animated: true)
            }
        }
    }
}

Now when we request the corresponding page status at each route jump, we can jump Native or H5 in the background.

Looking back at the previous code, we have now achieved zero coupling between Model, View, View Model, Presenter and Controller. Think about whether it is a little exciting. In the next section, we will coordinate Swift, Vue and Koa to achieve a real hot repair framework.

github Download Address!!!


Click on the link below to jump!!
Specific source code, please download it on github! Like the friend sent the little star yo!!

Posted by NightCoder on Tue, 09 Jul 2019 11:53:56 -0700