且听疯吟 如此生活三十年

这是一个初听很震撼,细想好像没什么毛病的收购。
不管是作为十多年暴雪全家桶玩家,还是单纯作为随着电子游戏成长起来的一代,或者说即将成为元宇宙的第一波韭菜,各种意义上都算是见证历史了。

感觉几个有意思的点:

🤑 687 亿美元,现金

怎么说呢,最近各种新闻也是频繁刷新我对金钱数量的认识了
恒大:和我的债务比起来就是个弟弟

💰 只用了约一半现金储备

什么叫财大气粗啊~

💸 687 亿美元大约相当于整个网易市值,约是索尼市值的一半

Phil Spencer 是真的厉害,毕竟能赚钱的人很多,但是能让老板一下子掏出来近 700 亿美刀的人……

🎮 收购完成后,游戏业务微软排到了第三,第一是腾讯,第二是索尼

玩家:你有什么拿得出手的大作吗?
企鹅:啊,没有啊。
玩家:你有自己的硬件平台吗?
企鹅:啊,没有啊。
玩家:你有自己的平台吗?
企鹅:啊,没有啊。
玩家:那你凭什么第一?
企鹅:sorry,会赚钱就是为所欲为。

📈 收购消息传出后,动视暴雪股票暴涨,索尼大跌,任天堂微涨,育碧 EA 双双上扬

只有索尼受伤的世界达成了~

🤷‍♂️ 今夜大量玩家跪在微软门口,他们手上举着“救救魔兽”“救救星际”“救救守望”“风暴要火”……最后只有一位举着“暗黑不朽上线”的玩家被保安拖走了,因为微软没有手机。

虽然微软没有手机,但是现在他们有糖果传奇了
想想 Windows 11 刚上线不久的 WSA,所以这也在你的计划之中吗阿软!

😉 这波啊,利好元宇宙

请问核聚变和元宇宙的共同点是什么:
永远离我们还有五十年~

最后,救救 Wow!

https://i.imgur.com/o1dmwYm.jpg

夜晚的潜水艇-陈春成

对我而言,这是一本难得的能带来纯粹的愉悦阅读体验的书了。

阅读这本书,灵魂好像被塞进一艘参杂着博尔赫斯的诗句、古老深山中炼丹炉的烟气、苏联红砖房里的单簧管、漂浮着纤毫毕现的隐秘心绪的潜水艇,在斑驳静谧的天空、大地和海底游弋。

放下书本,灵魂抽离而出,从防盗窗的空隙,钻回钢铁水泥的躯壳。
但至少,在某一个夜晚,有一艘蓝色的潜水艇,和一段奇异而愉悦的旅程。


在朋友圈里安利了这本书
一个朋友问我缘由,我说因为……好看,朋友玩笑表示毫无说服力。

在我看来读书是一件很个人的事情,你无法像吃药那样通过疗效来证明它的有效性。
何况先入为主的读后感,对旁人很难说有什么用处。
用俗套但有用的话来说,和书的相遇无非是缘 份一道桥
这只是一个契机,也许错过这次安利,终会有另一次偶遇让你看到它;
也许最后你也不会看或者不喜欢这本书。

椋鸟不一定会找到属于自己的灰烬之歌。
对社会动物来说,生活大概率并不会因此有什么影响。

那么,补上这篇无用的 算不上理由的理由 读后感,送给这个朋友吧😂

一点介绍

一个关于鼠疫爆发下,形形色色人们展现出的绝望、贪婪、疯狂,以及勇气和良知的故事。

关于作品

我看的这个版本,表述有点拖沓,缺乏一点文学上的趣味性。
不知道是译者的缘故还是原文如此。当然,不影响整个作品的光芒。

向来对外国人名的“脸盲”,对这些国外作品往往很难沉下心来品味。
但即使忽略具体行文,单纯去看整个故事,依然能感受到那种力透纸背的沉重。

习惯了文学作品中的“装腔作势”,才更能察觉到荒诞中的真实、中性和理智。

一点感想

困境下的贪婪与疯狂,勇气与良知,永远存在,重复上演。

不管困境来源于鼠疫、法西斯还是病毒。
不管在世界上的哪一个角落。

最近沉迷于 vscode 和 powershell 不能自拔,真的是太好用了~
顺便撸了一个小功能,用来直接在 powershell 中用浏览器打开对应 git repository 的地址

食用方法:

  • 在 powershell 中输入 code $PROFILE 来编辑 profile (或者你也可以使用其他的编辑器~

  • 将以下内容添加到 profile 文件结尾并保存

    
    function Open-GitWeb {
        $r = git remote -v | Select-String -Pattern "(https:\/\/|[email protected])(?<git>.*)\.git"
        if ($r.Matches.Length -gt 0) {
            $t = "https://" + ($r.Matches[0].Groups |
                Where-Object {$_.Name -eq "git"}).Value.Replace(":", "/")
            Write-Host "gh: openning ",$t,"..." -ForegroundColor "green"
            Start-Process $t
        }
        else
        {
            Write-Host "gh: not a git repository or origin not set correctly." -ForegroundColor "red"
        }
    }
    
    Set-Alias gh Open-GitWeb
    

    也可以从这个 gist 地址获取最新版本

  • 在 powershell 中输入 . $PROFILE 刷新配置文件(类似于 bash 的 source)

  • done! 在 git repository 目录下输入 gh 就可以打开对应的 url 了

问题

先看下面一个简单的 ASP.NET MVC 5 的 demo:

  • model

    public class TestModel
    {
        public List<int> Ints { get; set; }
    }
    
  • controller

    public ActionResult Index()
    {
        var testModel = new TestModel();
        return View(testModel);
    }
    
    [ActionName("Index"), HttpPost]
    public ActionResult Post(TestModel testModel)
    {
        return View(testModel);
    }
    
  • view

    @model Test.Controllers.TestModel
    
    <form action="@Url.Action("Index")" method="post">
    
        @for (var i = 0; i < 10; i++)
        {
            @Html.TextBoxFor(model => model.Ints[i])
        }
    
        <input type="submit" value="Submit" />
    </form>
    

有没有看出什么问题?

View 里面的

@for (var i = 0; i < 10; i++)
{
    Html.TextBoxFor(model => model.Ints[i])
}

Model.Ints 并没有初始化的情况下被使用了。

正常情况下可能会这么写:

@{
    if (Model.Ints == null)
    {
        Model.Ints = new List<int>();
    }
    for (var i = 0; i < Model.Ints.Count; i++)
    {
        @Html.TextBoxFor(model => model.Ints[i])
    }
}

如果我们需要 10 个 input,可能还得费心给 Model.Ints 初始化并添加 10 个 元素。

然而前面的写法真的会报错吗?

其实并不会,it works well.

为什么呢? @Html.TextBoxFor(model => model.Ints[i])Model.Ints 并未初始化的时候就使用了,那么应该会抛出异常才对?

原因

那么,我们来看看为什么没有报错。
这就要从源代码上找原因。

幸好,ASP.NET MVC 已经在 Github 上开源了,地址在这里

  • 我们很容易根据 namespace 找到 Html.TextBoxFor 的实现,参考 https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Mvc/Html/InputExtensions.cs#L425

  • 简略的说,根据方法签名追踪,可以找到 InputHelper 方法,即https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Mvc/Html/InputExtensions.cs#L483

  • 重点在这一段:

    string attemptedValue = (string)htmlHelper
        .GetModelStateValue(fullName, typeof(string));
    tagBuilder.MergeAttribute("value", attemptedValue ?? ((useViewData)
        ? htmlHelper.EvalString(fullName, format)
        : valueParameter), isExplicitValue);
    

    如果要报错,那么应该报错在 htmlHelper.GetModelStateValue,因为很明显这是获取 Model.Ints[i] 的值的地方

  • 继续找到 HtmlHelper.GetModelStateValue 方法,即 https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Mvc/HtmlHelper.cs#L391

    internal object GetModelStateValue(string key, Type destinationType)
    {
        ModelState modelState;
        if (ViewData.ModelState.TryGetValue(key, out modelState))
        {
            if (modelState.Value != null)
            {
                return modelState.Value.ConvertTo(destinationType, null /* culture */);
            }
        }
        return null;
    }
    

    重点就在于 ViewData.ModelState.TryGetValue 了,显然 ModelState 主结构是一个 Dictionary 来存储所有的值,这个想必大部分人都知道,所以我们绕了一圈最终找到了这里
    也就是说,实际上是通过 Dictionary.TryGetValue(key, out value) 这样的形式来获取对应的值
    具体到我们的问题,即 i == 0 时,在 ModelState 中寻找 key == "Ints[0]" 的值,当然,其值为 null 并且并不会报错

所以整个流程中并不会因为 Model.Ints 未初始化而报错,因为 Html.TextBoxFor(model => model.Ints[i]) 并不是通过直接访问而是从 expression 数据结构和 ModelState 数据绑定中取值。虽然这背后机制并不复杂,但是这个问题突然冒出来的时候,没有完整看过这部分实现,我也并没有想到这其中的关联。

最后,在使用之前初始化一定是一个好习惯!

附送

其实比起看源码,通过 Visual Studio 来 debug 可能更方便。

那么步骤如下:

  • 找到 Tool -> Options -> Debugging -> General
  • Uncheck Enable Just My Code
  • Check Enable Source Server Support
  • 转到 Tool -> Options -> Debugging -> Symbols
  • Check Microsoft Symbol Servers
  • Add http://referencesource.microsoft.com/symbols
  • Add http://msdl.microsoft.com/download/symbols
  • Add http://srv.symbolsource.org/pdb/Public
  • 我也不知道哪个 symbol server 对你有效,所以就都加上吧~
  • 如果你只需要一部分的 modules,可以选择 Only specified modules,比如添加 System.Web.Mvc.dll

接下来进入调试时,只要右键在当前断点上选择 Step Into Specific 就可以选择进入调试源码了~

最近从 farbox 迁移到了 bitcron,由于 bitcron 不再支持 html 的模板,只好用 jade 重写了一次
顺便整理了下之前写这个模板时碰到的一些细节,也许用得上吧

lang

国内很多网站都是不写这个 lang 属性的,比如 baidu。 而大部分国外网站都会写,Twitter 甚至为每一条推文都加上了 lang 属性
那么写上 lang 属性有什么意义呢?顾名思义,lang 属性声明了内容的语言。更详细的来说,比如:

  • 浏览器可以根据 lang=en 知道当前网页是英文,于是可以问你是否需要开启翻译功能

  • Chrome 在版本 21 之后,开始根据 lang 属性来应用不同的默认字体。这意味着你可以为英语页面和中文、日文等页面设置不同的默认字体

    这个选项没有出现在默认设置里,你可以使用这个 Chrome 扩展来设置:Advanced Font Settings

  • 页面的 lang 属性会影响字体的显示
    比如 思源黑体 中同时包含了中文字体和日文字体,我们知道日文中一部分汉字是相同的,为了节省空间,所以它们被放在了“同一个位置”上
    然而即使是同样的字,也可能在字形上存在不同,比如,如果你安装了思源黑体,那么下面同样的字
    门类门类

    <span lang="ja-jp" style="font-family:'Source Han Sans'">门类</span>
    <span lang="zh-Hans" style="font-family:'Source Han Sans'">门类</span>
    

    显示出来的字形是不一样的,如图:
    lang
    另外,由于中文字体 fallback 的关系,lang=en 下显示的是 lang=ja-jp 的字形,所以,如果发现网页显示的字形很奇怪,那么看看 lang 属性有没有正确的设置吧~
    可以参考这里来决定选择用哪个 Language Code

meta

  • charset
    千万不要忘记 charset 设置,否则可能会出现莫名其妙的乱码
    一般来说是这样设置的:

    <meta name="Content-Type" content="text/html;charset=utf-8" />
    
  • viewport
    这个就不多说了,如果做了自适应的话,应该都不会忘记这个

    可以参考 https://www.w3schools.com/css/css_rwd_viewport.asp

  • web app icon
    在移动设备上,除了将网站收藏为书签外,现在还有更多快捷访问的方式,比如,固定到桌面等等。所以,相对于 favicon,我们有了更多需要设置的 icon

    比如为 iOS 设置放到主屏幕上的图标:

    <link rel="apple-touch-icon" href="apple-touch-iphone.png" />
    <link rel="apple-touch-icon" sizes="72x72" href="apple-touch-ipad.png" />
    <link
        rel="apple-touch-icon"
        sizes="114x114"
        href="apple-touch-iphone4.png"
    />
    <link
        rel="apple-touch-icon"
        sizes="144x144"
        href="apple-touch-ipad-retina.png"
    />
    

    相应的还有 Android,Windows 等等,所以你可能会需要这个网站,一次生成支持所有平台的 html。

待续~

假设我们要实现一个炉石的对战机制,要怎么做呢?
并没有游戏开发经验,所以以下都是乱猜加瞎扯~

分类

首先,我们把炉石里的主要元素分成几类:

  • 法术
    法术很好理解,一般来说法术包括几个要素

    • 目标
      • 可能是一个目标也可能是多个目标
      • 可能包含一个筛选,比如「对非恶魔随从造成伤害」
    • 效果
      • 可能是给目标一个 buff 或者 debuff,也可能是造成伤害等等
      • 大部分法术都是立即效果
      • 也有一部分法术是触发效果,比如 奥秘,比如 本回合随从生命值不会降低到 1 点以下
  • 武器

  • 英雄

    • 英雄技能可以看作一个法术
  • 随从

    • 随从本身具有的属性

      • 花费/攻击/血量
      • 类别: 恶魔/鱼人/野兽
      • 其他
        我们以 https://hearthstonejson.com/ 上的卡牌火车王的 Json 数据为例:
      {
          "id": "EX1_116",
          "name": "Leeroy Jenkins",
          "text": "<b>Charge</b>. <b>Battlecry:</b> Summon two 1/1 Whelps for your opponent.",
          "rarity": "LEGENDARY",
          "type": "MINION",
          "cost": 5,
          "attack": 6,
          "health": 2,
          "collectible": true,
          "set": "EXPERT1",
          "faction": "ALLIANCE",
          "artist": "Gabe from Penny Arcade",
          "flavor": "At least he has Angry Chicken.",
          "mechanics": [
              "BATTLECRY",
              "CHARGE"
          ],
          "dust": [
              1600,
              3200,
              400,
              1600
          ]
      }
      
    • 随从效果

      • 即上面的 mechanics 数据
      • 目前炉石包括了 冲锋、亡语、发现、战吼、法术伤害、连击 等等大概二十多种效果

效果

  • 法术效果
    • 法术效果相对来说容易归纳
  • 随从效果
    • 实际上随从效果可以认为是法术效果,比如自带亡语的随从,可以认为是通过法术给予了亡语效果——毕竟,炉石里面确实存在着这样的法术
    • 具有亡语效果 随机召唤一个法力消耗为 3 的随从 的随从死亡时,可以认为是使用了一个 随机召唤一个法力消耗为 3 的随从 的法术
    • 光环是一种特殊的法术效果,拥有更加复杂的触发条件,比如 将你的治疗法术或技能改为造成等量的伤害

对局

  • 从对局中我们可以总结出一些规则,比如大家经常会讨论的 结算顺序
    • 虽然打出一张牌表面上看起来很简单,但是背后其实包括了多个阶段,比如:
      • 选择战吼目标并出牌,此时扣费
      • 登场效果触发,这个时候一部分其他卡牌的效果已经会被触发,比如 任务达人
      • 召唤效果触发,比如会触发 送葬者
      • 战吼效果触发,此时开始结算战吼效果
      • 奥秘效果触发,这也是为什么 谢娜 这张牌可以先偷到复制但是自己本身并不会被复制
      • 召唤结束,这时会触发 飞刀杂耍者 之类的效果
        (结算比较复杂,而且涉及到各种死亡结算和效果,而且暴雪也没有出官方的结算规则,所以,以上只是举个栗子~
  • 从上面的例子可以看到,不同的牌在不同的阶段触发效果
    假设我们需要实现一张 飞刀杂耍者,每召唤一个随从就发射一把飞刀
    • 我们可以很容易分开两个角色
      • 系统,它担任裁判的角色
      • 卡牌
        这样由系统去执行整个流程,在召唤了一个随从后,通知 飞刀杂耍者 去执行发射飞刀的任务
  • 显然我们没办法把所有卡牌逻辑都塞在 系统 这个角色中,假设我们有 500 张卡牌,那么 系统 这部分的调用代码可能会突破天际
  • 我们也不能让 飞刀杂耍者 自己去监控整场对局,从而决定什么时候发射飞刀,显然这样 系统 这个角色就失去了其意义,代码也会无比混乱
  • 实际上对于这样的问题,我们有一个经典的例子(或许你在面试中已经被问了无数次的 「猫叫老鼠跑人醒」)
    • 这就是观察者模式(事件-通知)
    • 我们只需要让 飞刀杂耍者系统 那里注册一个事件,告诉它,有随从被召唤的时候,通知我,然后由我来执行对应的逻辑
    • 系统 则需要维护一个注册的事件列表,比如随从被召唤的事件、随从死亡的事件等等
    • 系统 按结算规则来执行,并在对应的事件触发的时候,按一定的顺序去通知即可

其他

  • 炉石包含很多不同的效果,事实上这也正是它好玩的地方,但一来暴雪没有出明确的结算规则和效果解释,有些卡牌的效果也找不到同类和规律(比如改变战吼效果的 铜须 光环,和改变法术执行目标的 扰咒术 等等),所以有些时候想总结一套规则来套用所有卡牌不太容易
  • 对于单个的特殊的效果,hardcode 也是一个解决办法

插件

额外插一点关于炉石插件的内容,之前看到有童鞋在好奇

  • 炉石传说有一个隐藏的 debug-logging 模式,打开之后会产生非常详细的记录,通过解析这个 log 可以做到记录和获取对局信息
  • 网易的盒子貌似额外使用了抓包解析的方法,所以功能会强大一些,比如导入导出卡组的功能
  • 但是考虑到可能触犯 ToS ,其他插件没有这么做
  • log 大概是这样的:
    [Zone] ZoneChangeList.ProcessChanges() - id=1 local=False [name=Garrosh Hellscream id=4 zone=PLAY zonePos=0 cardId=HERO_01 player=1] zone from -> FRIENDLY PLAY (Hero)
    
    • 可以看出 log 是非常详细的,包含各种事件的触发
    • 解析完整的 log 可以帮助理解整个炉石的事件机制
  • 当初想要自己实现盒子的时候参考过这篇文章 https://www.reddit.com/r/hearthstone/comments/268fkk/simple_hearthstone_logging_see_your_complete_play
  • 当然现在很多开源的插件了,比如 https://github.com/HearthSim/Hearthstone-Deck-Tracker

待补充

想到哪写到哪,有什么想法再补充吧 =-=

从《凯恩之书》开始就期待了,终于出了编年史
不负责任的猜想是暴雪觉得魔兽世界这个剧情坑太大太多懒得圆吧
还好终于出了

中亚还是挺快的,原来预计发货时间排到 6 月,都想去美亚订了
幸好还是提前发货了

大部分是为信仰充值吧
不过这次编年史的内容还是挺有趣的
有很多剧情上的填坑和重新解读
当然也少不了暴雪粑粑的吃书

比预计的要薄一点,但还是挺有分量的
这只是 Volume Ⅰ,估计后续还会有几本(SHUT UP AND TAKE MY MONEY!

印刷质量还行,但也没有到惊艳的程度。
意外的发现 Printed in China =-=

英文看得还是有点费力……
慢慢啃吧,有什么好玩的内容会更新在这里(大概吧~



For the Horde!

编译、测试、打包、部署这一系列的操作实在是太麻烦而且容易出错漏,能自动化的东西我们就没必要手动去点。端着咖啡悠哉地等着叮的一声,安装包出现在面前,这才是我们想要的
花了点时间,把正在进行的 React Native 项目的自动部署完善了一下,实现了通过 Travis CI 自动编译测试,并打包成 ipa 发布到 FTP 的整个流程
现在只要 Push 到 Github 上,等到 Travis CI 运行完成,就直接可以拿到 ipa 包安装测试了

准备工作:
首先你需要一个 Github 账户
Travis CI 连接 Gtihub 后,会自动检查根目录下带有 .travis.yml 的项目
关于 Travis CI 的功能和文档,请参考 https://docs.travis-ci.com/

这里给出一个 React Native 项目的 yml 文件示例:

language: objective-c
osx_image: xcode7.1
xcode_project: ios/MyApp.xcodeproj
xcode_scheme: MyApp
env:
    matrix:
        - SPEC=spec1

before_install:
    - ./scripts/decrypt_key.sh
    - ./scripts/add_key.sh
    - brew update

install:
    - brew reinstall node flow watchman xctool
    - npm install -g react-native-cli

branches:
    only:
        - master

script:
    - ./scripts/release.sh

首先设置项目编译环境:

包括 language 和 要使用的编译镜像 osx_image,并指定项目文件和编译的 scheme

证书加密:

由于 iOS 打包过程需要一些证书密钥,这些是无法公开 Push 到 Github 的
虽然 Travis CI 没有提供 security file 的功能,但是提供了一个 security 的环境变量的功能
因此,我们可以通过在本地加密证书然后上传,线上编译时再解开来实现这一目的

比如:

  • Encrypt
# encrypt_key.sh
openssl aes-256-cbc -k ${ENCRYPT_PASS} -in scripts/MyAppdev.mobileprovision -out scripts/MyAppdev.mobileprovision.enc -a
openssl aes-256-cbc -k ${ENCRYPT_PASS} -in scripts/dist.cer -out scripts/dist.cer.enc -a
openssl aes-256-cbc -k ${ENCRYPT_PASS} -in scripts/dist.p12 -out scripts/dist.p12.enc -a
  • Decrypt
# decrypt_key.sh
openssl aes-256-cbc -k ${ENCRYPT_PASS} -in scripts/MyAppdev.mobileprovision.enc -out scripts/MyAppdev.mobileprovision -d -a
openssl aes-256-cbc -k ${ENCRYPT_PASS} -in scripts/dist.cer.enc -out scripts/dist.cer -d -a
openssl aes-256-cbc -k ${ENCRYPT_PASS} -in scripts/dist.p12.enc -out scripts/dist.p12 -d -a

注意: 请不要直接上传你的证书和密钥!也不要把密码上传到公开的地方!

导入证书到 Travis 编译环境:

参考下面的步骤:

# add_key.sh

# Create a custom keychain
security create-keychain -p travis ios-build.keychain

# Make the custom keychain default, so xcodebuild will use it for signing
security default-keychain -s ios-build.keychain

# Unlock the keychain
security unlock-keychain -p travis ios-build.keychain

# Set keychain timeout to 1 hour for long builds
# see http://www.egeek.me/2013/02/23/jenkins-and-xcode-user-interaction-is-not-allowed/
security set-keychain-settings -t 3600 -l ~/Library/Keychains/ios-build.keychain

# Add certificates to keychain and allow codesign to access them
security import ./scripts/apple.cer -k ~/Library/Keychains/ios-build.keychain -T /usr/bin/codesign
security import ./scripts/dist.cer -k ~/Library/Keychains/ios-build.keychain -T /usr/bin/codesign
security import ./scripts/dist.p12 -k ~/Library/Keychains/ios-build.keychain -P $KEY_PASS -T /usr/bin/codesign


# Put the provisioning profile in place
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp "./scripts/MyAppdev.mobileprovision" ~/Library/MobileDevice/Provisioning\ Profiles/

安装环境

安装编译所需要的环境,比如 xctool、node、npm、react-native 等等

编译和打包

举个栗子:

# release.sh

npm install

react-native bundle --dev false --platform ios \
    --bundle-output "/tmp/main.jsbundle" --entry-file index.ios.js

xctool -project ios/MyApp.xcodeproj -scheme MyApp build -sdk iphoneos \
    configuration Release OBJROOT=$PWD/build SYMROOT=$PWD/build

if [[ $? != 0 ]];then
     echo ">> build failed"
     exit 1;
 fi

PROVISIONING_PROFILE="$HOME/Library/MobileDevice/Provisioning Profiles/MyAppdev.mobileprovision"
OUTPUTDIR="$PWD/build/Release-iphoneos"
NAME=Esports_${TRAVIS_COMMIT:0:6}.ipa

xcrun -log -sdk iphoneos PackageApplication "$OUTPUTDIR/MyApp.app" \
    -o "$OUTPUTDIR/$NAME"

if [[ $? != 0 ]];then
     echo ">> package application failed"
     exit 1;
 fi

cd $OUTPUTDIR
curl --ftp-create-dirs -T "$NAME" -u $FTP_USER:$FTP_PASSWORD \
    ftp://$FTP_SERVER/esports/$NAME

echo ">> {$NAME} uploaded!"
echo ">> all done!"

基本上和本地编译运行 React Native 项目类似,不同之处在于使用了 xctool 命令行来编译

  • 首先安装依赖 package
  • 然后 react-native bundle (如果使用 online 的模式,可以忽略这一步)
  • 使用 xctool 编译和测试项目
  • 打包 App 为 ipa 文件
  • FTP 发布(Travis CI 支持多种发布方式,可以参考文档实现)
  • 完成,邮件或 IM 通知(如果你需要)

ref: https://www.objc.io/issues/6-build-tools/travis-ci/

最近了解了下 react 和 react native,感觉看到了一条成为「Full-stack Developer」的捷径啊(雾

React

  • 消息同步
    不管是 Web 还是 Native 应用,一个很麻烦的问题就是保持某个 Message 在不同 View 之间的状态同步。
    比如,收到一条消息,需要在未读标签显示状态和数字,然后未读列表中插入一条消息记录。
    阅读消息后,需要从未读中去掉这条记录,同时在已读中新增一条记录,还不能忘了将未读数字减去。
    我们是怎么处理这种情况?
    要在接收消息的 handler 中判断各个 View 当前的状态,然后根据对应的状态写不同的处理代码,阅读消息同理。当然高端点的可能自己搞个 Manager 之类的东西去对这些逻辑进行管理。
    但是,并没有什么用。一旦业务复杂,处理和 Debug 起来就很麻烦了。看上去未读是 1,点进去发现是空,这种场景想必见得也不少了。

    还记得很久之前我们怎么写页面的吗?(多久?大概到 ASP 吧=-=
    

    那时候我们没有这么多烦恼。因为我们是这么干的。不管什么操作,ok,刷新页面,可以保证所有状态都是正确的,so easy!
    React + Flux 所做的正和我们很久之前的做法有点像。数据更新了?重新渲染一遍 View 不就好了。
    但是将整个 DOM 重新渲染一遍是开销很大的,当然不可能真的这么做。
    所以就有了 Virtual DOM。
    其实我们对 View 所做的操作大部分都是修改内容,比如修改某个块里面的文字啦,对某个 list 增删改啦。这些都不需要完全重绘。
    React 做到了「智能」去更新 DOM,只改变需要改变的地方。(嗯,就是不相信你能写出高效正确的操作 DOM 的代码,所以我们都帮你写好啦~(雾

  • 组件化
    通常我们的页面上的控件都是带有不同的状态的,比如选框是否选中等等。而不同情况下状态可能会根据一些参数去变化,这让一般情况下的组件复用变得很麻烦。
    而 React 可以做到类似于函数的给定输入参数,输出固定的状态,可以很方便的实现 Web Components,也不会出现组件状态上的冲突。
    而组件化的开发带来的效率提升是非常高的。

  • 其他
    另外经常看到人说 HTML 嵌在 JS 里面感觉很奇怪是不是模板耦合在代码里面之类的,我倒是觉得这种写法挺简洁优雅的。如果把 JSX 里面的 XML 看作是一个 XML 表示的 Object,这样理解可能会好点吧。当然你也可以使用纯 JS 的方式去写。

好的东西往往是看上去简单实现复杂。
Java 本身虽然不怎么样,但是它可以让不同水平的人写出能够达到标准的代码,依然坚挺不是没有理由的。
所以我多少也有些认同 react 可以让不同水平的前端工程师写出符合效用的代码这个说法了。
算是一点并没有深入研究过前端的人的一点想法吧。

React Native

最近公司同时在做某个应用的 iOS 和 Android 版本,虽然没有参与开发,但是从两边的进程来看,大部分时间其实是把同一份逻辑翻译成 Objective-C 和 Java,但是这也是没办法的事情。
毕竟现有的 Hybrid App 方案表现都不太理想, Native 的优势暂时无法替代。如果可以借鉴 React 的优势和开发效率,同时带来 Native 的体验,excited!
所以 React Native 就顺理成章了。
难得的是 React 没有提「Write once, run anywhere」(JAVA:=-=)
而是 「Learn once, write anywhere」。

目前感觉除了基于 React 本身的那些优势外,React Native 在开发 App 方面也是很爽的

  • 效果
    WebView 总是让我们感觉没有 Native 那么顺畅自然,大概是因为触摸反馈之类的原因吧。React Native 在这方面则没有这个问题,可以非常容易的用到 Native 的触摸和动画效果
  • flex 布局
    熟悉 Web 那一套的话用起来还是很方便的。但是 React Native 的 flex 只是 Web 的 Lite 版,虽然尽力去做 Native 兼容,但是表现能力不如 Web 也不如 Native 是必然的,幸好移动端布局通常没有 Web 那么复杂。
  • 组件化
    React Native 提供了基本组件,通过自己组合,可以做出很多意想不到的效果。加上通过 npm 可以引入其他开源的组件,组合出一个 App 更加容易了。
  • 效率
    从安装 Xcode 到 写出一个新闻列表功能只花了两个小时,虽然之前完全没有接触过 iOS 开发。大公司项目这种提供详细文档和 starterkit 的作风真是太赞。
  • 调试方便
    即时刷新,用调试 Web 的方式调 App 真的很爽
  • 集成到现有 App
    可以将某个 React Native 实现的部分集成到现有 App 中,对已有的业务不产生影响

缺点也有:

  • 平台
    除了一些容易抽象的组件比如 ListView,View,Text 之外,目前很多组件是限于平台的。这点也是可以理解的,毕竟不同平台的 API 千差万别,很难做到通用。所以想要一次开发之后小小修改就可以通吃的可以醒醒了 =-=
    这也导致实际上还是无法绕开去,必须要了解 Native 开发的一些组件和细节。比如在 iOS 中,首先你需要放一个 Tabbar,然后每个 Tabbar Item 里面放一个 Navigator,这和 Native 开发其实是差不多的模式。单纯从做 Web 过来的人,还是需要去学习这些东西才能开始。
    但是比起完整使用 Native 开发,这个学习时间要短得多。
  • 表现能力
    虽然 React Native 的表现能力不如 Native ,但是它并不是为了取代 Native 开发存在的。在需要的领域做得足够好,这就够了。
  • 第三方库
    包括 React 和 React-Native 都有这个问题。事实上由于 Virtual DOM 和 React 革命性的架构方式,以往的第三方库很难做到直接可用了。而 React 的可用第三方库还不够多,当然社区还是挺活跃的,相信这个问题会好转。
  • 成熟度
    随着项目变大和引入的 package 变多,你会发现越来越多的莫名奇妙的出错和 bug,这个时候你需要耐心,放狗去搜。最容易找到解决方案的地方还是 Github 上的 issue list。有很多人会碰到同样的问题,也很可能会有人在下面给出解决的方法或是方向。当然,你也可以 subscribe 这个 issue,等待官方哪天大发慈悲解决掉它。
  • 更新速度
    更新太快有好处也有坏处。当你发现新版本解决了一个问题,迫不及待的想要更新的时候,可能发现:你的 App 挂了;第三方组件挂了;你必须升级一堆 package,并解决可能与之相关的问题。当然啦,大部分情况下还是没有那么蛋疼的。

虽然接触的时间不长,也有碰到一些小坑,但是目前看来,对我这种不会 Native 开发的人来说,React Native 是个非常不错的选择。
也许有时间会写写碰到过的坑和一些学习的建议之类的 :-)