第一个OpenGL小程序

一、在Mac系统下配置OpenGL开发环境
1. 安装CMake
CMake官网:https://cmake.org/
下载安装dmg文件
2. 安装GLFW
GLFW官网:https://www.glfw.org/
1)下载Source Package,解压得到文件夹glfw-3.2.1,在glfw-3.2.1中创建子文件夹build。
2)打开CMake
配置source code目录为文件夹glfw-3.2.1
配置build目录为文件夹build
首次点击Configure按钮,会弹出弹框,选择选择Unixfile,就开始configure了,以后点击configure,不会再弹出弹框。完成之后,CMake界面中间有红色条目,继续点击Configure按钮,红色消失,选中“BUILD_SHARED_LIBS”选项,点击Generate按钮,等待完成。

3)在终端打开文件夹build
输入make,回车,等待完成
输入make install,回车,等待完成
GLFW的lib和include的文件就写入到了/usr/local/里。
3. 安装GLAD
1)打开GLAD的在线服务:http://glad.dav1d.de/
将语言(Language)设置为C/C++,在API选项中,OpenGL(gl)选择Version 4.6。之后将模式(Profile)设置为Core,并且保证生成加载器(Generate a loader)的选项是选中的。都选择完之后,点击生成(Generate)按钮来生成库文件。


生成zip压缩文件点击下载解压,包含两个头文件目录,和一个glad.c文件。
将两个头文件目录(glad和KHR)复制到你的Include文件夹中(即/usr/local/include),并添加glad.c文件到稍后的工程中。
二、环境配置好了,创建xcode项目
1)打开Xcode,新建project,选择macOS –> Command Line Tool,点击next,填写项目名,选择Language: C/C++,创建。
2)选择project的targets,点击Build phases,选择Link Binary With Libraries 点➕号,选择OpenGL.frameworks
继续添加,Add Other 我们要到/usr/local/下面找lib去(出现那个finder文件目录界面的时候使用快捷键cmd+shift+G)
在/usr/local/lib 找到libglfw.3.2.dylib 添加上就ok了。

3)设置Build Setting
search paths:
Header Search Paths: /usr/local/include
Library Search Paths: /usr/local/lib
4)在项目中添加上面得到的glad.c文件

5)在main.app中添加以下代码

#include <GLFW/glfw3.h>
#include "glad/glad.h"
#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

int main()
{
    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
#endif

    // glfw window creation
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // input
        // -----
        processInput(window);

        // render
        // ------
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // glfw: terminate, clearing all previously allocated GLFW resources.
    // ------------------------------------------------------------------
    glfwTerminate();
    return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
    if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}

三、cmd+R运行,得到下面渲染出的窗口,第一个项目跑通了。

JSON Schema在Vue中的实践(二)

前言

上一篇我们讲到了如何通过schema 生成表单,但只是生成表单还是远远不够的,下面我就为大家讲下如何数据绑定以及组件复用。

数据绑定

我们目前已经有了一个表单,但是没有办法将数据绑定。首先想到的可能是使用组建的v-model,添加一个value属性。

<input
  type="text"
  :name="name"
  v-model="value"
  :placeholder="placeholder"
/>

这样写的话回报错误

[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"

found in

---> <TextInput> at /src/components/TextInput.vue
       <FormsDemo> at /src/App.vue
         <Root>

因为虽然Vue提供了语法糖,使得组件可以实现双向绑定,但是框架还是偏向单项数据流。如果我们试图修改父组件的数据,Vue还是会向我们发出警告。

所以我们根据vue的官方文档中的介绍来解决

<input v-model="something">

<input  
  v-bind:value="something"
  v-on:input="something = $event.target.value"
>

以上两种写法是等同的
通过第二种我们就可以实现把值提供给子组件,并且让父组件知道值的更新。

我们通过绑定到value并发出@input事件来通知父组件值已经发生变化,从而完成此操作。

以下为实现代码

// 子组件
<div>
  <label>{{label}}</label>
  <input type="text"
    :name="name"
    :value="value"
    @input="$emit('input', $event.target.value)"
    :placeholder="placeholder"
  >
</div>
// 父组件
<component v-for="(field, index) in schema"
  :key="index"
  :is="field.fieldType"
  v-model="formData[field.name]"
  v-bind="field">
</component>  

这样父组件提供绑定值,同时也负责处理绑定到它自己的组件状态。

现在我们算是基本完成了生成表单的工作,既然是组件我们就要做到可复用

可复用性

对于一个表单生成器,我们肯定希望将数据作为prop传递过去,然后在组件之间建立数据绑定。

比如这样

<form-generator :schema="schema" v-model="formData">  
</form-generator>

这样简化了父组件的复杂度。

我们创建了名为FormGenerator的组件。

代码如下

<component
  v-for="(field, index) in schema"
  :key="index"
  :is="field.fieldType"
  :value="formData[field.name]"
  @input="updateForm(field.name, $event)"
  v-bind="field">
</component>

我们将v-model改为了:value,使用@input处理事件,添加value和props上。

这样我们就有了一个可复用的表单生成器了。

结尾

由于马上就要过年了,时间比较匆忙,最终的代码我之后会整理出来,之后发给大家,实在抱歉,提前给大家拜个早年。

简述前端系统的微内核架构

现今大型前端系统越来越多,功能也越来越复杂的环境中,前端也需要通过架构来降低开发成本,提高维护效率,所以社区也提出了相应的微前端架构概念,但是相对于较新的微前端概念,微内核架构已经在各种软件场景中应用了。

微内核架构主要包含两种架构组件: 核心系统和插件模块。核心系统一般情况下只包含一个能够使系统运作起来的最小化模块。而插件模块独立维护的的组件,通常我们需要设定一些规范或规则来连接核心模块。

整体而言微内核有以下优点:
– 整体灵活性高,通过插件模块的松耦合实现,可以将变化隔离起来,并且快速满足需求。通常,微内核架构的核心系统很快趋于稳定,这样系统就变得很健壮,随着时间的推移它也不会发生多大改变。
– 易于部署,插件模块能够在运行时被动态地添加到核心系统中
– 插件模块能够被独立的测试,够非常简单地被核心系统模拟出来进行演示,或者在对核心系统很小影响甚至没有影响的情况下对一个特定的特性进行原型展示。

核心系统

在前端中,核心系统应该是一些最基本的操作规则,比如路由的处理,页面的展示,全局信息的发布,以及模块化方案的选择,插件的加载调度,这些都需要系统服务来处理。也就是说核心模块需要了解插件模块的可用性以及如何获取到它们。

插件模块

插件模块是一个包含特定处理、额外特性的独立组件。自定义意味着增加或者扩展核心系统以达到产生附加的业务逻辑的能力。通常,插件模块之间应该是没有任何依赖性的,但是你也可以设计一个需要依赖另一个插件的插件。但无论如何,插件之间的通信要保持在最低限度,以避免因依赖导致的问题出现。所以,每个插件模块都需要提供一个包含本身信息的描述文件,如果我们依赖NPM服务来创建的话,我们可以利用NPM的package.json来加快整体系统的成型。

在我们内网环境中,我们建立NPM服务来维护管理我们的插件模块,NPM服务提供多维度插件查询接口,在核心系统中,我们可以通过该接口,获取到特定模式的模块。

插件模块的开发

在开发插件模块的时候,需要根据核心系统提供的规则,来设定插件模块的依赖和接口,通常我们需要暴露插件的名称、功能、使用模式等方便构建系统的调度,核心系统收集错误,重启、关闭插件模块等。

插件模块的安装调用机制

1、本地构建,项目源码中引用

在本地的构建系统中,我们通过NPM直接获取插件本身,安装在核心系统的插件目录中,当项目构建打包时,通过打包工具如webpack等生产相应的bundle文件。

  • 优点:
    • 可以使用编辑器的本地联想
    • 可以降低每次插件模块更新带来的影响,对于质量较低的插件模块发生更新后也不会影响到所有引用系统的报错甚至瘫痪
  • 缺点也很明显,就是当我们的比较通用的插件模块发生更新后,我们需要去对没有引用了该插件的模块进行新的构建及发布,这样对于维护通用性较强的插件是一件痛苦的事情。
2、异步加载

插件模块在编译时需要提供最终可使用的bundle文件,发布时直接发布到CDN之类的服务上,在核心系统中我们需要提供一个引用关系描述文件或者结构,每次项目中插件模块发生更新的时候,我们只需要更新这个描述体即可,甚至,我们可以点对点更新项目中的插件,比较登陆模块发生更新,我们需要做的是发布登陆相关的bundle,狠一点直接刷新CDN有良好的基建服务可以使用刷新描述体文件的服务即可。

  • 优点
    • 更新及时,维护大量项目时复杂度很低
    • 独立开发维护
  • 缺点
    • 不能在本地使用编辑器的本地联想
    • 需要大量的测试来保证插件的可用性和有效性,被使用的维度越大,发布新版时心跳就越快!

构建系统

待续。。。

最后

微内核架构需要详尽周全的设计和严格的规范结构,这使得它实现起来相当复杂。版本控制,内部插件注册,插件粒度,广泛使用的插件的质量维护,所有这些都是导致该架构的实现变得复杂的重要因素。所以,如何通过组织搭建一个构建系统来降低这些问题是一件值得探讨和努力的事情。

OpenLDAP 原理、部署、应用

一、目录服务

目录是一个为查询、浏览和搜索而优化的专业分布式数据库,它呈树状结构组织数据,就好象Linux/Unix系统中的文件目录一样。目录数据库和关系数据库不同,它有优异的读性能,但写性能差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据。所以目录天生是用来查询的,就好象它的名字一样。

目录服务是由目录数据库和一套访问协议组成的系统。

二、OpenLDAP 是什么
OpenLDAP 是一款轻量级目录访问协议(Lightweight Directory Access Protocol,LDAP)。

OpenLDAP 属于开源软件,且OpenLDAP 支持LDAP 最新标准、更多模块扩展功能、自定义schema、满足需求、权限管理、密码策略及审计管理、主机控制策略管理、第三方应用平台管理以及与第三方开源软件结合实现高可用负载均衡平台等功能,这也是商业化管理软件无可比拟的。

三、OpenLDAP 功能

查询操作(ldapsearch):允许查询目录并取得条目,其查询性能比关系数据库好。

更新操作(ldapupdate):目录树条目支持条目的添加、删除、修改等操作。

同步操作:OpenLDAP 是一种典型的分布式结构,提供复制同步,可将主服务器上的数据通过推或拉的机制实现在从服务器上更新,完成数据的同步,从而避免OpenLDAP 服务器出现单点故障,影响用户验证。

认证和管理操作:允许客户端在目录中识别自己,并且能够控制一个会话的性质。

四、OpenLDAP 目录架构

主要介绍互联网命名组织架构

LDAP 的目录信息是以树形结构进行存储的,在树根一般定义国家(c=CN)或者域名(dc=com),其次往往定义一个或多个组织(organization,o)或组织单元(organization unit,ou)。

五、OpenLDAP 安装部署
环境: centos 7

安装 OpenLDAP

[root@localhost ~]# yum -y install openldap compat-openldap openldap-clients openldap-servers openldap-servers-sql openldap-devel
[root@localhost ~]# systemctl start slapd
[root@localhost ~]# systemctl enable slapd

设置管理员密码

[root@localhost ~]# slappasswd
[root@localhost ~]# cd /etc/openldap/slapd.d/cn=config
[root@localhost ~]# vi olcDatabase={2}hdb.ldif
olcSuffix: dc=test,dc=com
olcRootDN: cn=Manager,dc=test,dc=com
olcRootPW:{SSHA}LvKLgOHmDx6gMTeqFCDnp+77u4m2C45s
[root@localhost ~]# vi /etc/openldap/slapd.d/cn=config/olcDatabase={1}monitor.ldif

olcAccess: {0}to * by dn.base=”gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth” read by dn.base=”cn=Manager,dc=test,dc=com” read by * none

[root@localhost ~]# slaptest -u

[root@localhost ~]# cp /usr/share/openldap-servers/DB_CONFIG.example /var/lib/ldap/DB_CONFIG

[root@localhost ~]# chown ldap:ldap /var/lib/ldap/*

[root@localhost ~]# ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/cosine.ldif

[root@localhost ~]# ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/nis.ldif

[root@localhost ~]# ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/inetorgperson.ldif

[root@localhost ~]# vim /root/base.ldif

dn: dc=test,dc=com

dc: test

objectClass: top

objectClass: domain

dn: cn=ldapadm ,dc=test,dc=com

objectClass: organizationalRole

cn: Manager

description: LDAP Manager

dn: ou=People,dc=test,dc=com

objectClass: organizationalUnit

ou: People
[root@localhost ~]# ldapadd -x -W -D “cn=Manager,dc=test,dc=com” -f /root/base.ldif

增加用户
[root@localhost ~]# vim /root/user.ldif
dn: cn=lisi,ou=People,dc=test,dc=com
cn: lisi
givenname: lisi
mail: lisi@test.com
objectclass: inetOrgPerson
objectclass: organizationalPerson
objectclass: person
sn: li
uid: lisi
userpassword: {SSHA}d351u8sDyiX2fe8FGf2ZH0MarFp4RAo5

[root@localhost ~]# ldapadd -x -W -D "cn=Manager,dc=test,dc=com" -f /root/user.ldif

测试用户

[root@localhost ~]# ldapsearch -x cn=lisi -b dc=test,dc=com

git原理浅析

使用git作为版本管理工具已经是大势所趋,从之前经验来看git和传统的svn类工具无论是从设计上还是思想上都有非常大的差别,使用svn可以浅尝辄止,但是如果使用git还停留于表面那么一定是事倍功半的,每天写代码因为git使用不熟悉,出现问题不能定位,不能利用git的强大特性支持自己的开发,久而久之会浪费非常多的时间。
所以停下你手中的工作,花几个小时的时间去阅读和理解git。别在这个地方开发中再阻塞自己
https://git-scm.com/book/zh/v2

玩转git请处理git的基本原理,让事半功倍

一、重中之重 git sha1 id

之前在博客浅谈分布式系统ID生成算法有提到过这个Git SHA1 ID的概念,SHA1 ID的理解足够的透彻,对帮助git使用是怎么说都不为过的。
与其说Git是个版本管理系统,不如说Git是个文件系统。git pro如是说“Git 是一个内容寻址文件系统。 看起来很酷, 但这是什么意思呢? 这意味着,Git 的核心部分是一个简单的键值对数据库(key-value data store)。 你可以向该数据库插入任意类型的内容,它会返回一个键值,通过该键值可以在任意时刻再次检索(retrieve)该内容。”
Git系统中每种类型的对象都有一个ID,Git系统中ID有两方面作用,一个是标识另一个更重要的是HASH校验。世界上任何一个被放入Git系统中的文件都会有一个自己唯一的标识,标识自己、标识在整个结构中的目录关系,标识自己在版本历史中的位置。为什么说Git是分布式版本管理,这个是一个很大的原因,不过你是版本库a,还是版本库b,你可以把他们认为是独立的概念,你同样可以把他们定义为一个整体的概念,想象一下全世界所有的Git仓库,共有的、私有的,都是一体的,都是可以共存的,原因不是因为他们物理隔离性,是为Git的id系统。
基于这套哈希系统的可靠性怎么样,可以继续参考浅谈分布式系统ID生成算法中的说明。

二、git 对象

git 有4中对象模型 tree、commit、blob、tag

文件通过blob索引,blog通过tree来组织目录树,commit用来组织版本历史,tag则是对commit对快照。有了这几层概念,就清楚了,拿到了一个commit,就拿到了一个仓库的断点,你可以用它完成追溯

blog是对文件内容的哈希
tree是对文件树的哈希
commit是对tree以及相关提交信息的哈希
tag则是对commit快照的哈希
每个对象sha1哈希出来的key,就是这个key-value系统的id。

命令看下commit
git cat-file -p d297db660b66b1308c7c4b5afcbb1f39e6d372de
tree 90e77ed06942c36734532e26d8a11f390d4bef5c
parent a11c1138c017e961dd27da6e89de8ce7c856d713
author user 1521611242 +0000
committer user 1521614270 +0000

change

Change-Id: I172082da6d169bab6692e5c2d0a5a040bb957acc

看一个tree的
git cat-file -p 90e77ed06942c36734532e26d8a11f390d4bef5c
100644 blob 63a60bbad68714a96dae200270e54e9ecbd8a2e3 .gitignore
040000 tree 732ae1b65d9cf6fae13a532773ce836ab69a14b5 PushServer
040000 tree cecff1e4df3268ce3dded6d10ad71cafb3e67ece adapter
040000 tree 70dd525ec9a93253464338fb426cd395dfbcf19c command
040000 tree 060db777d1761434b3a2a621e5057d727c2addea hpservice
040000 tree e7e527ec72fd9b73e668244db6fffea1d5538897 idl
040000 tree 4bb41eb70c8389024ef067a9ef4c1b34e91be17e include
040000 tree 3d178fea2a5ebf673e8b8a8e5133c3b9b96d5ed0 service
040000 tree 4c59e9b135026aa0a3e6e04f46090bcd38219e1e util
040000 tree ee1ac12bb737381585b1c56ffc5b16834cba79cc zyservice

请把这个图印在自己的脑子里!!!

请把这个图印在自己的脑子里!!!

请把这个图印在自己的脑子里!!!
重要的话说三遍

三、git内部状态和差异比较

在 Git 内都只有三 种状态:已提交(committed),已修改(modified)和已暂存(staged)

直接快照,而非比较差异,这个和svn有非常本质的区别,为什么这样?因为前面提到的git存储设计

四、Git是分布式的分布式的

你修改的都是你本地的,不会影响到别人,只要不操作push就什么问题都没有。所以一切一切怕弄个分支影响到别人都是不存在的
分布式版本管理

集中式版本管理

bridge、bridge_retained、bridege_transfer

Core Foundation 和Cocoa Framework::Foundation的转化

用到的三个关键字:bridge、bridge_retained、bridege_transfer

bridege:

1、只做类型转化处理,对对象的所有权不做修改(对引用计数不做处理);
2、将objective-c的对象类型用 __bridge 转换为 void* 类型和使用__unsafe_unretained (arc下的weak)关键字修饰的变量是一样的。被代入对象的所有者需要明确对象生命周期的管理,不要出现异常访问的问题。

bridge_retained:

1、一般常用于OC类型转为CF类型;
2、转化后的CF对象引用计数+1;
(需要添加CFRelease方法对CF进行释放)

bridege_transfer:(隐性添加strong关键字修饰)

1、常用于CF类型转化为OC类型;
2、转化后自动将CF类型引用计数-1;
(不需要调用CFRelease方法释放CF指针)

一、需要特别明确被转换类型是否是 ARC 管理的对象

1、Core Foundation 对象类型不在 ARC 管理范畴内
Core Foundation是C语言接口,需要手动释放。(CFRelease)
2、 Cocoa Framework::Foundation 对象类型(即一般使用到的Objectie-C对象类型)在 ARC 的管理范畴内

二、如果不在 ARC 管理范畴内的对象,那么要清楚 release 的责任应该是谁

UITableView 刷新机制

UITableView 的代理方法

在实际开发过程中,大多数情况下,在网络接口返回数据后,我们会执行 reloadData,去刷新表格,更新数据,随后会进入一系列UITableViewDataSource和UITableViewDelegate的回调;一般会执行下面的方法:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{   NSLog(@"numberOfRowsInSection");
    return self.productList.list.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    RDProductTableViewCell *cell = [tableView   dequeueReusableCellWithIdentifier:NSStringFromClass([RDProductTableViewCell class]) forIndexPath:indexPath];
     NSLog(@"numberOfRowsInSection");
     TKReadingProductModel *product = [self.productList.list objectAtIndex:indexPath.row];
    return cell;
}

方法2中返回的cell个数的数组和方法3中取值的数组是同一个数组。但是我们经常能看到数组越界的崩溃,例如:[__NSArrayM objectAtIndexedSubscript:]: index 5 beyond bounds for empty array,下面是崩溃堆栈信息

UIKitCore   -[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] + 684
6 UIKitCore -[UITableView _createPreparedCellForGlobalRow:willDisplay:] + 80
7 UIKitCore -[UITableView _updateVisibleCellsNow:isRecursive:] + 2112
8 UIKitCore -[UITableView layoutSubviews] + 140
9 UIKitCore -[UIView(CALayerDelegate) layoutSublayersOfLayer:] 

在创建cell时数组越界崩溃,由此可见,在这两个方法执行的过程中,数据源self.productList.list被修改了。

    NSLog(@"Reload Begin");
    [self.tableView reloadData];
    NSLog(@"Reload End");

调用reloadData打印日志

Reload Begin
numberOfRowsInSection
Reload End
cellForRowAtIndexPath

如上所示,调用reloadData 之后,立即调用numberOfRowsInSection,但是cellForRowAt 是异步调用,回到当前RunLoop,布局cell时才会被调用.
reloadData 这样的特性就导致了没有及时调用相对应的代理方法;在于异步执行cellForRowAtIndexPath的时候,我们所依赖的状态可能会发生变化,上面代码中的list如果元素被修改过,极有可能发生数组越界的异常。

当列表界面数据不怎么变化的时候,这种异常很少出现,因为reloadData返回之后,下一次loop就开始执行异步的操作了。但是当列表界面的数据有可能经常变化的时候,尤其是在多线程的场景下,就会出现偶现的bug了。

解决方案

方式一:在使用[self.productList.list objectAtIndex:indexPath.row]前,做长度检查,如果索引大于数组长度,直接返回nil

- (id)safeObjectAtIndex:(NSUInteger)index
{
    if (index >= self.count) {
        return nil;
    } else {
        return [self objectAtIndex:index];
    }
}

方式二:想要调用reloadData 之后立即调用所有代理方法,我们可以添加layoutIfNeeded 让TableView强制布局

    [self.tableView reloadData]
   [self.tableView layoutIfNeeded];

Chrome前端调试—代理插件

Chrome前端调试—代理插件

前端开发过程中,经常会有需要对远程环境调试或者本地mock数据的需求。比如,修改线上bug,开发环境不在本地,后端只提供了接口文档等等。

我理想中的请求映射工具应该是这样的:简单,打开浏览器就能用、支持目录映射和文件映射、跨平台。 ReRes以及偷天换日就是居于这个目标写出来的,您可以把请求映射到其他的url,也可以映射到你本机的文件或者目录。ReRes支持单个url映射,也支持目录映射。

  1. 开始使用ReRes

    首先从chrome商店安装ReRes:地址

    安装完毕后,在地址栏输入chrome://extensions/进入扩展页,找到ReRes,勾选“允许访问文件网址”,这样才能让ReRes支持本地映射,如下图:

    至此,ReRes就可以使用了。下面是一些基本功能的使用操作方法:

    添加规则

    点击“添加规则”按钮,输入以下信息,然后保存:

  • If URL match: 一个正则表达式,当请求的URL与之匹配时,规则生效。注意:不要填开头的/和结束的/g,如/.*/g请写成.*

  • Response: 映射的响应地址,线上地址以http://开头,本地地址以file:///开头,比如http://cssha.comfile:///D:/a.js

    启动/禁用

    勾选/取消对应规则前面的勾选框即可。

    编辑规则

    鼠标移到响应规则上,点击“编辑”。

    删除规则

    鼠标移到响应规则上,点击“删除”。

    批量导入规则

    点击“管理规则”按钮进入管理页,点击顶部“导入”按钮,即可导入规则列表文件。规则列表文件是一个json文件,其格式如下:

    [
    {
        "req": ".*auth",
        "res": "https://www.ibanyu.com:30000/auth",
        "checked": false
    },
    {
        "req": ".*/opapi",
        "res": "https://test.ipalfish.com:30000/opapi",
        "checked": true
    },
    {
        "req": ".*/ugc",
        "res": "http://rap2api.taobao.org/app/mock/118135/ugc",
        "checked": false
    },
    {
        "req": ".*/wechatcourse/",
        "res": "http://rap2api.taobao.org/app/mock/118135/wechatcourse/",
        "checked": false
    },
    {
        "req": ".*/thirdparty/wechat",
        "res": "https://www.ipalfish.com/klian/thirdparty/",
        "checked": false
    },
    {
        "req": ".*/wechat",
        "res": "http://rap2api.taobao.org/app/mock/118135/wechat",
        "checked": false
    },
    {
        "req": ".*/base",
        "res": "https://www.ipalfish.com:30000/klian/base",
        "checked": false
    }
    ]
    

    其中相关字段含义如下:

    • req:请求所匹配的正则表达式(对应于If URL match输入框的内容)
    • res:映射的响应地址(对应Response输入框的内容)
    • checked:是否启用
  • 在日常开发中经常会有后端同学定义了接口,但是没有环境的同步开发流程。这个时候就需要借助一些mock数据平台来模拟数据。常用平台有:http://rap2.taobao.org/

    类似下面模拟操作:

    [
    {
        "req": ".*/ugc",
        "res": "http://rap2api.taobao.org/app/mock/118135/ugc",
        "checked": false
    }
    ]
    

  1. 偷天换日(开发者为前项目组同事—见智)

    首先从chrome商店安装偷天换日:

    地址

Quicklink-利用最新的浏览器特性让你的链接实现秒开

关于Quicklink

Quicklink是18年11月份由谷歌开源的一项新技术,贡献者是来自Google Chrome的工程师Addy Osmani,同时也是《JavaScript设计模式》的作者。Qucklink旨在为网站提供一套解决方案,预获取处于用户视区中的链接,同时保持极小的体积(minifiy/gzip 后 <1KB)。

工作原理

Quicklink 可以在空闲时间预获取页面可视区域(以下简称视区)内的链接,加快后续加载速度。

通过以下方式加快后续页面的加载速度:

  • 检测视区中的链接(使用 Intersection Observer)。
  • 等待浏览器空闲(使用 requestIdleCallback)。
  • 确认用户并未处于慢速连接(使用 navigator.connection.effectiveType)或启用省流模式(使用 navigator.connection.saveData)。
  • 预获取视区内的 URL(使用 或 XHR)。可根据请求优先级进行控制(若支持 fetch() 可进行切换)。

在VUE及React中的应用

关于在现代框架中的使用,官方文档提及比较少,Github也没有比较成熟的组件实现。不过利用quicklink可以针对DOM调用的特性,我们可以先通过ref拿到组件dom,然后初始化quicklink时传入组件dom就可以了。具体实现可以参考掘金的两篇文章

适用范围

Quicklink最适合的场景应该是内容提供类的网站,例如博客,新闻类。如果网站内直接跳转不多或者用户点击概率不高,则需要衡量一下维护难度与用户体验之间的平衡点。另外如果是站外链接的话,可能会遇到CORB 以及 CORS 问题!大家可能都听过CORS,关于CORB大家如果想了解的可以参考这篇文章:30 分钟理解 CORB 是什么

其他

其他的一些详细配置大家可以在官方文档中找到。

浅谈yycache之《二》 磁盘缓存的实现原理

上一篇中,介绍了YYDiskCache的使用,本篇介绍一下它的实现。
YYDiskCache支持在一个app中创建多个缓存实例,每个独立的实例都必须以独立的路径为准,多次创建同一路径的cache,返回的可能是同一个实例。YYCache管理了一个全局集合,类型为NSMapTable,所有的DiskCache实例都保存在这个Map中,该集合是线程安全的,由一个单独的信号量机实例保证。 创建DiskCache对象会先从Map中找对应路径的,没有才会创建新的。

_globalInstances = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];

NSMapTable是Foundation下的API,可以扩展key和value的内存语义,比NSDictionary要强大,上面这句话的意思就是,全局集合中的value是weak的,可以随时被释放,不会造成相互引用。

每一个YYDiskCache都由一个YYKVStorage,一个信号量锁,和一个并发任务队列构成。YYKYStorage封装了对SQL、文件的读写操作,信号量保证线程安全,并发队列提升效率。

@implementation YYDiskCache {
    YYKVStorage *_kv;
    dispatch_semaphore_t _lock;
    dispatch_queue_t _queue;
}

KVStorage的操作的两份内容 DB/File,结构如下:

/*
 File:
 /path/
      /manifest.sqlite
      /manifest.sqlite-shm
      /manifest.sqlite-wal
      /data/
           /e10adc3949ba59abbe56e057f20f883e
           /e10adc3949ba59abbe56e057f20f883e
      /trash/
            /unused_file_or_folder

 SQL:
 create table if not exists manifest (
    key                 text,
    filename            text,
    size                integer,
    inline_data         blob,
    modification_time   integer,
    last_access_time    integer,
    extended_data       blob,
    primary key(key)
 ); 
 create index if not exists last_access_time_idx on manifest(last_access_time);
 */

这个path的父目录即创建diskcache时外部设置的path。
/manifest.sqlite是整个cache的数据库,另外两个文件.sqlite-shm/.sqlite-wal 是数据库产生的临时文件,一个代表共享内存(shared memory),一个代表日志(write-ahead log)。DB删除的时候,需要把这两个文件一起删除掉。
/data/目录存储了我们缓存的所有文件,和实际文件之间的二进制对应关系可以自定义,默认是archeive方式,文件名也可以自定义,默认是MD5方式,上一篇文章中已经说过。
/trash/是data中文件删除前的一道屏障,相当于回收站。不过当前使用的时候,是在reset时机,这时,两个文件夹中的文件会被先后删除。

SQL 即在manifest.sql库中创建一个manifest表,以记录每一条缓存数据。包括以文件形式存储的,只是文件的话,inline_data为空。每次对缓存的操作都由last_access_time modification_time进行记录。

Storage中定义实现了各种增删改查的操作,以及数据库的建立关闭。尤其是数据库的操作,涉及每一个缓存对象,所以在对象dealloc的时候,向系统申请了后台额外时长,以防止后台时进程被系统杀死,导致存储异常。

- (void)dealloc {
    UIBackgroundTaskIdentifier taskID = [_YYSharedApplication() beginBackgroundTaskWithExpirationHandler:^{}];
    [self _dbClose];
    if (taskID != UIBackgroundTaskInvalid) {
        [_YYSharedApplication() endBackgroundTask:taskID];
    }
}

beginBackgroundTaskWithExpirationHandler 这个方法会申请3分钟左右的后台存活时间,ios7以前可以存活5-10分钟。这个方法必须与 endBackgroundTask 配对使用。

_YYSharedApplication() 的定义引出了引出了另一个概念:App Extension,看一下这个定义:

/// Returns nil in App Extension.
static UIApplication *_YYSharedApplication() {
    static BOOL isAppExtension = NO;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class cls = NSClassFromString(@"UIApplication");
        if(!cls || ![cls respondsToSelector:@selector(sharedApplication)]) isAppExtension = YES;
        if ([[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]) isAppExtension = YES;
    });
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
    return isAppExtension ? nil : [UIApplication performSelector:@selector(sharedApplication)];
#pragma clang diagnostic pop
}

App Extension的概念相信大家也见过,就是apple定义的一些app增强功能,包括widget, 分享,快速回复等等。以后文章专门介绍一下。官方文档:https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/index.html#//apple_ref/doc/uid/TP40014214-CH20-SW1
它的基本生命周期图如下:

总结一下:
1、YYDiskCache线程安全
2、YYDiskCache维护了一个全局集合,用于存储各个Cache对象,value全部为弱引用
3、YYDiskCache的操作行为主要由YYKVStorage来代理,数据库创建打开关闭销毁,增删改查,文件读写。
4、YYKVStorage做了进程后台免杀死的基本保护,beginBackgroundTaskWithExpirationHandler和endBackgroundTask要成对出现。
5、UIApplication有可能是 App Extension,并没有sharedApplication方法,需要加以区分。
6、NSMapTable是Foundation的API,功能强大,支持对key和value内存语义更广泛的定义。