了解了iOS应用内购买流程后,需要开发一个模块,将其集成到原有项目中。该模块实现虚拟币的应用内购买,能支持原生App调用和Web调用。需求就这样,我用了些功夫捣鼓出来了,将过程纪录一下。

模块设计

或许觉得功能不难,给我的需求就简单几句话。在编码之前我想弄清楚自己开发的模块是怎么样的,该怎么与外界交互。我要找到个工具将这点弄明白,于是想到画UML图。

UML活动图

UML建模之活动图介绍(Activity Diagram)了解了一下,画活动图比较好,感觉这个大概能达到我的目的。于是画了如下两个图,分别表示IAP两个阶段发生的事:

  • 请求商品信息

    请求商品信息

  • 购买商品

    购买商品

简单介绍

当时了解到的需求是只用提供Web调用的接口,参照Android的设计,我以为本模块只用负责逻辑处理部分,不涉及UI展示,所以设计了以上活动图,我的工作主要是App部分。后来需求改了。

需求改变

  • 本模块负责IAP所有事项,包括UI展示
  • 本模块需要能被原生App调用
  • 考虑到虚拟币商品的固定性,简化购买过程
    • 虚拟币商品比较固定,展示商品时可以拿本地商品信息展示,而不用等到App Store返回商品信息
    • 将请求商品信息与购买商品合并到“确定支付”过程中

UML时序图

需求更改后活动图有变化,可第一版代码编写后,模块与外界的交互已经比较好理解,我就没再上新图了。我用另外一种图确定了模块内的设计。顺便学习一下时序图

IAP时序图

简单介绍

  • 调用者
    • ViewController:这里是App原生调用代码。考虑到易嵌入性,我这次放弃使用代理方式,而选择了Block作为反馈处理。但愿代码中没有出现Cycle Retain
  • IAP模块
    • IAPViewController:模块的主要处理类,负责与用户交互,及调用IAPManagerIAPObserver 与App Store进行交互;
    • IAPManager:负责处理请求商品信息;
    • IAPObserver:负责处理购买商品。

IAPManagerIAPObserver只将过程信息打成log,错误信息提示等与用户交互相关的处理通过Block交给IAPViewController

Demo

iapTest

后续

  • UML图是利用在线工具processon画的,保存什么的还是很方便,不过功能不及EA等;
  • 设计与代码都在学习中,欢迎一起交流。

看了一些网上教程,基本上是老版本的了。我针对自己遇到的一些问题,结合官方文档把IAP(In-App Purchase)过程梳理了一下。
P.S. 官方文档才是王道!

编码之前

应用内购买要和App Store发生交互,这里在正式编写代码前需要做几个工作。

  • 完善账户信息

    收费App、含应用内购买的App等有付费功能的需要完善这部分信息。

    1. 进入iTunes Connect

      iTunes Connect是苹果提供的一个平台,主要提供App发布和管理App的,最重要的功能是创建管理项目信息,项目付费产品(道具)管理、付费的测试账号、提交App等等。

    2. 进入协议、税务和银行业务

      这一部分具体可以参照这篇iOS App提交指南(二)-协议、税务和银行业务
      协议税务银行业务

  • 创建App

    1. 进入iTunes Connect
    2. 进入我的App
      进入新建App
    3. 新建App

      这里说一下,套装ID就是Bundle ID,保证和Xcode项目中的Bundle ID一致。其实不晓得填的地方点击那个小问号就有提示了。(我不知道为嘛要截这么多图,或许会让文章显得亲切点😄)
      新建App
      对了,新建App时要保证应用内购买的功能时勾选上的。

  • 创建商品

    建好支持应用内购买的App后,就可以该App可购买的商品了。

    1. 创建App内购买项目

      依次点击{创建的App名} -> 功能 -> App内购买项目 -> +
      创建App内购买项目

    2. 选择项目类型
      项目类型

    一般对项目来说大多数都是选择“消耗型项目”这个种类,比如游戏中购买虚拟货币等。具体区别请看这里

    1. 项目摘要
      项目摘要

      • 参考名称:商品名称,可以根据商品等实际意义填写,不会显示在App Store
      • 产品ID:要求唯一性,可以用App的Bundle ID加后缀表示
      • 价格等级:苹果的销售商品不能随意定价,按等级选择合适的即可。点击查看价格表可以看到各等级价格,以及商品卖出后你的实际收益。
        价格标准
        其中CNY为人民币
    2. 项目详情
      项目详情

      • 语言:至少添加一种语言的项目描述
      • 审核备注:我填了测试账户信息
      • 屏幕快照:按要求上传,我传的是支付页面的屏幕截图
    3. 等待审核
      等待审核

      !这个状态下已经能编码对相应商品就行购买测试了,审核通过的状态得等到App提交后才行

  • 申请测试账号

    用户与职能

    1. 进入用户与职能
    2. 点击沙箱技术测试员
    3. 点击+添加新账号

      注意:

    • 账号记不住密码就删除,再用不同的邮箱重新创建
    • 账号创建后不能被修改
    • 沙盒测试账户被删除后,该Apple ID也不能再用作沙盒测试用户iTunes Connect用户
    • 账号邮箱可以是随意编造
    • 该账号不能用来在正式的App Store上登录,只用于测试环境下
  • 测试账号的使用

    1. 清除测试设备的账号信息
    2. 在设备的”设置”里退出App Store账号(这能避免测试过程中真实账号被使用)
    3. 在Xcode中将App编到测试设备
    4. 在测试时,App会要求登录,这时候选择测试账号登录,完成交易

代码部分

  • 购买流程

    购买阶段

    总的来说,交互分为三个阶段:

    1. 获取商品信息: app向App Store请求商品信息,并展示;
    2. 购买请求: 用户选择商品,由app向App Store请求购买;
    3. 交付商品: App Store处理支付请求,app交付商品。

      !在Xcode中要加入StoreKit.framework

  • 获取商品信息

    1. 从App Bundle或者自己的服务器上获取商品的ID

      1
      // Load the product identifiers fron ProductIds.plist
      NSURL *plistURL = [[NSBundle mainBundle] URLForResource:@"ProductIds" withExtension:@"plist"];
      NSArray *productIds = [NSArray arrayWithContentsOfURL:plistURL];

      这里的product id就是在iTunes Connect中创建的应用内购买项目的ID。

    2. 将商品ID集合发给App Store(利用SKProductsRequest)

      1
      // Create a product request object and initialize it with our product identifiers
      SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:productIds]];
      request.delegate = self;
         
      // Send the request to the App Store
      [request start];

      这里由StoreKit发起异步请求。

    3. 将App Store返回的商品信息展示(返回的商品用SKProduct表示)

      1
      - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
      {
         ...
      }
  • 购买请求

    1. 向SKPaymentQueue添加一个购买请求

      1
       SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
      [[SKPaymentQueue defaultQueue] addPayment:payment];

      SKPaymentQueue会自动向App Sotre 提交购买请求。

    2. 给SKPaymentQueue添加监听器,该监听器实现了SKPaymentTransactionObserver协议

      1
      // Attach an observer to the payment queue
      [[SKPaymentQueue defaultQueue] addTransactionObserver:[StoreObserver sharedInstance]];

      demo中是在App启动时就添加了监听器。
      主要实现的方法是下面这个更新用的:

      1
      // Called when there are trasactions in the payment queue
      - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
      {
      	for(SKPaymentTransaction * transaction in transactions)
      	{
      		switch (transaction.transactionState )
      		{
      			...
      			// 购买成功
      			case SKPaymentTransactionStatePurchased:
      				...
      				break;
      			// 恢复交易
      			case SKPaymentTransactionStateRestored:
      				...
      				break;
      			// 购买失败
      			case SKPaymentTransactionStateFailed:
      				...
      				break;
      			default:
      				break;
      		}
      	}
      }

      Restore这种交易状态是恢复。如果有些人在iPhone上用一个账号购买了一个产品,那么在iPad上又下载了这个应用,就不需要重新购买了。通过Restore在App Store中检测你这个账号的购买记录,如果有购买记录存在,那就不用再次购买了,直接恢复,就会出现restoreTransaction。关于 商品恢复,请点击

    • 会打断app工作流,不应该每次启动时执行,应该让用户自己触发
  • 交付商品

    1. 将购买成功的商品纪录保存,以便下次启动时用
    2. 调用SKPaymentQueue的finishTransaction方法
  • demo

    demo项目修改

    • 点击下载demo
    • 将工程target中的Bundle Identifier改成前面创建的App ID
    • 在工程中的ProductIds.plist中添加前面在iTunes Connect中创建的商品ID
    • 编译运行

参考

以前多多少少接触过一些shell命令,用的不多,现在打算好好了解一下。看了Linux Shell脚本教程,这里做点笔记:

Shell 在线环境

tutorialspoint

几种常见的Shell

  • bash: Linux标准默认的shell,内部命令一共有40个。
  • sh: Unix 标准默认的shell。

bash完全兼容sh,也就是说,用sh写的脚本可以不加修改的在bash中执行。

不使用Shell的情况

  1. 资源密集型的任务,尤其在需要考虑效率时(比如,排序,hash等等)。
  2. 需要处理大任务的数学操作,尤其是浮点运算,精确运算,或者复杂的算术运算(这种情况一般使用C++或FORTRAN 来处理)。
  3. 有跨平台(操作系统)移植需求(一般使用C 或Java)。
  4. 复杂的应用,在必须使用结构化编程的时候(需要变量的类型检查,函数原型,等等)。
  5. 对于影响系统全局性的关键任务应用。
  6. 对于安全有很高要求的任务,比如你需要一个健壮的系统来防止入侵、破解、恶意破坏等等。
  7. 项目由连串的依赖的各个部分组成。
  8. 需要大规模的文件操作。
  9. 需要多维数组的支持。
  10. 需要数据结构的支持,比如链表或数等数据结构。
  11. 需要产生或操作图形化界面 GUI。
  12. 需要直接操作系统硬件。
  13. 需要 I/O 或socket 接口。
  14. 需要使用库或者遗留下来的老代码的接口。
  15. 私人的、闭源的应用(shell 脚本把代码就放在文本文件中,全世界都能看到)

Shell脚本

  • “#!” 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种Shell。
  • 变量

    • 变量名和等号之间不能有空格。
    • 定义变量时,变量名不加美元符号($);使用一个定义过的变量,只要在变量名前面加美元符号($)即可。
    • 变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界。建议使用时在所有变量外加花括号。
    • 使用readonly命令可以将变量定义为只读变量,只读变量的值不能被改变。
    • 使用unset命令可以删除变量。
    • 变量类型:
      1. 局部变量:在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
      2. 环境变量:所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
      3. shell变量:由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行。
    • 特殊变量:
      1. $0 当前脚本的文件名
      2. $n 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2。
      3. $# 传递给脚本或函数的参数个数。
      4. $* 传递给脚本或函数的所有参数。
      5. $@ 传递给脚本或函数的所有参数。被双引号(“ “)包含时,与 $* 稍有不同,下面将会讲到。
      6. $? 上个命令的退出状态,或函数的返回值。
      7. $$ 当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。
    • $*$@ 的区别

      $ 和 $@ 都表示传递给函数或脚本的所有参数,不被双引号(“ “)包含时,都以”$1” “$2” … “$n” 的形式输出所有参数。但是当它们被双引号(“ “)包含时,”$“ 会将所有的参数作为一个整体,以”$1 $2 … $n”的形式输出所有参数;”$@” 会将各个参数分开,以”$1” “$2” … “$n” 的形式输出所有参数.

  • Shell替换

    • echo命令的 -e 表示对转义字符进行替换,-E 选项禁止转义,默认也是不转义的;使用 -n 选项可以禁止插入换行符。
    • 变量替换
      1. ${var} 变量本来的值
      2. ${var:-word} 如果变量 var 为空或已被删除(unset),那么返回 word,但不改变 var 的值。
      3. ${var:=word} 如果变量 var 为空或已被删除(unset),那么返回 word,并将 var 的值设置为 word。
      4. ${var:?message} 如果变量 var 为空或已被删除(unset),那么将消息 message 送到标准错误输出,可以用来检测变量 var 是否可以被正常赋值。
        若此替换出现在Shell脚本中,那么脚本将停止运行。
      5. ${var:+word} 如果变量 var 被定义,那么返回 word,但不改变 var 的值。
  • 注释

    • 只有单行注释,用#
    • 如果在开发过程中,遇到大段的代码需要临时注释起来,可以把这一段要注释的代码用一对花括号括起来,定义成一个函数,没有地方调用这个函数,这块代码就不会执行。
  • 字符串

    • 单引号:单引号字符串中的变量是无效的;单引号字串中不能出现单引号(对单引号使用转义符后也不行)。
    • 双引号:双引号里可以有变量,双引号里可以出现转义字符。
    • 获取字符串长度:
      ${#string} #输出 4```
      1
      - 提取子字符串:
      ```echo ${string:1:4} #输出liba
  • 数组

    • 用括号表示
    • 使用@ * 可以作下标获取数组中的所有元素
  • printf命令

    与C语言中的printf的不同:

    • 不用加括号
    • 参数的分隔用空格,不是逗号
    • 格式字符串能重用,可以将所有参数都转换
  • case … esac

    • 类似于C中的switch ...case...语句
    • 取值后面必须为关键字 in,每一模式必须以右括号结束
    • ;; 与其他语言中的 break 类似
    • * 类似于default,捕捉其他情况
  • 运算符

    • expr

      1. 注意表达式和运算符之间要有空格;
      2. 乘号(*)前边必须加反斜杠()才能实现乘法运算。
    • 关系运算符:只支持数字,不支持字符串,除非字符串的值是数字。

    • 布尔运算符:
      1. ! 非运算
      2. -o 或运算
      3. -a 与运算
    • 字符串运算符:

      1. = 检测两个字符串是否相等;算数运算符的相等比较用==
      2. != 检测两个字符串是否相等
      3. -z 检测字符串长度是否为0
      4. -n 检测字符串长度是否为0
      5. str 检测字符串是否不为空
    • 文件测试运算符:

      1. -b file 检测文件是否是块设备文件,如果是,则返回 true。 [ -b $file ] 返回 false。
      2. -c file 检测文件是否是字符设备文件。
      3. -d file 检测文件是否是目录。
      4. -f file 检测文件是否是普通文件(既不是目录,也不是设备文件)。
      5. -g file 检测文件是否设置了 SGID 位。
      6. -k file 检测文件是否设置了粘着位(Sticky Bit)。
      7. -p file 检测文件是否是具名管道。
      8. -u file 检测文件是否设置了 SUID 位。
      9. -r file 检测文件是否可。
      10. -w file 检测文件是否可写。
      11. -x file 检测文件是否可执行。
      12. -s file 检测文件是否不为空(文件大小是否大于0)。
      13. -e file 检测文件(包括目录)是否存在。
  • break命令

    • 跳出循环,不跳出case
    • 在嵌套循环中,break 命令后面还可以跟一个整数,表示跳出第几层循环
  • 函数

    • 定义函数时,函数名前加上关键字 function 可有可无
    • 可以显式增加return语句;如果不加,会将最后一条命令运行结果作为返回值
    • 调用函数只需要给出函数名,不需要加括号
    • 删除函数也可以使用 unset 命令,不过要加上 .f 选项
    • 将函数定义在主目录下的 .profile 文件,这样每次登录后,在命令提示符后面输入函数名字就可以立即调用
  • 输入输出重定向

    1. command > file 将输出重定向到 file。
    2. command < file 将输入重定向到 file。
    3. command >> file 将输出以追加的方式重定向到 file。
    4. n > file 将文件描述符为 n 的文件重定向到 file。
    5. n >> file 将文件描述符为 n 的文件以追加的方式重定向到 file。
    6. n >& m 将输出文件 m 和 n 合并。
    7. n <& m 将输入文件 m 和 n 合并。
    8. << tag 将开始标记 tag 和结束标记 tag 之间的内容作为输入。

      /dev/null 文件是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到.

  • 文件包含

    • Shell 中包含脚本可以使用:. filenamesource filename
    • 被包含脚本不需要有执行权限

今天是GIF2016的第一天。我很早就在关注这个大会,并且报名了。可这两天事情比较多,本来还有犹豫今天要不要去。去了后听到了有用的东西,还是觉得很值得的,也不枉我请假,早起,各种赶车从京城西北赶到798。
GIF大会
去了很多大牛,基本都是很出色公司的创始人/CEO之类的,有Uber/积木盒子/蓝港互动/100offer/努比亚等。大牛的故事听起来都是各种味道,激情满满,我已经有点免疫了。不过有两个人说的我想记录一下,他们的话让我有了新的角度。是两个不同年龄段人为梦想的努力

张向东, 700bike

这句话先触动了我

哪里有什么模式可复制,唯有试错,反思再前进

他说的这句话没什么特别的,却吸引了我。最近总是觉得自己畏手畏脚,很想要找到那种不顾一切的闯劲,所以这话很对我的口。
他开始谈自我否定,他认为不是所有行业都存在单品爆款,不应该试图用一个app改变一个行业。我听到的意思是:

跨行业创业,应该站在你要发展的行业看待问题

很多人,包括我之前对互联网+或者互联网与其他行业的合作都是用站在“互联网”这边角度思考,对传统行业往往用的就是“改造”,甚至是“颠覆”之类的字眼。而一个小产品就想改变一个行业,张向东认为那太小瞧那个行业了。所以他现在给自己的定位是「城市自行车」的创新,而不是智能硬件的创新。
这在我看来就是

一个人实现了财务自由,然后带着他在以往工作领域积累的经验去逐梦,在爱好的领域开始新的创业。他将自己定位在新的行业中,用以往领域的知识让现在的行业变得更好。

齐俊元, Teambition

关注到他,是因为最近想提升团队的工作效率,试用了几款协同工具。齐俊元说的是他做这个工具的理想,他希望做点真正对社会有用的东西,不管怎样的动机,这也是个有情怀说法,也是个逐梦的故事。而他,还只是个90后。
我希望工具能提升团队的效率,而齐俊元在说这个产品形成过程中的故事时,提到了一句:

扁平化决策比扁平化机构更重要

这个让我觉得很重要。现在公司说要做**扁平化组织结构**,貌似也确实是这样子的,大家都能很快和领导沟通到,领导也会让大家看到不少信息。可是效率依然不高。原因或许就在于决策还不是扁平化的。这种情况下,大家不能真正参与到决策中来,你扁平化的机构给员工再多信息也白搭,人家根本不care,他觉得反正自己也没有发言权。

其他

VR体验

随着苹果调整组织架构,正式涉足VR/AR领域,VR/AR看上去都快要到爆发的时候了。我这次过来想重点看看这方面的行业发展,毕竟这是老本行,以前就是研究这个的。
诺亦腾发布alice

试玩了几个头戴式设备,体验没有特别的,后来听说还有个可以用三星手机屏当头戴设备的,没来得及体验。诺亦腾发布alice项目,说是可以对动作进行毫米级捕捉,这个很不错,不过不知道传感器能否隐藏起来,放在外面总是影响使用的。
至于他们的增强现实,我只能说渲染的场景还是不够逼真,我在设备中看到的场景就模模糊糊的,人肯定没法长时间戴着那个在场景中。

700bike自行车体验

中午的时候我进了体验区,逛着逛着被一个摆着好些个漂亮自行车的展区吸引住了。首先就觉得这是个智能硬件的厂商,然后上去就问人家“和普通自行车有啥不一样”“都有哪些智能功能”之类的,当听说这自行车就是“装载GPS,拥有显示屏”“可以防盗,可以显示骑行速度…”时不免有些失望。现在看来那会真是只站着”互联网”角度去想“颠覆”之类的了。
除了脸,其他都清晰

我被色彩鲜艳的车子吸引了,加上本来也对这个有兴趣,就试骑了一下,体验还是不错的,还有自动变速。可我对这个公司的看法还是在听了张向东演讲后改变的。

链接

这是在微软技术大会Ignite2015上听李智桦老师推荐的视频,说得是DevOps(开发运维一体化),旨在推动IT开发效率。

视频中的两个小伙来自Flickr,人家一天能发布十多个版本。这效率也真是让人醉了,开发运维搅在一起,也真是激情无限。做了点笔记(很久不用英语,听错了的请轻拍):

##要点

  1. 自动化基础
    • 角色/配置管理
      • 系统镜像?
  2. 统一版本控制方式
    • 运维人员也需
    • 让所有人都知道该干什么
  3. 一步编译/部署
    • 一个操作解决
    • 工具自动记录时间/任务/事情
    • 小而频繁地更改
  4. 特征标记(分支)
    桌面软件

    • 可以开发很多功能,完成之前不对外开放
    • 不同开发语言都有?
    • 水桶测试

      设想把编程看成是转动曲柄从井里提一桶水上来的过程。如果水桶比较小,那么仅需一个能自由转动的曲柄就可以了。如果水桶比较大而且装满水,那么还没等水桶全部被提上来你就会很累了。你需要一个防倒转的装置,以保证每转一次可以休息一会儿。水桶越重,防倒转的棘齿相距越近。 测试驱动开发中的测试程序就是防倒转装置上的棘齿。一旦我们的某个测试程序能工作了,你就知道,它从现在开始并且以后永远都可以工作了。相比于测试程序没有通过,你距离让所有的测试程序都工作又近了一步。现在我们的工作是让下一个测试程序工作,然后再下一个,就这样一直进行。分析表明,要编程解决的问题越难,每次测试所覆盖的范围就应该越小。

    • Dark Lauches
      简单说,就是开发新功能是要能使之方便地开启/关闭,可以很好应对突发事故。

  5. 统一的度量标准

    • 开发人员可以看到运维情况(CPU/网络等情况)
  6. 即时聊天工具/机器人

    • 远程办公
      • 开发/运维/机器人可以进行有情景地沟通,更好传达信息

文化

  • 不要有偏见
  • 尊重他人的工作/观点/技能
  • 别隐藏东西

开发对运维说对代码的影响:

  • 什么标注(CPU/网络…)会更改,怎么改?
  • 风险是什么?
  • 事情变糟糕的迹象时啥?
  • 突发事件时啥?

信任

  • 运维应该信任开发,邀其讨论需求
  • 开发应该信任运维,与之讨论基础变更
  • 每个人都应该相信其他人都在为工作努力

正确面对失败

如果你认为你可以避免失败,你就失去了锻炼应对失败的能力。

我先前有一些前端接触,html/css/js语言基础具备,也接触过Node.js /AnjularJS,不过因为从来没系统地学习过,前端技术丰富且更新快,我自己感觉一直游离在专业前端开发之外。现在,打算好好学习一下前端技术。

##原则

  1. 选择活跃度最大/最贴近工作需求的技术入手;
  2. 某阶段内只专注学习一门技术,其他相关技术按需了解;
  3. 快速开发,不断迭代项目;

##React学习
学习王沛老师的React专栏文章,遇到不明白的自己搜索,很多知识可以从阮一峰的网络日志上找到。

###一 React的设计哲学 - 简单之

  1. React是面向MVC中的View的框架;
  2. 基于Virtual DOMDOM Diff技术,可以实现浏览器端的“全页面刷新”;
  3. 简化的组件模型:所谓组件,其实就是状态机器;

###二 React开发神器Webpack

  1. 对项目中的静态资源进行统一管理,为产品的最终发布提供最优的打包部署方案;
  2. 同时支持CommonJS和AMD模块(对于新项目,推荐直接使用CommonJS);
  3. source map:就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置;
  4. 模块加载器(Loaders)在一定程度上可以代替Css Sprites(用一张图片代替多张图片来节省开销和减少http请求)
  5. react-hot-loader:轻松实现React组件的热替换;
  6. 打包成多个资源文件两个目的:
    • 将多个页面的公用模块独立打包,从而可以利用浏览器缓存机制来提高页面加载效率;
    • 减少页面初次加载时间,只有当某功能被用到时,才去动态的加载;
  7. 栗子项目

###三 理解JSX和组件

  1. React模拟事件系统:React并不会真正的绑定事件到每一个具体的元素上,而是采用事件代理的模式:在根节点document上为每种事件添加唯一的Listener,然后通过事件的target找到真实的触发元素;
  2. 在JSX中使用样式:通过style属性来定义,但和真实DOM不同的是,属性值不能是字符串而必须为对象;
  3. 基本上属性名的转换规范就是将其写成驼峰写法;
  4. 对于自定义组件,唯一必须实现的方法就是render(),除此之外,还有一些方法会在组件生命周期中被调用,如下图所示:
    调用函数
    • componentDidMount: 在组件第一次render之后调用,这时组件对应的DOM节点已被加入到浏览器。在这个方法里可以去实现一些初始化逻辑;
    • componentWillUnmount: 在DOM节点移除之后被调用,这里可以做一些相关的清理工作;
    • shouldComponentUpdate: 这是一个和性能非常相关的方法,在每一次render方法之前被调用。它提供了一个机会让你决定是否要对组件进行实际的render;

###四 虚拟DOM Diff算法解析

  1. 对于列表节点提供唯一的key属性可以帮助React定位到正确的节点进行比较,从而大幅减少DOM操作次数,提高了性能;

###五 使用Flux搭建React应用程序架构

  1. 单向数据流
    单向数据流
  2. Object.assign() 复制对象所有可枚举的属性到目标。

由于需要将UIImage转化成OpenGL中的纹理,先得获取其中的图像数据。首先在网上看到一段代码,一开始还挺好用,利用CFDataGetBytePtr来获取:

1
CGDataProviderRef inProvider = CGImageGetDataProvider(img);
CFDataRef inBitmapData = CGDataProviderCopyData(inProvider);
   
size_t width = CGImageGetWidth(img);
size_t height = CGImageGetHeight(img);

/// 
CZImage *retImg = new CZImage(width,height,RGBA_BYTE,CFDataGetBytePtr(inBitmapData));

后来考虑到图片需要适应设备的屏幕,利用UIImage-Resize将图片先缩放到合适大小,然后再利用以上代码时,图片显示就出问题了。

IOS Developer Library说CGImage里面的数据不太靠谱,最好将图片重绘一遍才行。于是我将代码改为如下:

1
size_t width = CGImageGetWidth(img);
size_t height = CGImageGetHeight(img);

size_t rowByteSize = width *  4;
unsigned char *data = new unsigned char[height * rowByteSize];

CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(data, width, height, 8, rowByteSize,
                                             colorSpaceRef,
                                             kCGImageAlphaPremultipliedLast);
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextDrawImage(context, CGRectMake(0.0, 0.0, width, height), img);
CGContextRelease(context);

CGColorSpaceRelease(colorSpaceRef);
CZImage *retImg = new CZImage((int)width,(int)height,RGBA_BYTE,data);
delete [] data;

这样获取的数据是没有问题了。对比缩放UIImage后两种方法获得的数据,发现第一种方法得到的数据在每一行多了若干个空数据,rgba全为0。这样就造成了图片显示不正确。

题目说的貌似有点拗口。平时将IB/Storyboard中的控件与代码相连,都可以通过按住 control键拖动控件到代码的连线即可。可当控件所在的自定义视图是当前ViewController.View的子view时,是没法通过这个方法连接的。

场景

在IB/Storyboard中,添加ViewController.View的子view(图2中的紫色视图),并将其设置为自定义的UIView子类customView:
自定义视图的设置
按照常规方法无法连接:
控件无法和代码连接上

解决方法

  1. 在自定义子View的代码中手动添加控件代码:

    1
    @IBOutlet weak var testLabel: UILabel!
  2. 从代码前出现圆点拖动到IB/Storyboard上的label控件上:
    从代码前圆点拖动到控件

参考引用:Can’t Wire to Subview in IB

刚开始学习Swift,下载了他人写的项目,用xcode打开后都会提示错误。将代码中的一些as修改为as!就没问题了。在网上找了下原因,发现了这篇文章—— The as! Operator,翻译如下:

as! 操作符

在Swift 1.2以前,操作符as根据表达式的类型可以用来执行两种不同的转换:

  • Guaranteed conversion: Swift编译器保证一种类型的值转换为另外一种类型。例如,向上转换(从一种类型转换为其父类型)或者指定表达式的类型1 as Float).
  • Forced conversion: Swift编译器不保证这种强制将一种类型转换为其他类型的安全性,其可能导致运行时问题。例如,向下转换(从一种类型转换为其子类型).

Swift 1.2 从概念上将Guaranteed conversionForced conversion分成两种不同的操作符。 Guaranteed conversion仍然用as操作符,而Forced conversion则用as!操作符执行。感叹号!表明这种转换可能会失败,这样你看一眼就知道哪种代码会导致程序奔溃。

下面用例子展示其区别:

1
class Animal {}
class Dog: Animal {}
    
let a: Animal = Dog()
a as Dog		// now raises the error:  "'Animal is not convertible to 'Dog';
				// ... did you mean to use 'as!' to force downcast?"

a as! Dog		// forced downcast is allowed

let d = Dog()
d as Animal		// upcast succeeds

注意将表达式后缀符!?与转换符as!as?做类比:

1
class Animal {}

class Cat: Animal {}

class Dog: Animal {
	var name = "Spot"
}

let dog: Dog? = nil
dog?.name		// evaluates to nil
dog!.name		// triggers a runtime error

let animal: Animal = Cat()
animal as? Dog	// evaluates to nil
animal as! Dog	// triggers a runtime error

记住Swift中这些操作符模式的最简单方法是:!表示“这可能有问题”,而?表示“这可能为空”。