解决 Mac 钥匙串访问中证书不受信任问题

开发 IOS 应用时少不了打几次证书,这次重新打包一个好久没动的 app,证书要重新申请,结果在导出 .p12 证书时,出现证书不受信任问题。 这个问题在之前也出现过,当时也解决了,这次又出现让我迷惑了一下,后来想到我不久前重装过几次系统,可能是这个原因导致的,那就重新解决下。 这个问题具体原因说不清,我手动信任证书是没有用的,从各方搜集到的解决方法都是安装根证书,证书下载地址: https://www.apple.com/certificateauthority/ 。需要下载其中的 Worldwide Developer Relations 证书: 有人说要下载全部,但我只下载了 3 个安装上就解决了问题,安装方式是双击。
 · 2 min read 

Go 语言中空接口以及类型断言的使用

断断续续地在看 Go 语言的语法,看到空接口这块,感觉有些意思,记录一下。 空接口 Go 语言中空接口的表示方式是 interface{},它最大的作用就是可以存储任意类型的值,可以理解为 TypeScript 中的 any。 作为一个前端开发,Go 中这种表示任意类型的方式让我觉得有些怪异,但它确实是很有用的。比如我想存储一组用户信息,但具体多少信息是不确定的,我希望可以无限被扩展,那我可以这样写: func main() { userInfo := make(map[string]interface{}) // 信息的值是可以任意类型的 userInfo["name"] = "Tom" userInfo["isAdmin"] = true userInfo["age"] = 18 userInfo["hobby"] = []string{"reading", "running"} } 如果不使用空接口,那么我可能需要定义一个结构体来存储这些信息: type UserInfo struct { name string isAdmin bool age int hobby []string } func main() { userInfo := UserInfo{ name: "Tom", isAdmin: true, age: 18, hobby: []string{"reading", "running"}, } } 这样每当我想要新增一个字段,就需要去结构体中添加一个字段类型,虽说加类型不算什么大问题,但无法做到添加未知类型的字段。所以空接口的作用在这个场景下就体现出来了。其实空接口在 Go 标准库的源码中也是被大量使用的。 类型断言 说到空接口,那不得不提下类型断言,一些被定义为空接口的值,如果想要访问其身上的特定方法时,就必须要用到类型断言。 还是上面的例子,如果想要访问 hobby 中的第一个值,我们的直觉应该是通过索引去访问,如: func main() { userInfo := make(map[string]interface{}) userInfo["hobby"] = []string{"reading", "running"} fmt.Println(userInfo["hobby"][0]) // 通过索引访问 } 但这样写是错误的,会报错 invalid operation: cannot index userInfo["hobby"] (map index expression of type interface{})。 其实仔细想想也很正常,userInfo["hobby"] 可能是任意一种类型的值,它不一定具备索引访问的特性。所以这里就需要用到类型断言,必须先断言出它的具体类型,才可以去访问它的值。 不过 Go 中的类型断言写法,也是让我觉得有点怪异,写法是 n.(type),n 代表要断言的变量,type 代表断言的类型。如上我想要获取 hobby 中的第一个值,可以这样写: v, ok := userInfo["hobby"].([]string) if ok { fmt.Println(v[0]) } else { fmt.Println("no hobby") } 这样写可能看着有些繁琐,但确实能提高程序的健壮性,作为初学者,我只能去适应接受。
 · 4 min read 

关于并发请求

日常开发中的并发请求,一般不会去特地控制,直接依次请求就好,或者使用 Promise.all() 包装一层,等待所有成功的结果。但是当并发请求的数量大起来后,如果不处理并发量,可能会造成较严重的后果。比如大文件上传的场景,一般大文件上传需要切片分传,如果文件较大,可能会被分成100个小块分别请求,那这种同时100条的并发请求,大概率会有以下几个问题: 浏览器会限制,首先浏览器会限制同一域名下的并发请求数量,100个请求是不会立刻并发执行的,会排队处理,可能导致部分请求等待较长时间才开始; 服务器压力,即使绕过了浏览器的并发请求数量,短时间内大量的请求也会给服务器带来较大压力; 网络拥堵,大量并发请求会占用较多的网络带宽,可能导致其他网络活动受到影响,影响用户体验。 所以我们需要对大并发请求做个控制,即限制每次并发的请求量,比如每次只请求5条,100条请求分20次完成。那么怎么实现这种效果呢? 我看好多人会用 Promise.all() 来处理,比如将100条异步请求分成20个数组,每个数组有5个请求,每次将一个请求数组传入 Promise.all()中,当这组数据请求完,再传入下一个数组,以此类推直至请求结束。这种做法在理论上确实可以解决并发问题,之所以是理论上,主要有两个问题: Promise.all() 不能保证所有的请求结束后再给出结束状态,一旦有一个请求错误,会直接返回错误信息,不管后面的成员还在不在请求中。这样本质上还是无法准确控制同时并发量; 即使每次传入的一组请求,都是成功状态返回,或者使用Promise.allSettled,保证所有的请求结束后才返回状态。那么还是有效率问题,因为它需要等待这组数据全部请求完才能开启下一轮,如果这一组数据有一个接口需要10秒,那么这组数据请求就需要多等10秒,显然这问题是很大的。 所以最终还是需要我们自己手动来封装一个并发请求控制,主要实现两个功能: 可以灵活控制并发数量; 请求应该是队列形式,当一组请求中的某一条请求结束了,应自动加入下一条请求,以保证最大的效率。 最后,根据需求,参考网上资料,我简单封装了一个并发请求方法,代码如下: function concurRequest(urls, maxNum) { return new Promise((resolve) => { if (urls.length === 0) { resolve([]) } // 请求的 url 索引 let index = 0 // 已完成请求的数量 let count = 0 // 请求到的结果,每项都是 promise const result = [] // 请求函数 async function request() { // 缓存原始的下标 const i = index // 当前要请求的 url const url = urls[index] // 获取完当前的请求地址后,递增下标,为下一次请求准备 index++ try { const res = await fetch(url) result[i] = res } catch (err) { result[i] = err } finally { count++ // 如果已请求的数量等于总请求数量,那么就返回结果 if (count === urls.length) { resolve(result) } if (index < urls.length) { request() } } } // 第一次触发请求 for (let i = 0; i < Math.min(maxNum, urls.length); i++) { request() } }) } concurRequest 方法可以传入两个参数,接口请求路径数组urls和并发数量maxNum,方法会按照传入的最大请求数来控制同时并发量,当某一个请求结束后,会按照接口数组中的顺序自动请求下一条,直至结束。当然这只是一个范例,我们可以根据实际开发需求改动,比如可以传入一组定义好的请求方法,这样每个请求方法的内部都可以灵活控制。
 · 6 min read 

TypeScript 中 null,undefined 和 void 的区别

在 ts 中的原始类型基本与在 js 中保持一致,但是 null 和 undefined 是有点区别的,它们跟新增的 void 类型也有点关联,所以这里给它们捋捋关系。 在 js 中,null 表示有值,但值为空 ,而 undefined 表示未定义,没有值 。但是在 ts 中,它们都是一个有具体意义的类型值。在没有开启 strictNullChecks 检查的情况下,它们可以成为其他类型的子类型,且可以互相赋值,示例如下: let nul: null = null let und: undefined = undefined // 以下情况仅在 strictNullChecks 未开启时有效 // 可以互相被赋值 nul = undefined und = null // 可以被赋值给其他类型 let str1: string = null let str2: string = undefined 在 js 中,如果一个函数最后没有显示 return 值,或者没有 return,那么默认返回的就是 undefined 。但是在 ts 中,情况变得不一样,如果函数没有 return 或者 return 为空,那么函数返回类型是 void ,除非手动 return 了 undefined,返回值类型才是 undefined,示例代码: // 在 ts 中,func1 和 func2返回类型是 void function func1() {} function func2() { return } // 这里推导返回类型是 undefined,但是如果关闭 strictNullChecks,这里会被推导为 any function func3() { return undefined } func3 函数显示 return 了 undefined ,推导返回类型就是 undefined,但是在关闭 strictNullChecks 情况,会被推导为 any,这种情况需要注意一下。 再说 void 类型,在 ts 中它表示空类型,如果函数没有返回值,即推导为 void 类型。在关闭 strictNullChecks 情况,null 和 undefined 都可以被赋值给 void 类型,如下: // 返回 null,但是可以用 void 类型接 function func4(): void { return null } // null 和 undefined 可以赋值给 void 类型 const voidVar1: void = null const voidVar2: void = undefined 所以说到这,有一个很关键的点就是 strictNullChecks 的开启关闭挺混淆概念的,有点乱。所以我觉得在生产开发中,应该始终开启 strictNullChecks 检查。 总结 在 ts 中,null 和 undefined 都是有意义的类型值,在开发中应始终开启 strictNullChecks 检查,当开启后,有如下表现: null 和 undefined 类型不能互相赋值,且不能被作为子类型赋值给其他类型 函数没有 return 或者 return 具体值时,推导为返回 void 类型,除非手动指定返回 undefined ,则返回值类型是 undefined。同时如果函数 return undefined 了,函数的返回值类型可以使用 void 接。 void 类型是空类型,不能被赋值为其他类型
 · 4 min read 

新开发的博客站点,SEO 这就 100 分了?

昨天晚上心血来潮,把这个新博客部分重构优化了一下,顺便整了一下 SEO。部署之后想着测试一下性能,就用谷歌浏览器自带的 lighthouse 跑了一遍测试,结果出乎意料地好,请看下图: 右边的显示结果全绿,且分数都挺高的,尤其是 SEO 都满分了。这测试的还是 Mobile 端结果,我还没有优化移动端呢。然后今天我又跑了一遍桌面端测试,结果如下: 嘿嘿,分数更高一点,两个满分了。为此我先小小地嘚瑟了一下,以为是自己写的一手好代码。冷静下来后,我想更多的原因还是得益于 Astro 这个框架的优化,你就正常用这个框架开发网站,性能应该都不会很差。关于选择Astro框架的原因,可以看官方文档的自夸:为什么选择 Astro? 关于SEO优化 SEO 这块我其实不是很了解,目前只做了下面这些优化: 最基础的就是网页头中的<meta> 标签该写上的都写上,且每一篇文章都是独立的数据。做法很简单,我设计网站有一个全局的 Layout 布局组件,渲染文章页时,将文章元数据传递到布局组件,以展示各 meat 标签数据。 生成站点地图,并使用 link 标签引入到网页头,框架提供的方案:@astrojs/sitemap。 生成 robots.txt 文件放置网站根目录,优化搜索引擎爬虫抓取。 可以看到我做的很有限,但是收益还不错,这都得益于框架的优化。后面我会抽时间再做些优化,相信可以将分数再提一提。
 · 3 min read