泰国之旅

序曲

上月 22 号下午,我坐高铁来到上海,和朋友会面,正式开启了为期七天的泰国之旅。虹桥火车站还是额外熟悉,一年前我还经常来这里,逛了几家便利店,买了些东西当明天的早饭,在喜士多便利店我瞧见了一个大冰柜,一大片都是三得利乌龙茶,我的怨念得到了巨大的满足,顺手买了一瓶。

#1

在浦东机场附近宾馆住了一晚后,第二天 04:30 AM 就要坐班车到机场。整个人有些迷糊,而且第一次出境,有些好奇,虽然遇到了小小的插曲,不过总的还算顺利。

到了曼谷后,马不停蹄地到了机场出口附近办了租车手续,各个出口都是租车的公司,我们之前有预约过,也还算快速。租的车很干净,不过车是右舵的,道路是靠左行驶,驾车人还是要稍微熟悉一下。

national_car_rent.jpg
某款尼桑

national_car_rent_taken.jpg
行驶在阿育他亚,路上基本上都是日本车

从机场出发差不多 1h 就到了阿育他亚,一个离曼谷 80 多公里的古城。我们来这里主要是为了住一晚这里的民宿,然后看一眼古建筑。

ayutthaya_house
第一晚在阿育他亚的民宿,我们住第一层

ayutthaya_house_inside
室内

ayutthaya_house_bedroom
卧室

ayutthaya_house_inside_tripadvisor
trip advisor 的奖状

fish_chicken
午饭

我们点午饭的时候给了一个全是泰文,没有图片的菜单。我们就说了 fish 和 chicken,然后给我们上了这些。这个烤鱼是伏笔,后面在不同夜市都看到过类似的烤鱼。

ice_bag
不要钱的冰块

ayutthaya_dinner
第一天的晚饭

很棒的音乐,热情会讲很好中文的服务员,可惜最后结账的时候没找到他,要不然肯定给他小费了。泰国一般给 20 泰铢小费,给 100 的话是非常高了。

ayutthaya_outside
阿城

ayutthaya_outside2
阿城

我对这样的东西很容易审美疲劳,没多拍了。

#2

ayutthaya_breakfast
第二天提供的早饭

那个像烤土豆的是水果,不要被误导了,里面的肉像山竹,不过是透明的。这里比较可惜的是没给我们服务的阿伯小费,主要是我们没有零钱,当时开车走的时候就在想应该给 100 泰铢小费,还是没经验。BTW,黑咖啡,好评。

离开阿城后,我们就驱车回曼谷找网络上约好的驴友了。然后我们三人加一个内蒙古哥们和两厦门姑娘在一个商场逛了一下午。最后她们想去游船,我们四个男的就去夜市了。本来我们三想先去公寓,后来比较曲折,Airbnb 在地图中的位置有误,我最后在 Google 地图搜索地名才找到,不过最后还是先去夜市了,怕耽误大家时间。

way_to_the_bangkok_night_market
前往曼谷夜市的路上

bangkok_apartment
曼谷市区公寓

公寓大楼外面是突出来的建筑风格(照片没拍),很奇特。

bangkok_night_market_inside
曼谷夜市

bangkok_drink
曼谷的一条街

和她们会合后

bangkok_drink_people
喝酒

有一个朋友开车不能喝,还有一个姑娘不喝,还有一个姑娘喝了两杯,还有一个朋友不怎么喝。那基本上是我和内蒙古哥们喝了,不多说了……

bangkok_drink_photo
合照

#3

bangkok_to_phuket
飞去普吉岛

喝了那么多,第二天起来头居然不痛。

phuket_airport
机场出来

这里机场出来有人会拉你叫车,不过跟国内不一样,这里拉人的是正规的出租车公司,而且会比叫网约车便宜。

phuket_view_from_apartment
下榻的地方

住的地方阳台出来拍的。

phuket_beach
卡马拉沙滩

没什么太阳。然后我们付钱在躺椅躺了一会,居然有点感冒了。事实证明不要在没太阳的沙滩躺下去吹风。

phuket_cocoanut_tree
椰子树

#4

chiang_mai_apartment
清迈的公寓

我们那个卡马拉沙滩人少,也没什么活动。上午逛了逛普吉岛,下午搞了个 SPA,然后就去机场飞到清迈了。

#5

chiang_mai_dessert_street
网红街里的甜品店

在这里点了个吃不下的甜品。

chiang_mai_shotgun
清迈的射击场

这是霰弹枪,非常重,开枪声音巨响,两发子弹,一发未中。还试了 0.22 口径步枪和手枪,感觉很好,喜欢步枪。

chiang_mai_juice_girl
清迈的一家夜市

很可爱的小姐姐,做表情然后想了半天说出中文 ¥75。

chiang_mai_night_market_eating
吃晚饭

席地而坐,literally 地上有席子。点了烤鱼和像干锅做法的海鲜。某人点了芒果糯米饭。

#6

chiang_mai_small_elephant
小象

chiang_mai_elephant_river
骑大象

跋山涉水。喂食的时候,旁边的大象差点要把我手机抢了吃了,可怕。

chiang_mai_elephant_show
看小象表演

cable_way_overview
滑索道

坐了 2 个多小时的车,来到这里滑索道,我们有「王力宏」,「王宝强」,「能」教练…… 总共 42 段索道,刚开始滑的时候有点害怕,后来就麻木了,我只想赶快滑完回家。BTW 这里全是中国人。

cable_way_photo
和「能」教练合影

滑到一半休息。

cable_way_900m
900m 的索道

最后一段索道,如果第一段滑这个,有点慌的。

chiang_mai_last_dinner
最后的晚餐

和三位驴友会合吃晚饭。这时候我确认了两位厦门姑娘的口音已经非常像台湾口音了,或者应该反过来说?

chiang_mai_last_dinner_steak
分量很足的 steak

这里的菜很便宜,分量也足。不过我们点的果汁有点坑,冰水里放了几块西瓜那种。

chiang_mai_shopping_night
陪她们逛那个网红街

陪姑娘们逛街,买香水,香氛。那个芒果糯米饭味的香水真的很像……

#7

chiang_mai_breakfast
炸鱼

最后一天!我们六个人来这个市场买点果干,然后我们就告别了。

泰国我觉得西化程度挺高的,这里店面多有英文,西餐挺多,服务人员大多会讲英语,也有不少直接跟你讲中文的。总的来说是一个非常适合度假的国家,尤其这里物价也不高,也可以长待。这里可以感受到一股轻松愉快的氛围,不像国内大城市有那种不奋斗就要死的风气。对我来说调整一下生活节奏,体验下不同的生活方式,挺好。

如果有机会,我是非常推荐来泰国旅游的。

OWL 与电竞

owl_overview

OWL 在暴雪推动下已经如火如荼开展起来,12 个席位,每个席位接近 2000w 美金,各自代表一个城市,似乎这些都预示着电子竞技发展的元年已悄然开始。

曾几何时,我们坐在电视机前为 NBA 自己的主队喝彩,没想到电子竞技也会迎来这一天,而且是这么的快,这次我们中国观众还有真正的主队,上海龙之队。按照规划,每一个队伍在各自城市都会有自己的场馆,并有主客场机制,期待海外战队来上海客场作战那一天。

作为一个正规的联赛,选手的薪资也是有保障,选手会签 1+ 1 合同(第二年战队有决定权),一年底薪 5w 美元。除了 OWL 作为全球最高水平联赛,各个国家也有 OC (Overwatch Contenders) 这样的比赛,类似于 NBA 发展联盟,不过代表了区域最高水平赛事,根据官网介绍。

Introducing Overwatch Contenders - the development league for aspiring Overwatch League professionals.

在 OC 表现突出的,OWL 可以在支付一定费用后,跳过战队选人。不过我觉得这样对 OC 战队挺残酷,毕竟补贴不会太高。

除了这些专业性质的比赛,业余选手也可以参加 OWOD (Overwatch Open Division) 这样的比赛,为一些天梯高手提供一个曝光自己的机会,表现好的战队也可以去争夺 OC 名额。总之,暴雪提供了 OWOD-OC-OWL 这样的三级比赛制度,好的选手可以层层选拔上去,当然那些特别突出的,OWL 战队甚至会直接试训,不过随着这个制度的展开,这样的机会应该会越来越少。

这些巨量资本的投入,和完整的规划,都是资方对电子竞技未来的投资和布局。如果联赛能稳固发展,那么作为一个战队的首任老板,未来的回报是相当可观的,不仅是战队带来的粉丝,赞助,未来围绕场馆所带来的效益那是不可估量,可以说这目前还是一个没人涉及的领域,充满着一切可能。

从游戏本身来说,Overwatch 首先是个 FPS 游戏,它有不同种类的英雄可供选择,因为有些英雄可以在天上驰骋,所以存在立体作战空间。从纯粹玩耍的角度,第一视角带技能的飞天入地游戏也确实很有魅力。想想 FPS 版拳皇,你就能理解了。

但作为一个 6v6 的团队游戏,没有配合那就说不过去。每个英雄有小技能,也有大招,但没有任何装备,等级系统,要想配合只能靠这些小技能和运营大招,所以没有配合要想靠一个人 Carry 那就是痴人说梦,除非你跟对手水平差距极大。要想获胜,除了技能的运用,整个团队的站位和阵型也相当重要,没有好的阵型展开,那么团队配合就没有施展空间。

对于不同英雄的选择,也造就了不同的阵容安排,下面是几个经典阵容:

  • 放狗阵容

    带有两个机动性极高的 C 位,猎空,原始。加上卢西奥加速,禅雅塔挂上易伤。两个机动性最强的 T 位,温斯顿和 D.Va。以高伤害,高机动性来击垮对手,讲究一个快字,这个阵容比较万金油,上下限都比较高。

  • 天地狗阵容

    天上法拉造成伤害,地上猎空补残血,配上天使牵线法拉,两个机动性坦克和卢西奥加速。这个阵容就纯属消耗了,如果处理不了法拉,那么就会被全程压制。这个阵容适合特定地图,上限比较低。

  • 地推阵容

    大锤举盾,毛妹给护盾,安娜奶量高,带卢西奥加速弥补机动性不足,C 位安排就比较自由,常见死神,麦克雷,猎空,原始。这套阵容适合地形狭窄的地图。如果占领了点位,两个 T 位控制型大招运营起来,对方基本上就很难组织有效的一波进攻。但最强的地方也是它的弱点,如果阵容被分散开来,各个击破,那地推也很难还手。

不同种类的阵容有一定的克制关系,需要选手灵活应变地调整,这也对选手的英雄池提出了很高的要求。对于一个顶尖自由人,现在 OWL 要求他必须精通即时命中和延迟弹道,也就是76,麦克雷,黑百合,猎空,源氏,法老之鹰,狂鼠等。对于 FPS 游戏来说,即时命中和延迟弹道的手感是完全不一样的,选手往往会设置不同的 DPI,这对操作的要求非常高。

OWL 第一阶段也快结束,现在发展势头还不错,观众人数超出了当初设立的目标。等到第一赛季结束和第二赛季更多战队的加入,我们再来看看 OWL 会发展成什么样。传统的体育联盟不能忽视同等规模电竞联盟的兴起,尤其在这样基本没有体力限制的电竞比赛里,团队的策略和配合的高要求和顶尖选手的持续发挥,将会使比赛置入不断的高潮中。人类一直以来对竞技的热爱,也将以一种不同的维度得到完美诠释。

谈谈 Hiphop

最近在我国因某款节目火起来的 hiphop 歌手们似乎又得低调起来。再高的潮汐也有退落的一天,然而这次的原因却是政府层面。笔者从没经历过在如此高曝光下一种文化被强权强行哑火的事情,体验后也深感暴力机器的威力。不过最让我震惊的是民众对这个权力的赞扬,实在不清楚这么多成年人为什么思考事情就像草履虫,给他刺激他就有反应。对演艺人士创作自由的压制和封杀,本是一件非常可怕的事情,然而却有这么多人看到一个非常小利己的一面后,便开始支持起来。

我是高中开始听 hiphop 歌曲,当时从蕾哈娜 take a bow 听到 Eminem,一开始只是觉得 cool,然后慢慢接触到这个文化就一直很喜欢。听起来很俗,但 “real” 是贯穿整个 hiphop 文化的关键字。不管你是从 Edison 那些自传视频^1了解到他是如何被这个文化影响,还是从其他影视作品了解,你都可以管中窥豹,它的确有属于它的文化。

除了封禁外,一些网友还对 hiphop 歌曲的创作内容发表建议,他们鄙弃那些带黄赌毒的歌曲,说真善美的 hiphop 也很棒。我不否认,清教徒式的 hiphop 有它的市场,但像你这样看起来没听过几首歌曲,也不会去了解它的历史,随便就想出售自己的世界观给这个圈子的人,真的合适么,show me your respect.

The Irving

不图钱,甚至不想马上再拿一个总冠军。欧文就这样离开了骑士。

对于这种做法,我除了钦佩还能表达什么。

期待下赛季欧文穿着其他球队球衣出征。

iOS 手动创建 Framework

iOS 手动创建 Framework

MyFramework project

  1. new project -> Cocoa touch Framework

  2. add headers to MyFramework.h

// Test.h, Test.m
@interface Test : NSObject
- (NSString* )test;
@end
@implementation Test
- (NSString* )test {
return @"test success";
}
@end
// MyFramework.h
#import <MyFramework/Test.h>
  1. add headers to Public

    MyFramework target -> Build Phases -> Headers -> Public

  2. build target -> MyFramework output

Framework test Demo project

  1. copy MyFramework to project

    using the MyFramework.framework output from last step

  2. Link Framework

    target -> Build Phases -> Link Binary With Libraries -> add MyFramework.framework to list

  3. copy Framework to app “Framework” destination
    target -> Build Phases -> New Copy Files Phases

    • Destination -> “Framework”
    • add MyFramework.framework to list

test

#import <MyFramework/MyFramework.h>
- (void)viewDidLoad {
[super viewDidLoad];
Test* t = [Test new];
NSLog(@"%@", [t test]);
// Do any additional setup after loading the view, typically from a nib.
}

聚合

这是最后一步,上述步骤创建的 Framework 只支持模拟器的指令集,为了能够使 Framework 同时支持真机和模拟器,需要创建一个包含真机指令集和模拟器指令集的 Framework。

指令集表格参考:

arm64 = iPhone 6s/6SP, iPhone 6/6P, iPhone 5s, iPad Air, Retina iPad Mini
armv7s = iPhone 5, iPhone 5c, iPad 4
armv7 = iPhone 3GS, iPhone 4, iPhone 4S, iPod 3G/4G/5G, iPad, iPad 2, iPad 3, iPad Mini
i386 = 32 bit simulator
x86_64 = 64 bit simulator

创建支持模拟器和真机的 Framework:

lipo -create /.../Build/Products/Release-iphonesimulator/MyFramework.framework/MyFramework /.../Build/Products/Release-iphoneos/MyFramework.framework/MyFramework -output MyFramework

查看聚集后的 Framework 支持的指令集:

-> lipo -info MyFramework
-> Architectures in the fat file: MyFramework are: i386 x86_64 armv7 arm64

可以通过创建一个 aggregate 的 target 和脚本来简化这些步骤

Editor -> Add Target -> cross-platform -> Aggregate

新建一个 script,aggregate.sh,放在项目根目录

# Sets the target folders and the final framework product.
# 如果工程名称和Framework的Target名称不一样的话,要自定义FMKNAME
# 例如: FMK_NAME = "MyFramework"
FMK_NAME=${PROJECT_NAME}
# Install dir will be the final output to the framework.
# The following line create it in the root folder of the current project.
INSTALL_DIR=${SRCROOT}/Products/${FMK_NAME}.framework
# Working dir will be deleted after the framework creation.
WRK_DIR=build
DEVICE_DIR=${WRK_DIR}/Release-iphoneos/${FMK_NAME}.framework
SIMULATOR_DIR=${WRK_DIR}/Release-iphonesimulator/${FMK_NAME}.framework
# -configuration ${CONFIGURATION}
# Clean and Building both architectures.
xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphoneos clean build
xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphonesimulator clean build
# Cleaning the oldest.
if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi
mkdir -p "${INSTALL_DIR}"
cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"
# Uses the Lipo Tool to merge both binary files (i386 + armv6/armv7) into one Universal final product.
lipo -create "${DEVICE_DIR}/${FMK_NAME}" "${SIMULATOR_DIR}/${FMK_NAME}" -output "${INSTALL_DIR}/${FMK_NAME}"
rm -r "${WRK_DIR}"
open "${INSTALL_DIR}"

在 aggregate target 的 Build Phases 里的 Run Script 内容中

"${SRCROOT}/aggregate.sh"

最后选择 Generic iOS Devicecmd+B 就能看到聚集后的 Framework

refs:
MyFramework repo

MyFramework test demo repo

Framework Programming Guide

浅谈iOS中Library和Framework

iOS-制作Framework

@weakify, @strongify 的实现思路

@weakify, @strongify 的实现思路

这篇文章介绍一下 “libextobj” 中 @weakify, @srongify 的实现思路。

为了方便测试,写了个测试用例,整体测试思路借鉴于 “libextobjc” 的一个测试用例。

先在 @autoreleasepool{} 声明几个对象,然后用 captureBlock capture 住,那么在结束这个 autoreleasepool 之后,测试对象是否存在。

开发者一般都知道,为了使 captureBlock 不 capture 住变量,会使用 __weak 来修饰这个变量,然后再 captureBlock 内部在用 __strong 修饰这个变量,不过经常这样写就显得繁琐。

下面介绍一下 “libextobj” 是如何巧妙简化这些步骤的。

这是测试用例的代码:

- (void)testWeakStrong {
void (^verifyMemoryManagement)(void);
void (^captureBlock)(void);
@autoreleasepool {
NSString* cycleRefObj = [@"cycleRef" mutableCopy];
NSString* nCycle = [@"ncycle" mutableCopy];
NSString* lnc = [@"libextobj no cycle" mutableCopy];
__weak typeof(nCycle) wnc = nCycle;
@weakify(lnc)
captureBlock = ^{
NSLog(@"%@", cycleRefObj);
__strong typeof(nCycle) snc = wnc;
NSLog(@"%@", snc);
@strongify(lnc) // redeclare lnc
NSLog(@"strongify %@", lnc);
};
captureBlock();
__weak typeof(cycleRefObj) wc = cycleRefObj;
verifyMemoryManagement = ^{
XCTAssertNotNil(wc);
XCTAssertNil(wnc);
@strongify(lnc);
XCTAssertNil(lnc);
};
}
verifyMemoryManagement();
}

nCycle 对象是手写 weak-strong-dancing 的变量,可以看到,这样写非常繁琐。
lnc 对象是用 “libextobj” 来处理 weak-strong-dancing 问题。
cycleRefObj 对象则没有处理被 block capture 的问题。

@weakify 宏展开

// 省略了非关键宏展开
#define ext_weakify_(INDEX, CONTEXT, VAR) \
CONTEXT __typeof__(VAR) VAR ## _weak_ = (VAR);

也就是说如果输入 @weakify(a),那么展开后就是:

// 这里的 `CONTEXT` 是由上一个宏传进来的 `__weak`
__weak __typeof__(a) a_weak_ = (a);

造成的结果就是会声明一个 __weak 修饰的变量 a_weak_

@strongify 宏展开

// 省略了非关键宏展开
__strong __typeof__(VAR) VAR = VAR ## _weak_;

那么 @strongify(a) 的展开结果是:

__strong __typeof__(a) a = a_weak_;

可以看到,它会引用上一个 __weak 修饰后的变量 a_weak_,并且在 block 作用域中重新声明变量 a,这里就不用担心 a 又被 capture 进来的问题了,因为它是新的局部变量。

宏拯救世界,因为@weakify@strongify 支持多参数,所以可以一次性设置,@weakify(a, b, c)