Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
前端架構(gòu)是為了在解決單體應(yīng)用在一個(gè)相對(duì)長的時(shí)間跨度下,由于參與的人員、團(tuán)隊(duì)的增多、變遷,從一個(gè)普通應(yīng)用演變成一個(gè)巨石應(yīng)用(Frontend Monolith)后,隨之而來的應(yīng)用不可維護(hù)的問題。這類問題在企業(yè)級(jí) Web 應(yīng)用中尤其常見。
微前端框架內(nèi)的各個(gè)應(yīng)用都支持獨(dú)立開發(fā)部署、不限技術(shù)框架、支持獨(dú)立運(yùn)行、應(yīng)用狀態(tài)隔離但也可共享等特征。
本文會(huì)從框架的應(yīng)用隔離實(shí)現(xiàn)方案、實(shí)戰(zhàn)、優(yōu)缺點(diǎn)三個(gè)方面探一探各個(gè)框架。幫助大家了解各個(gè)框架是如何使用,如何運(yùn)行,從而能選出適合自己項(xiàng)目的微前端方案。
在沒有各大微前端解決方案之前,iframe是解決這類問題的不二之選,因?yàn)?span style="color: #9654B5; --tt-darkmode-color: #9654B5;">iframe提供了瀏覽器原生的硬隔離方案,不論是樣式隔離、js 隔離這類問題統(tǒng)統(tǒng)都能被完美解決。
但他的最大問題也在于他的隔離性無法被突破,導(dǎo)致應(yīng)用間上下文無法被共享,隨之帶來的開發(fā)體驗(yàn)、產(chǎn)品體驗(yàn)的問題:
目前(2024年4月)github star 13k
Single-spa(https://github.com/single-spa/single-spa) 是最早的微前端框架,兼容多種前端技術(shù)棧;是一個(gè)將多個(gè)單頁面應(yīng)用聚合為一個(gè)整體應(yīng)用的 JavaScript 微前端框架;
簡(jiǎn)單來說就是一個(gè)聚合,使用這個(gè)庫可以讓你的應(yīng)用可以 使用多個(gè)不同的技術(shù)棧(vue、react、angular等等)進(jìn)行同步開發(fā),最后使用一個(gè)公用的路由去實(shí)現(xiàn)完美的切換;
Single-spa 實(shí)現(xiàn)了一套生命周期,開發(fā)者需要在相應(yīng)的時(shí)機(jī)自己去加載對(duì)應(yīng)的子應(yīng)用。
它做的事情就是注冊(cè)子應(yīng)用、監(jiān)聽 URL 變化,然后加載對(duì)應(yīng)的子應(yīng)用js,執(zhí)行對(duì)應(yīng)子應(yīng)用的生命周期流程。
主要通過single-spa提供的registerApplication方法注冊(cè)子應(yīng)用,子應(yīng)用需要指定加載子應(yīng)用的方法、和路由條件。
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import { registerApplication, start } from 'single-spa'
Vue.config.productionTip=false
// 遠(yuǎn)程加載子應(yīng)用
function createScript(url) {
return new Promise((resolve, reject)=> {
const script=document.createElement('script')
script.src=url
script.onload=resolve
script.onerror=reject
const firstScript=document.getElementsByTagName('script')[0]
firstScript.parentNode.insertBefore(script, firstScript)
})
}
// 記載函數(shù),返回一個(gè) promise
function loadApp(url, globalVar) {
// 支持遠(yuǎn)程加載子應(yīng)用
return async ()=> {
await createScript(url + '/js/chunk-vendors.js')
await createScript(url + '/js/app.js')
// 這里的return很重要,需要從這個(gè)全局對(duì)象中拿到子應(yīng)用暴露出來的生命周期函數(shù)
return window[globalVar]
}
}
// 子應(yīng)用列表
const apps=[
{
// 子應(yīng)用名稱
name: 'app1',
// 子應(yīng)用加載函數(shù),是一個(gè)promise
app: loadApp('http://localhost:8081', 'app1'),
// 當(dāng)路由滿足條件時(shí)(返回true),激活(掛載)子應(yīng)用
activeWhen: location=> location.pathname.startsWith('/app1'),
// 傳遞給子應(yīng)用的對(duì)象
customProps: {}
},
{
name: 'app2',
app: loadApp('http://localhost:8082', 'app2'),
activeWhen: location=> location.pathname.startsWith('/app2'),
customProps: {}
},
{
// 子應(yīng)用名稱
name: 'app3',
// 子應(yīng)用加載函數(shù),是一個(gè)promise
app: loadApp('http://localhost:3000', 'app3'),
// 當(dāng)路由滿足條件時(shí)(返回true),激活(掛載)子應(yīng)用
activeWhen: location=> location.pathname.startsWith('/app3'),
// 傳遞給子應(yīng)用的對(duì)象,這個(gè)很重要,該配置告訴react子應(yīng)用自己的容器元素是什么,這塊兒和vue子應(yīng)用的集成不一樣,官網(wǎng)并沒有說這部分,或者我沒找到,是通過看single-spa-react源碼知道的
customProps: {
domElement: document.getElementById('microApp'),
// 添加 name 屬性是為了兼容自己寫的lyn-single-spa,原生的不需要,當(dāng)然加了也不影響
name: 'app3'
}
}
]
// 注冊(cè)子應(yīng)用
for (let i=apps.length - 1; i >=0; i--) {
registerApplication(apps[i])
}
new Vue({
router,
mounted() {
// 啟動(dòng)
start()
},
render: h=> h(App)
}).$mount('#app')
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css'
import { BrowserRouter, Link, Route } from 'react-router-dom'
import singleSpaReact from 'single-spa-react'
// 子應(yīng)用獨(dú)立運(yùn)行
if (!window.singleSpaNavigate) {
ReactDOM.render(rootComponent(), document.getElementById('root'))
}
// 生命周期a
const reactLifecycles=singleSpaReact({
React,
ReactDOM,
rootComponent,
errorBoundary(err, info, props) {
return <div>
This renders when a catastrophic error occurs
</div>
}
})
// 這里和vue不一樣,props必須向下傳遞
export const bootstrap=async props=> {
console.log('app3 bootstrap');
return reactLifecycles.bootstrap(props)
}
export const mount=async props=> {
console.log('app3 mount');
return reactLifecycles.mount(props);
}
export const unmount=async props=> {
console.log('app3 unmount');
return reactLifecycles.unmount(props)
}
// 根組件
function rootComponent() {
return <React.StrictMode>
<BrowserRouter>
<div>
<Link to="/app3">Home</Link> |
<Link to="/app3/about"> About</Link>
<Route exact path="/app3" component={Home} />
<Route exact path="/app3/about" component={About} />
</div>
</BrowserRouter>
</React.StrictMode>
}
// home 組件
function Home() {
return <div>
<h1>app3 home page</h1>
</div>
}
// about 組件
function About() {
return <div>
<h1>app3 about page</h1>
</div>
}
將子應(yīng)用導(dǎo)出模式設(shè)置為umd
const package=require('./package.json')
module.exports={
// 告訴子應(yīng)用在這個(gè)地址加載靜態(tài)資源,否則會(huì)去基座應(yīng)用的域名下加載
publicPath: '//localhost:8082',
// 開發(fā)服務(wù)器
devServer: {
port: 8082
},
configureWebpack: {
// 導(dǎo)出umd格式的包,在全局對(duì)象上掛載屬性package.name,基座應(yīng)用需要通過這個(gè)全局對(duì)象獲取一些信息,比如子應(yīng)用導(dǎo)出的生命周期函數(shù)
output: {
// library的值在所有子應(yīng)用中需要唯一
library: package.name,
libraryTarget: 'umd'
}
}
}
可以看到它是動(dòng)態(tài)加載的子應(yīng)用的js,并執(zhí)行js,將內(nèi)容渲染到了主應(yīng)用的盒子內(nèi)。
目前(2024年4月) github star 15.4k
阿里的qiankun 是一個(gè)基于 single-spa 的微前端實(shí)現(xiàn)庫,孵化自螞蟻金融,幫助大家能更簡(jiǎn)單、無痛的構(gòu)建一個(gè)生產(chǎn)可用微前端架構(gòu)系統(tǒng)。
// 偽代碼
class ProxySandbox {
constructor() {
const rawWindow=window;
const fakeWindow={}
const proxy=new Proxy(fakeWindow, {
set(target, p, value) {
target[p]=value;
return true
},
get(target, p) {
return target[p] || rawWindow[p];
}
});
this.proxy=proxy
}
}
let sandbox1=new ProxySandbox();
let sandbox2=new ProxySandbox();
window.a=1;
// 偽代碼
((window)=> {
window.a='hello';
console.log(window.a) // hello
})(sandbox1.proxy);
((window)=> {
window.a='world';
console.log(window.a) // world
})(sandbox2.proxy);
// 偽代碼
class SnapshotSandbox {
constructor() {
this.proxy=window;
this.modifyPropsMap={}; // 修改了那些屬性
this.active(); // 調(diào)用active保存主應(yīng)用window快照
}
/**1. 初始化時(shí),在子應(yīng)用即將mount前,先調(diào)用active,保存當(dāng)前主應(yīng)用的window快照*/
active() {
this.windowSnapshot={}; // window對(duì)象的快照
for (const prop in window) {
if (window.hasOwnProperty(prop)) {
// 將window上的屬性進(jìn)行拍照
this.windowSnapshot[prop]=window[prop];
}
}
Object.keys(this.modifyPropsMap).forEach(p=> {
window[p]=this.modifyPropsMap[p];
});
}
/**
* 子應(yīng)用卸載時(shí),遍歷當(dāng)前子應(yīng)用的window屬性,和主應(yīng)用的window快照做對(duì)比
* 如果不一致,做兩步操作
* 1. 保存 不一致的window屬性,
* 2. 還原window
*/
inactive() {
for (const prop in window) { // diff 差異
if (window.hasOwnProperty(prop)) {
// 將上次拍照的結(jié)果和本次window屬性做對(duì)比
if (window[prop] !==this.windowSnapshot[prop]) {
// 保存修改后的結(jié)果
this.modifyPropsMap[prop]=window[prop];
// 還原window
window[prop]=this.windowSnapshot[prop];
}
}
}
}
}
主應(yīng)用入口文件初始化應(yīng)用,注冊(cè)子應(yīng)用,注冊(cè)子應(yīng)用時(shí)支持傳入子應(yīng)用列表, 注冊(cè)子應(yīng)用時(shí)需要指明以下幾個(gè)主要參數(shù):
options.prefetch此時(shí)可以選擇是否預(yù)加載子應(yīng)用。
options.sandbox默認(rèn)情況下的沙箱可以確保單實(shí)例場(chǎng)景子應(yīng)用之間的樣式隔離,但是無法確保主應(yīng)用跟子應(yīng)用、或者多實(shí)例場(chǎng)景的子應(yīng)用樣式隔離。qiankun提供了另外兩種方式的隔離,供開發(fā)者選擇:
import { registerMicroApps, start, initGlobalState } from 'qiankun';
registerMicroApps([
{
name: 'react app', // app name registered
entry: '//localhost:7100',
container: '#yourContainer',
activeRule: '/yourActiveRule',
},
{
name: 'vue app',
entry: { scripts: ['//localhost:7100/main.js'] },
container: '#yourContainer2',
activeRule: '/yourActiveRule2',
},
]);
// 通訊
const { onGlobalStateChange, setGlobalState }=initGlobalState({
user: 'qiankun',
});
onGlobalStateChange((value, prev)=> console.log('[onGlobalStateChange - master]:', value, prev));
setGlobalState({
ignore: 'master',
user: {
name: 'master',
},
});
/**
* 設(shè)置默認(rèn)進(jìn)入的子應(yīng)用
*/
setDefaultMountApp('/react16');
/**
* 啟動(dòng)應(yīng)用
*/
start({
prefetch: true, // 預(yù)加載子應(yīng)用
sandbox:{
strictStyleIsolation: true, // shadow dom的方式實(shí)現(xiàn)樣式隔離
// experimentalStyleIsolation: true, //添加特殊的選擇器的方式實(shí)現(xiàn)樣式隔離
}
});
runAfterFirstMounted(()=> {
console.log('[MainApp] first app mounted');
});
子應(yīng)用需要在自己的入口 js導(dǎo)出 bootstrap、mount、unmount 三個(gè)生命周期鉤子,以供主應(yīng)用在適當(dāng)?shù)臅r(shí)機(jī)調(diào)用。
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
function render(props) {
const { container }=props;
ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
}
/**
* 和主應(yīng)用通訊
*/
function storeTest(props) {
props.onGlobalStateChange((value, prev)=> console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev), true);
props.setGlobalState({
ignore: props.name,
user: {
name: props.name,
},
});
}
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}
/**
* bootstrap 只會(huì)在微應(yīng)用初始化的時(shí)候調(diào)用一次,下次微應(yīng)用重新進(jìn)入時(shí)會(huì)直接調(diào)用 mount 鉤子,不會(huì)再重復(fù)觸發(fā) bootstrap。
* 通常我們可以在這里做一些全局變量的初始化,比如不會(huì)在 unmount 階段被銷毀的應(yīng)用級(jí)別的緩存等。
*/
export async function bootstrap() {
console.log('[react16] react app bootstraped');
}
/**
* 應(yīng)用每次進(jìn)入都會(huì)調(diào)用 mount 方法,通常我們?cè)谶@里觸發(fā)應(yīng)用的渲染方法
*/
export async function mount(props) {
console.log('[react16] props from main framework', props);
storeTest(props);
render(props);
}
/**
* 應(yīng)用每次 切出/卸載 會(huì)調(diào)用的方法,通常在這里我們會(huì)卸載微應(yīng)用的應(yīng)用實(shí)例
*/
export async function unmount(props) {
const { container }=props;
ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}
為了讓主應(yīng)用能正確識(shí)別微應(yīng)用暴露出來的一些全局信息和開發(fā)環(huán)境下的跨域兼容,在子應(yīng)用(以create-react-app出來的react項(xiàng)目為例)安裝@rescripts/cli,并在子應(yīng)用目錄下新建.rescriptsrc.js,內(nèi)容如下:
const { name }=require('./package');
module.exports={
webpack: (config)=> {
config.output.library=`${name}-[name]`;
config.output.libraryTarget='umd'; // 為了能通過window['app-name1']拿到子應(yīng)用聲明的生命周期
// webpack 5 需要把 jsonpFunction 替換成 chunkLoadingGlobal
config.output.jsonpFunction=`webpackJsonp_${name}`;
config.output.globalObject='window';
return config;
},
devServer: (_)=> {
const config=_;
config.headers={
'Access-Control-Allow-Origin': '*',
};
config.historyApiFallback=true;
config.hot=false;
config.watchContentBase=false;
config.liveReload=false;
return config;
},
};
使用strictStyleIsolation:true方式進(jìn)行樣式隔離,會(huì)生成一個(gè)shadow dom,進(jìn)行樣式的完全隔離:
使用experimentalStyleIsolation:true的方式進(jìn)行樣式隔離,會(huì)在css選擇器前添加特殊標(biāo)識(shí):
可以看到,qiankun會(huì)將子應(yīng)用的html渲染到自定義的container中。 主應(yīng)用加載的是子應(yīng)用的html,在解析子應(yīng)用的html的過程中遇到j(luò)s和css會(huì)載框架內(nèi)進(jìn)行沙盒處理,完成css和js的隔離,之后下載并執(zhí)行,完成整個(gè)子應(yīng)用的渲染過程。
目前(2024年4月)github star 3.7k
wujie是騰訊出品。基于 webcomponent 容器 + iframe 沙箱,能夠完善的解決適配成本、樣式隔離、運(yùn)行性能、頁面白屏、子應(yīng)用通信、子應(yīng)用保活、多應(yīng)用激活、vite 框架支持、應(yīng)用共享等
wujie跟qiankun一樣,都是基于html entry加載的,但他們解析html的過程是不一樣的。 qiankun是直接解析并執(zhí)行js、css、html的,而wujie則是先解析html,提取出script腳本放入空的iframe中,提取出css、html放入到web components中,具體來說:
wujie接入很簡(jiǎn)單,主應(yīng)用可以讓開發(fā)者以組件的方式加載子應(yīng)用。子應(yīng)用只需要做支持跨域請(qǐng)求改造,這個(gè)是所有微前端框架運(yùn)行的前提,除此之外子應(yīng)用可以不做任何改造就可以在無界框架中運(yùn)行,不過此時(shí)運(yùn)行的方式是重建模式。 子應(yīng)用也可以配置保活、生命周期適配進(jìn)入保活模式或單例模式。
與其他框架一樣,先配置子應(yīng)用,
// main-react/index.js
import "react-app-polyfill/stable";
import "react-app-polyfill/ie11";
import React from "react";
import ReactDOM from "react-dom";
import WujieReact from "wujie-react";
import "./index.css";
import App from "./App";
import hostMap from "./hostMap";
import credentialsFetch from "./fetch";
import lifecycles from "./lifecycle";
import plugins from "./plugin";
const { setupApp, preloadApp, bus }=WujieReact;
const isProduction=process.env.NODE_ENV==="production";
bus.$on("click", (msg)=> window.alert(msg));
const degrade=window.localStorage.getItem("degrade")==="true" || !window.Proxy || !window.CustomElementRegistry;
/**
* 大部分業(yè)務(wù)無需設(shè)置 attrs
* 此處修正 iframe 的 src,是防止github pages csp報(bào)錯(cuò)
* 因?yàn)槟J(rèn)是只有 host+port,沒有攜帶路徑
*/
const attrs=isProduction ? { src: hostMap("//localhost:7700/") } : {};
/**
* 配置應(yīng)用,主要是設(shè)置默認(rèn)配置
* preloadApp、startApp的配置會(huì)基于這個(gè)配置做覆蓋
*/
setupApp({
name: "react16",
url: hostMap("//localhost:7600/"),
attrs, // 子應(yīng)用iframe的src
exec: true, // 預(yù)執(zhí)行
fetch: credentialsFetch, // 自定義的fetch方法
plugins,
/** 子應(yīng)用短路徑替換,路由同步時(shí)生效 */
prefix: { "prefix-dialog": "/dialog", "prefix-location": "/location" },
/** 子應(yīng)用采用降級(jí)iframe方案 */
degrade,
...lifecycles,
});
setupApp({
name: "vue3",
url: hostMap("//localhost:7300/"),
attrs,
exec: true,
alive: true, // 子應(yīng)用保活,state不會(huì)丟失
plugins: [{ cssExcludes: ["https://stackpath.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"] }],
// 引入了的第三方樣式不需要添加credentials
fetch: (url, options)=>
url.includes(hostMap("//localhost:7300/")) ? credentialsFetch(url, options) : window.fetch(url, options),
degrade,
...lifecycles,
});
if (window.localStorage.getItem("preload") !=="false") {
preloadApp({
name: "react16",
});
if (window.Proxy) {
preloadApp({
name: "vue3",
});
}
}
ReactDOM.render(<App />, document.getElementById("root"));
引入子應(yīng)用的地方直接以組件式的方式引入:
import React from "react";
import hostMap from "../hostMap";
import WujieReact from "wujie-react";
import { useNavigate, useLocation } from "react-router-dom";
export default function React16() {
const navigation=useNavigate();
const location=useLocation();
const path=location.pathname.replace("/react16-sub", "").replace("/react16", "").replace("/",""); ////
const react16Url=hostMap("//localhost:7600/") + path;
const props={
jump: (name)=> {
navigation(`/${name}`);
},
};
return (
// 單例模式,name相同則復(fù)用一個(gè)無界實(shí)例,改變url則子應(yīng)用重新渲染實(shí)例到對(duì)應(yīng)路由
<WujieReact
width="100%"
height="100%"
name="react16"
url={react16Url}
sync={!path}
props={props}
></WujieReact>
);
}
截至目前(2024年4月)github star 5.2k
mirco-app 是京東2021年開源的一款微前端框架。它借助了瀏覽器對(duì) webComponent 的支持,實(shí)現(xiàn)了一套微前端方案體系。并且由于 Shadow Dom 對(duì) react 這類庫的兼容性較差,便自己實(shí)現(xiàn)了類 Shadow Dom 的效果。與 qiankun 相比,接入更加簡(jiǎn)單。最新的版本也支持iframe實(shí)現(xiàn)js隔離,類似wujie。
首先micro-app實(shí)現(xiàn)了一個(gè)基于WebComponent的組件,并實(shí)現(xiàn)了類Shadow Dom 的效果,開發(fā)者只需要用<micro-app name="xx" url="xx" baseroute="/xxx/xxx">來加載子應(yīng)用,整個(gè)對(duì)子應(yīng)用的加載、js隔離、css隔離的邏輯都封裝在了web component組件<micro-app>中,具體來說:
默認(rèn)使用正則將CSS字符串切割成最小單元,每個(gè)單元包含一段CSS信息,將所有的信息整理生成CSSTree,遍歷CSSTree的每個(gè)規(guī)則,添加前綴實(shí)現(xiàn)樣式隔離。
micro-app有兩種方式實(shí)現(xiàn)js隔離,默認(rèn)是跟qiankun一樣采用proxy沙箱的方式隔離, 在v1.0發(fā)布后支持了基于原生iframe的隔離方式。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Router from './router';
import microApp from '@micro-zoe/micro-app'
microApp.start()
ReactDOM.render(
<React.StrictMode>
<Router />
</React.StrictMode>,
document.getElementById('root')
);
export function MyPage () {
return (
<div>
<h1>子應(yīng)用</h1>
// name:應(yīng)用名稱, url:應(yīng)用地址
<micro-app name='my-app' url='http://localhost:3000/'></micro-app>
</div>
)
}
作者:韓國芳
來源:微信公眾號(hào):奇舞精選
出處:https://mp.weixin.qq.com/s/997pVVxdgpOH6ZsDsFAh2g
并讠果:quangneng.com/5131/
Swift 是蘋果公司于2014年推出的一種新的編程語言,用于iOS、macOS、watchOS和tvOS應(yīng)用程序的開發(fā)。Swift因其安全性、速度和現(xiàn)代性而受到開發(fā)者的喜愛。在這篇文章中,我們將從入門到進(jìn)階,通過實(shí)戰(zhàn)探探iOS APP,來學(xué)習(xí)Swift編程。
一、Swift入門
首先,我們需要在蘋果官方網(wǎng)站下載并安裝Xcode,Xcode是蘋果公司提供的IDE,用于Swift語言的開發(fā)。安裝完成后,打開Xcode,創(chuàng)建一個(gè)新的Swift項(xiàng)目,即可開始Swift編程。
Swift的基礎(chǔ)語法包括變量、數(shù)據(jù)類型、運(yùn)算符、控制結(jié)構(gòu)等。熟悉這些基礎(chǔ)知識(shí)是進(jìn)行Swift編程的前提。
Swift支持函數(shù)和閉包的概念。函數(shù)是一段具有特定功能的代碼,而閉包則是一種匿名函數(shù)。掌握函數(shù)和閉包的定義和使用,有助于我們編寫結(jié)構(gòu)清晰、易于維護(hù)的代碼。
Swift通過類和結(jié)構(gòu)體實(shí)現(xiàn)面向?qū)ο缶幊獭n惡徒Y(jié)構(gòu)體都是用于封裝數(shù)據(jù)和行為的數(shù)據(jù)類型,但類支持繼承和多態(tài),而結(jié)構(gòu)體則不支持。了解它們的使用場(chǎng)景和操作方法,可以更好地設(shè)計(jì)程序。
枚舉和協(xié)議是Swift語言中常用的特性。枚舉用于定義一組具有共同屬性和行為的值,協(xié)議則用于定義一組方法或?qū)傩浴U莆彰杜e和協(xié)議,可以更好地組織代碼,提高可維護(hù)性。
二、Swift進(jìn)階
在Swift中,錯(cuò)誤處理是一個(gè)重要的環(huán)節(jié)。通過Error協(xié)議和do-catch語句,我們可以有效地處理程序運(yùn)行過程中出現(xiàn)的錯(cuò)誤。
Swift支持?jǐn)U展和泛型編程。擴(kuò)展用于為現(xiàn)有的類、結(jié)構(gòu)體、枚舉和協(xié)議添加新的功能,泛型則用于編寫可復(fù)用的、類型安全的代碼。
Swift支持多線程編程,通過DispatchQueue和OperationQueue等API實(shí)現(xiàn)。掌握多線程編程,可以充分利用計(jì)算機(jī)資源,提高程序性能。
三、實(shí)戰(zhàn)探探iOS APP
探探是一款流行的社交應(yīng)用,用戶可以通過滑動(dòng)來選擇喜歡或不喜歡的人。在這個(gè)項(xiàng)目中,我們將使用Swift語言開發(fā)一個(gè)簡(jiǎn)化版的探探應(yīng)用。
我們的簡(jiǎn)化版探探應(yīng)用將具備以下功能:
(1)用戶注冊(cè)和登錄;
(2)用戶可以上傳頭像和填寫個(gè)人信息;
(3)用戶可以查看其他用戶的資料,并進(jìn)行喜歡或不喜歡操作;
(4)用戶可以查看喜歡自己的人,并進(jìn)行配對(duì)操作。
在這個(gè)項(xiàng)目中,我們將使用以下技術(shù):
(1)使用UIKit構(gòu)建用戶界面;
(2)使用Core Data進(jìn)行數(shù)據(jù)存儲(chǔ);
(3)使用 Alamofire 進(jìn)行網(wǎng)絡(luò)請(qǐng)求;
(4)使用 SwiftSoup 解析 HTML 數(shù)據(jù)。
在開發(fā)過程中,我們遵循Swift編程規(guī)范,采用面向?qū)ο缶幊獭㈠e(cuò)誤處理、多線程編程等技術(shù),保證代碼質(zhì)量。同時(shí),我們編寫了詳細(xì)的單元測(cè)試和文檔,確保應(yīng)用的穩(wěn)定性和可維護(hù)性。
為了保證應(yīng)用的高性能,我們采用了以下優(yōu)化措施:
(1)使用懶加載和緩存技術(shù),減少不必要的資源消耗;
(2)使用UITableView進(jìn)行列表展示,提高列表的滑動(dòng)性能;
(3)使用GCD進(jìn)行圖片異步加載,提高圖片加載速度。
在項(xiàng)目部署階段,我們使用Xcode進(jìn)行應(yīng)用的打包和發(fā)布。同時(shí),我們使用TestFlight進(jìn)行應(yīng)用的測(cè)試和分發(fā)。
四、Swift高級(jí)特性
Swift使用自動(dòng)引用計(jì)數(shù)(ARC)機(jī)制來管理內(nèi)存,但開發(fā)者仍需理解內(nèi)存管理的概念,以避免內(nèi)存泄漏和野指針問題。了解Swift中的強(qiáng)引用、弱引用、無主引用等概念對(duì)于編寫高效和安全的代碼至關(guān)重要。
Swift的協(xié)議擴(kuò)展允許開發(fā)者在不修改原有類的情況下,為協(xié)議提供默認(rèn)實(shí)現(xiàn)。這一特性極大地增強(qiáng)了代碼的復(fù)用性和靈活性。
泛型是Swift中的一個(gè)強(qiáng)大特性,它允許開發(fā)者編寫適用于任何類型的代碼。泛型函數(shù)和泛型類型能夠提高代碼的通用性和類型安全。
Swift雖然是一門靜態(tài)語言,但它也提供了一定程度的動(dòng)態(tài)性。例如,使用dynamic關(guān)鍵字可以使類成員動(dòng)態(tài)派發(fā),從而支持運(yùn)行時(shí)檢查和修改。
五、iOS設(shè)計(jì)模式與實(shí)踐
Model-View-Controller(MVC)是iOS開發(fā)中最常用的設(shè)計(jì)模式。了解MVC模式的原則,能夠幫助開發(fā)者更好地組織代碼,實(shí)現(xiàn)視圖、模型和控制器之間的解耦。
Model-View-ViewModel(MVVM)是MVC模式的一種變體,它通過引入ViewModel層來進(jìn)一步解耦視圖和模型,提高代碼的可測(cè)試性和可維護(hù)性。
單例模式確保一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)全局訪問點(diǎn)。在iOS開發(fā)中,單例模式常用于管理共享資源或提供全局服務(wù)。
依賴注入是一種設(shè)計(jì)模式,用于實(shí)現(xiàn)控制反轉(zhuǎn)。在Swift中,依賴注入可以幫助我們減少組件之間的耦合,提高代碼的可測(cè)試性和可維護(hù)性。
六、iOS應(yīng)用測(cè)試與優(yōu)化
單元測(cè)試是確保代碼質(zhì)量的關(guān)鍵。在Swift中,使用XCTest框架進(jìn)行單元測(cè)試,可以驗(yàn)證代碼的各個(gè)部分是否按預(yù)期工作。
UI測(cè)試用于驗(yàn)證應(yīng)用的界面和用戶交互是否符合預(yù)期。使用XCTest框架進(jìn)行UI測(cè)試,可以確保應(yīng)用的用戶體驗(yàn)質(zhì)量。
性能優(yōu)化是iOS應(yīng)用開發(fā)的重要環(huán)節(jié)。使用Instruments等工具進(jìn)行性能分析,可以幫助開發(fā)者發(fā)現(xiàn)并解決性能瓶頸。
代碼審查是提高代碼質(zhì)量的有效手段。通過代碼審查,可以及時(shí)發(fā)現(xiàn)和修復(fù)代碼中的問題,提高代碼的可讀性和可維護(hù)性。
七、項(xiàng)目總結(jié)與展望
通過實(shí)戰(zhàn)探探iOS APP的開發(fā),我們不僅學(xué)習(xí)了Swift編程語言的基礎(chǔ)和進(jìn)階知識(shí),還了解了iOS應(yīng)用開發(fā)的最佳實(shí)踐和設(shè)計(jì)模式。項(xiàng)目實(shí)踐中,我們遇到了各種挑戰(zhàn),如網(wǎng)絡(luò)請(qǐng)求的異步處理、用戶界面的流暢度優(yōu)化、數(shù)據(jù)存儲(chǔ)的可靠性等,通過不斷學(xué)習(xí)和嘗試,我們逐一解決了這些問題,最終完成了一個(gè)功能完善、性能良好的社交應(yīng)用。
展望未來,隨著Swift語言的不斷發(fā)展和iOS平臺(tái)的新技術(shù)涌現(xiàn),iOS應(yīng)用開發(fā)將變得更加高效和強(qiáng)大。作為開發(fā)者,我們需要不斷學(xué)習(xí)新技術(shù),提高自己的技能水平,以適應(yīng)不斷變化的技術(shù)環(huán)境。Swift和iOS開發(fā)將繼續(xù)為我們提供廣闊的發(fā)展空間和無限的創(chuàng)新可能。
1、趣調(diào)查
2、“裝修小能手”
前面我們研究了HTML,回顧下它是做什么的?
當(dāng)我們用HTML搭建好網(wǎng)頁的基本骨架,下面請(qǐng)出我們的“裝修小能手”--CSS。
3、如何學(xué)習(xí)CSS?
Python大星前去探探路...
4、學(xué)習(xí)必備
● 充分利用谷歌瀏覽器Chrome的審查元素功能
● CSS權(quán)威網(wǎng)站
https://developer.mozilla.org/zh-CN/docs/Web/CSS/Reference
1、基本問題
● CSS代碼寫在什么地方?
● CSS的語法規(guī)則?
2、CSS代碼的書寫位置
● 內(nèi)部樣式表
書寫在style元素中,一般放在<head></head>中。
有人可能會(huì)問,能放到其他元素里嗎?
答案:可以。但如果你使用內(nèi)部樣式表,建議放到head元素中,利于瀏覽器的加載渲染。
>> 舉個(gè)栗子:
● 內(nèi)聯(lián)樣式表(元素樣式表)
直接書寫在元素的全局屬性style中
● 外部樣式表
將樣式書寫到獨(dú)立的css文件中。
【1】理由有三:
① 解決多頁面樣式重復(fù)的問題;
② 有利于瀏覽器緩存,提高頁面響應(yīng)速度;
③ 有利于代碼分離,易閱讀和維護(hù)。
【2】如何使用外部樣式表:
3、CSS代碼的語法
CSS語法=選擇器 + 聲明塊
● 選擇器(Selector)
CSS 選擇器是CSS規(guī)則的一部分,使你能應(yīng)用樣式到指定元素。
① 基礎(chǔ)選擇器
① 關(guān)系選擇器
選擇緊跟A元素后的B元素,用+表示,選擇相鄰的第一個(gè)兄弟元素。
選擇A元素之后的所有兄弟元素B,作用于多個(gè)元素,用~隔開
選擇所有作為A元素的直接子元素B,對(duì)更深一層的元素不起作用,用>表示
選擇所有被A元素包含的B元素,中間用空格隔開,在CSS使用頻率高
③ 偽類選擇器
選中某些元素的某種狀態(tài)
1)link: 超鏈接未訪問時(shí)的狀態(tài) 2)visited: 超鏈接訪問過后的狀態(tài) 3)hover: 鼠標(biāo)懸停狀態(tài) 4)active:激活狀態(tài),鼠標(biāo)按下狀態(tài) 愛恨法則:love hate
● 聲明塊
出現(xiàn)在大括號(hào){}中
聲明塊中包含很多聲明(屬性),每一個(gè)聲明(屬性)表達(dá)了某一方面的樣式。
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。