且听疯吟 如此生活三十年

ChatGPT 不是质变,只是量变。

  • 它也许不是蒸汽机和互联网,但是它带来的改变,绝不比从拨号网络到千兆宽带小。单是这个量变,已经足以改变世界。

如同计算机和网络改变了所有行业,ChatGPT 也会带来同样的变化。

  • 想象一个真正智能的 Siri,设定好人设就能自主运行的游戏 NPC,提出需求就能快速生成代码的 IDE…而这些都只是皮毛。

ChatGPT 做为人类社会沉淀的知识和经验的桥梁,是更为重大的意义。

  • 语言是模糊而低效的,这导致了人与人之间沟通的巨大成本。
  • 传统上我们通过口授、阅读等方式来获取知识,过于低效,ChatGPT 可以帮助我们加速这个过程。
    举个例子,关于 Excel 文档处理的学习不再必要了,你只需向 ChatGPT 精确描述需求即可;
    而孩子将不再需要老师,只需要根据规划的路线和 ChatGPT 交流即可。
  • 同样,在一切面向人类沟通的地方,ChatGPT 都能提高沟通效率。

ChatGPT 也许会取代一批工作岗位,但同时也会创造许多新的职业。

  • 首先,它依然只是一个工具,需要人去操纵。
  • 再比如,某些领域的知识库积累可能并没有那么丰富,需要人去填充;在实际应用中,AI 的纠错和补充也是必不可少的。
  • 仅仅只是量变,恐怕不足以取代人的位置。

AIGC 也许才是元宇宙的答案。

  • 通过 AIGC 快速/个性化的搭建场景/模型/元素,元宇宙才具有可操作性。
  • Meta 也许走在了最终正确的方向上,但是路错了。

ChatGPT 的瓶颈也许会在算力上。

  • 但早期的汽车也跑不过马。一旦更多的资源涌向相关的芯片、软件、网络等行业,将会更快的推动世界前行,如同从马车到飞机高铁。

ChatGPT 对算力的需求也体现在训练成本上。

  • 也许这是一个只有超级公司才能玩得起的游戏了。
  • 这也意味着,以往程序员一台计算机一个网络就能创造奇迹的时代要过去了。
  • 从数据集到训练到使用,如果某个环节存在偏见,大概很难被发现。
  • 巨头的垄断也许会来的更快更全面了。

另一个瓶颈也许会体现在知识库上。

  • 英语作为世界上使用最多的语言,也是书写技术类知识最多的语言,在知识库上具有天然的优势。
  • 中文互联网目前有价值的知识和讨论一直呈下降趋势,也许会是一个拉开差距的地方。
  • 垂直领域的资料积累现在会发挥更大的价值,举个例子,拥有最全文档 MSDN 的微软,在相应领域带来的体验将会甩开其他人一大截。

科技很久没有革命性的发展了。

  • 区块链、元宇宙的狂热也许是人们对科技革命的迫切,而目前看来, ChatGPT 是更有希望的道路。

如同工业革命带来了环境污染,科技革命带来的除了发展,也往往带来坏的一面。

  • ChatGPT 会带来什么,我们才看到冰山一角。

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

感觉几个有意思的点:

🤑 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:\/\/|git@)(?<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/