2016/2017款MacBook Pro扩展坞(或者说dongle life)选购指南

严重警告,我要给诸位一个警告,千万不要看了这篇文章
2017开门红:MacBook Pro Thunderbolt 解锁 DELL 戴尔 TB15 扩展坞 开箱使用_开箱晒物_什么值得买 就去买一个DELL TB16来配自己的MacBook Pro。因为TB16完全不兼容MacBook Pro。买来TB16的话,你就会发现你如果没有戴尔电脑,你完全就是浪费钱。

同时,不要认为我没有试过上文提到的解锁操作和tb3enabler等工具,我告诉大家,我早就试过了,根本没有用,因为TB15在mac上被识别为不兼容的设备,你用tb3enabler就能解锁了,而TB16在mac上不是被识别为不兼容的设备,而是根本找不到任何设备,诸位懂我的意思了吗。我就是买了个TB16后才发现,根本不能用在我的2017 MacBook Pro上。万幸的是,这台TB16本来是为我的Dell Precision 5520买的,所以我还没有浪费钱。

我找遍淘宝京东等地,最后发现,如果要一个dongle解决充电,USB-A,读卡器和视频输出的话,只有下面两种选择。
HyperDrive雷电3Thunderbolt3扩展坞MacBookPro转换器Type-c转usb-淘宝网
和这一款
Belkin 雷雳 3 Express HD 基座

其实,雷雳3扩展坞或者dongle,满足usbc充电,转USB-A,包含SD/Micro SD读卡器的很多。支持HDMI 4k 60hz, 那就选择少很多了,或者说应该找不到。上面两款,都是通过mini displayport和tb3输出支持4k@60hz,其中第二款Belkin根本就不提供HDMI输出。

如果搭配LG Ultrafine 5k显示器的话,那么,购买Belkin那一款就可以了,但是,如果你还有别的5k显示器,如Dell UP2715K的话,那么,你可以参考下面我的做法。

我个人建议,最好买一个tb3 -> 3 USB-A + SD/MicroSD+ tb3 +usbc的转换器,如绿联的这款
绿联Type-C转接头hdmi网线vga数据线转换器苹果华为mate10手机多功能拓扩展坞 HDMI款+千兆网卡+HUB【图片 价格 品牌 报价】-京东
或者
【绿联40873】绿联Type-C转HDMI/VGA转换器 USB-C扩展坞PD充电转接头数据线 苹果MacBook华为Mate10Pro拓展坞集线器40873【行情 报价 价格 评测】-京东

绿联的这两款的优点在于额外提供了千兆以太网,缺点在于HDMI输出最高只能到4k@30hz。个人建议买绿联的第二款,因为第二款同时有HDMI和VGA,适合你开会的时候投影用,平时外接显示器的话,就用我下面提到的方法。

购买戴尔的tb3 -> displayport线,这条线肯定兼容MacBook Pro,因为我自己就买了两条,用来将15寸的MacBook Pro 2017与我的戴尔UP2715K显示器相连接,并且成功的输出了5k分辨率。
戴尔DELL Thunderbolt3 Type-c USB-C转DisplayPort DP接口适配器

这样最实用,因为既可以接一台UP2715K 5k分辨率,也可以接两台4k显示器,而且由于用了Displayport,一定支持60hz刷新。同时,也不影响额外再用别的tb3继续外接一台LG Ultrafine 5k显示器。

解决问题:iOS模拟器无法联网了

今天在Xcode上用模拟器进行调试的时候,突然发现一个奇怪的问题,如果我用了iOS 8版本的模拟器,无论是我的应用还是Simulator中的Safari,都无法连接互联网。Safari报告的错误是:“Safari cannot open the page because the network connection was lost”。我如果用iOS 11的模拟器,就没有这个问题。

我通过百度,Bing等搜索引擎进行了查找,找到了如下信息:
Can’t use HTTP in iOS 8 simulator https://stackoverflow.com/questions/25654679/cant-use-http-in-ios-8-simulator

ios模拟器连不上网 https://www.2cto.com/os/201612/576530.html

xcode 模拟器无法连接网络 http://www.cocoachina.com/bbs/read.php?tid-226644-page-e-fpage-96.html

结果,以上的方案没有一个适合我的情况。

最后,我想起来,我安装了Avast Security,并且启用了Web Shield,我关闭了Web Shield的话,iOS 8模拟器就可以联网了。
如果你也遇到了类似的问题,如果常规的方法不能解决的话,不妨检查一下你是不是也安装了类似的安全软件。

NSDateFormatter性能优化

近期在某个项目上,遇到了一个NSDateFormatter的性能问题。

不知何故,app中需要把将近一千个字符串转换为NSDate类型,运行后发现,转换居然要600ms以上,实在是不可忍受。最开始,我发现代码是每次进行转换都新创建一个NSDateFormatter对象,既然如此,我就进行了第一步优化,创建并缓存了全局的NSDateFormatter对象。可是,再次运行后发现,这个优化居然没有效果!!!怎么回事,不是苹果自己的文档说的“Cache Formatters for Efficiency”吗?

我用Instrument对应用进行了Profile,发现,创建一个NSDateFormatter并不消耗太长时间,反而是stringFromDate和dateFromString这两个方法耗时间,相比之下,创建NSDateFormatter的时间完全可以忽略不计。

最后,发现NSDateFormatter性能实在是太差,只能用sqlite了。

        sqlite3_stmt *statement = NULL;
        sqlite3_prepare_v2(db, "SELECT strftime('%s', ?);", -1, &statement, NULL);
        
        sqlite3_bind_text(statement, 1, [str UTF8String], -1, SQLITE_STATIC);
        sqlite3_step(statement);
        sqlite3_int64 interval = sqlite3_column_int64(statement, 0);
        NSDate *date = [NSDate dateWithTimeIntervalSince1970:interval];

strftime可以将yyyy-MM-dd HH:mm:ss这样的字符串转换为timestamp,然后再根据timestamp创建NSDate对象,这样比直接dateFromString要快很多。

同样,stringFromDate性能也很差,我最后直接用

calendar = [NSCalendar calendarWithIdentifier:NSGregorianCalendar];

获取到NSDateComponents,然后用

[NSString stringWithFormat:@"%4ld-%02ld-%02ld", components.year, components.month, components.day];

这样的代码获取字符串的日期和时间。

经过Instrument的Profiling,我发现calendarWithIdentifier:这个方法才是真的消耗时间,所以我缓存了NSCalendar对象。

结论,优化NSDateFormatter的结果就是别用NSDateFormatter。

一个小问题Phabricator的部分界面无法本地化

最近我进行了一些Phabricator本地化的工作,在这个过程中,我发现Phabricator的一小部分界面始终无法翻译,即使我在PhabricatorCNChineseTranslation.php中添加了相应的翻译项。

这部分无法翻译的界面是下图中的”Tag”这样的文字。
"Tag"无法被翻译

Phabricator是通过pht函数实现本地化的,pht(‘User’)将会返回User的本地化翻译,如果没有可用的翻译,那么就会返回’User’自身。

如果我们深入Phabricator的代码,我们不难发现,上图中的”Tag”之所以无法翻译,是因为这个”Tag”来源于PhabricatorProjectIconSet类的如下代码。

private static function getIconSpecifications() {
  return PhabricatorEnv::getEnvConfig('projects.icons');
}

上述代码其实是从Phabricator的配置项projects.icons中载入”Tag”, “Project”等配置。
这个配置项其实就是一串JSON
projects.icons

不难看出,无论如何,载入projects.icons的时候,Phabricator不会对projects.icons这个配置项的内容进行任何pht操作,这也就导致”Tag”等文本不会被翻译了。

我在自己的本地代码中,对PhabricatorProjectIconSet进行了如下改动,即在getIconSpec和getIconName函数上加上pht的调用。这样就可以实现本地化翻译了。但是,这样的改动是不符合Phabricator的pht规范的,即传入给pht的参数必须是scala string value,而由于下面的代码的缘故,我们并不能保证pht($value)的$value是一个scala value,理论上也可能是一个array(projects.icons是一个可以任意修改的配置项,所以我们并不能保证$value是scala),所以,我的代码是不能通过arc lint的。

 public static function getIconName($key) {
    $spec = self::getIconSpec($key);
    return pht(idx($spec, 'name', null));
  }

 private static function getIconSpec($key) {
    $icons = self::getIconSpecifications();
    foreach ($icons as $icon) {
      if (idx($icon, 'key') === $key) {
        $spec_local = array();
        foreach ($icon as $key => $value) {
          if ($key == 'name') {
            $spec_local['name'] = pht($value);
          } else {
            $spec_local[$key] = $value;
          }
        }
        return $spec_local;
      }
    }

    return array();
  }

简而言之,上述的代码只是一个补丁,仍然不是最好的方案,但是至少能够保证翻译了。

resource fork, Finder information, or similar detritus not allowed

今天,我准备更新我的一个app,这个app很久以前上架的,当时还是iPhone 32位 CPU的时候,所以有必要更新一次。

然而,在编译过程中,我遇到了以下错误:

resource fork, Finder information, or similar detritus not allowed

经过一番搜索,找到了解决方案。

http://stackoverflow.com/questions/39652867/code-sign-error-in-macos-sierra-xcode-8-resource-fork-finder-information-or

https://developer.apple.com/library/content/qa/qa1940/_index.html

原来,MacOS的文件有三个fork,data fork, resource fork和Finder info。Data fork存储了文件的内容;resource fork保存了一些扩展信息,如什么应用创建了这个文件,又例如上次你打开这个txt文件的时候,正在显示的是第几行,等等; Finder Info则保存了文件所有者,创建者等信息。

从iOS 10和macOS Sierra开始,从安全考虑,app bundle中的文件将不能包含resource for和Finder info了。所以,我们必须去掉这两个信息,才能成功进行代码签名。具体如何去掉这两块信息,大家可以参考下面的说明。

40
Code signing fails with error 'resource fork, Finder information, or similar detritus not allowed'

Q:  When I build my app, code signing fails with the error "resource fork, Finder information, or similar detritus not allowed." What does this mean and what should I do about it?

A: This is a security hardening change that was introduced with iOS 10, macOS Sierra, watchOS 3, and tvOS 10.

Code signing no longer allows any file in an app bundle to have an extended attribute containing a resource fork or Finder info.

To see which files are causing this error, run this command in Terminal:

$ xattr -lr 

replacing  with the path to your actual app bundle.

Here's an example of this command in action:

$ xattr -lr Foo.app
/Applications/Foo.app: com.apple.FinderInfo:
00000000  00 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00  |................|
You can also remove all extended attributes from your app bundle with the xattr command:

$ xattr -cr 

Note that browsing files within a bundle with Finder's Show Package Contents command can cause Finder info to be added to those files. Otherwise, audit your build process to see where the extended attributes are being added

更新到cocoapods 1.2.1了

今天准备更新到最新的cocoapods。我在mac上执行了遇到了一系列错误

$ sudo gem install cocoapods
ERROR: While executing gem ... (Gem::DependencyError)
Unable to resolve dependencies: cocoapods requires cocoapods-core (= 1.2.1), claide (< 2.0, >= 1.0.1), cocoapods-downloader (< 2.0, >= 1.1.3), cocoapods-trunk (< 2.0, >= 1.2.0), molinillo (~> 0.5.7), xcodeproj (< 2.0, >= 1.4.4), colored2 (~> 3.1), fourflusher (~> 2.0.1), ruby-macho (~> 1.1)

随后,我只能逐个执行gem install安装上述错误信息提供的依赖,结果,这样就安装成功了。似乎rubygems没有成功解决依赖,让我手动解决了????

$ sudo gem install cocoapods-core
Fetching: cocoapods-core-1.2.1.gem (100%)
Successfully installed cocoapods-core-1.2.1
Parsing documentation for cocoapods-core-1.2.1
Installing ri documentation for cocoapods-core-1.2.1
Done installing documentation for cocoapods-core after 1 seconds
1 gem installed


$ sudo gem install claide
Fetching: claide-1.0.1.gem (100%)
Successfully installed claide-1.0.1
Parsing documentation for claide-1.0.1
Installing ri documentation for claide-1.0.1
Done installing documentation for claide after 0 seconds
1 gem installed


$ sudo gem install cocoapods-downloader
Fetching: cocoapods-downloader-1.1.3.gem (100%)
Successfully installed cocoapods-downloader-1.1.3
Parsing documentation for cocoapods-downloader-1.1.3
Installing ri documentation for cocoapods-downloader-1.1.3
Done installing documentation for cocoapods-downloader after 0 seconds
1 gem installed

$ sudo gem install cocoapods-trunk
Fetching: cocoapods-trunk-1.2.0.gem (100%)
Successfully installed cocoapods-trunk-1.2.0
Parsing documentation for cocoapods-trunk-1.2.0
Installing ri documentation for cocoapods-trunk-1.2.0
Done installing documentation for cocoapods-trunk after 0 seconds
1 gem installed

$ sudo gem install molinillo
Fetching: molinillo-0.5.7.gem (100%)
Successfully installed molinillo-0.5.7
Parsing documentation for molinillo-0.5.7
Installing ri documentation for molinillo-0.5.7
Done installing documentation for molinillo after 0 seconds
1 gem installed

$ sudo gem install xcodeproj
ERROR: While executing gem ... (Gem::DependencyError)
Unable to resolve dependencies: xcodeproj requires colored2 (~> 3.1), nanaimo (~> 0.2.3)

$ sudo gem install colored2
Fetching: colored2-3.1.2.gem (100%)
Successfully installed colored2-3.1.2
Parsing documentation for colored2-3.1.2
Installing ri documentation for colored2-3.1.2
Done installing documentation for colored2 after 0 seconds
1 gem installed
mbp2:Caches jimmy$ sudo gem install nanaimo
Fetching: nanaimo-0.2.3.gem (100%)
Successfully installed nanaimo-0.2.3
Parsing documentation for nanaimo-0.2.3
Installing ri documentation for nanaimo-0.2.3
Done installing documentation for nanaimo after 0 seconds
1 gem installed

$ sudo gem install molinillo
Successfully installed molinillo-0.5.7
Parsing documentation for molinillo-0.5.7
Done installing documentation for molinillo after 0 seconds
1 gem installed

$ sudo gem install fourflusher
Fetching: fourflusher-2.0.1.gem (100%)
Successfully installed fourflusher-2.0.1
Parsing documentation for fourflusher-2.0.1
Installing ri documentation for fourflusher-2.0.1
Done installing documentation for fourflusher after 0 seconds
1 gem installed
mbp2:Caches jimmy$ sudo gem install ruby-macho
Fetching: ruby-macho-1.1.0.gem (100%)
Successfully installed ruby-macho-1.1.0
Parsing documentation for ruby-macho-1.1.0
Installing ri documentation for ruby-macho-1.1.0
Done installing documentation for ruby-macho after 0 seconds
1 gem installed

iOS/Android的WebView中用file input支持拍照或选择相册的照片

如果我们的一个移动端的网页需要让用户上传一张照片,那么,通常而言,我们可以写了以下一段HTML代码

<input type='file' />

那么Mobile Web界面将会显示下面的一个控件,通过该控件,用户可以拍照或者选择手机中的文件而上传。

在iOS下,Mobile Safari会在你点击上面的控件之后弹出如下界面
Screen Shot 2017-03-12 at 下午6.44.31

在Android系统下,弹出的界面根据具体的Android版本(不同的厂商定制版本、不同的Android系统版本)不同而略有不同,不过,通常也会提供相机,相册等选项。下图为小米手机的例子。
IMG_20170312_184934

然而,如果你在原生的应用中内嵌了一个UIWebView/WKWebView(iOS)或者WebView(Android),你就会发现,原来在Mobile Safari和Chrome中可以正常运行的代码很有可能不能正常工作了。

在iOS系统下,你会发现,如果UIWebView所在的View Controller是通过presentViewController展示的,那么,你会发现,用户点击 后,你的iOS应用会出现一些奇怪的问题,如应用崩溃,如你在相册选择了照片之后,UIWebView所在的View Controller不见了,等等。

最后,我的解决方法是,不通过presentViewController展示UIWebView所在的View Controller,而是通过UINavigationController的pushViewController去展示View Controller。

而对于Android系统,应用内嵌的WebView本来就不支持使用文件上传功能,所以,<input type=’file’ />在Mobile Chrome上有效,到了应用内嵌的WebView中就无效了。

在Android上,我的解决方法是这样的,通过WebView的addJavascriptInterface注入一个camera对象,让WebView中的js代码通过camera对象从而唤起相机或者相册。

webView.addJavascriptInterface(new DemoJavaScriptInterface(), “demo”);

Javascript代码

window.demo.camera();

假设DemoJavaScriptInterface的是位于WebViewActivity里面的一个内部类,且WebViewActivity有一个私有变量String mCameraPhotoPath,那么,DemoJavaScriptInterface代码如下

 final class DemoJavaScriptInterface {
        DemoJavaScriptInterface() {
        }

        @JavascriptInterface
        public void camera() {

                Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
                    // Create the File where the photo should go
                    File photoFile = null;
                    try {
                        photoFile = createImageFile();
                        takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath);
                    } catch (Exception ex) {
                        // Error occurred while creating the File
                        Log.e("WebViewSetting", "Unable to create Image File", ex);
                    }

                    // Continue only if the File was successfully created
                    if (photoFile != null) {
                        mCameraPhotoPath = photoFile.getAbsolutePath();
                        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                                Uri.fromFile(photoFile));
                    } else {
                        takePictureIntent = null;
                    }
                }

                Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
                contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
                contentSelectionIntent.setType("image/*");

                Intent[] intentArray;
                if (takePictureIntent != null) {
                    intentArray = new Intent[]{takePictureIntent};
                } else {
                    intentArray = new Intent[0];
                }

                chooserIntent = new Intent(Intent.ACTION_CHOOSER);
                chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
                chooserIntent.putExtra(Intent.EXTRA_TITLE, "拍照或者选择图片");
                chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);

                Activity mContext = WebViewActivity.this;
                if (Build.VERSION.SDK_INT >= 23) {
                    int checkCallPhonePermission = ContextCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA);
                    if (checkCallPhonePermission != PackageManager.PERMISSION_GRANTED) {
                        ActivityCompat.requestPermissions(mContext, new String[]{Manifest.permission.CAMERA}, REQUEST_CODE_ASK_CAMERA);
                        return;
                    }
                }

                WebViewActivity.this.startActivityForResult(chooserIntent, 101);

            }

    }

WebViewActivity中的onActivityResult方法代码如下

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == Activity.RESULT_OK) {
            switch (requestCode) {
                case 101:
                    if(data == null){
                        String imageurl = mCameraPhotoPath;
                        //相机拍好的照片就保存在路径imageurl中
                    }
                    else{
                        Uri uri = data.getData();
                        //通过uri获取照片数据
                    }

                    break;

                default:
                    return;
            }
        }

    }

openbakery gradle-xcodePlugin在keychainCreate阶段报错

我在iOS的项目中使用了gradle-xcodePlugin进行打包工作,然而,最近gradle-xcodePlugin一直报如下错误。


:archive
:keychainCreate FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':keychainCreate'.
> Command failed to run (exit code 1): 'security set-key-partition-list -S apple: -k -D -t private'

我将

security set-key-partition-list -S apple: -k -D -t private

在终端中执行的话,会遇到如下错误信息。

security: SecKeychainItemSetAccessWithPassword: The user name or passphrase you entered is not correct.

我在https://github.com/openbakery/gradle-xcodePlugin/issues/316 看到,已经有人报告了一个类似的issue。
原来gradle-xcodePlugin的作者为了解决issue 316而在0.14.5中添加了’security set-key-partition-list -S apple: -k -D -t private’命令。正是这个命令导致了我的打包失败。

最终,我改用了0.14.4版本的gradle-xcodePlugin,就可以正常打包了。

让Phabricator支持中文的全文搜索

Phabricator是一款优秀的开源项目管理、代码评审和代码管理平台,然而,默认情况下,它对于中文搜索的支持存在问题。

例如,如果你新建了一个标题为“公司年会准备工作”的Maniphest Task,那么,你在Phabricator中用“公司”或者“年会”进行搜索,是搜不到“公司年会准备工作”。这是因为Phabricator默认安装的时候,使用的是MySQL的全文索引,而MySQL默认的分词器是按照空白字符进行分词的,因此,“公司年会准备工作”是作为一个词语进行索引,而不是按照“公司”“年会”“准备”“工作”四个词语进行索引。

解决的办法有不少,例如,我们可以使用ElasticSearch为Phabricator的搜索引擎。不过,其实MySQL的全文索引是支持中文分词的,从MySQL 5.7.6开始,MySQL增加了NGRAM分词器,当你设置ngram_token_size=2时,“公司年会”会被分词为“公司” “司年” “年会”。

Phabricator的MySQL全文索引建立在phabricator_search库的search_documentfield表上,索引名称为corpus,对应的表的列名为corpus。 索引名称为key_corpus,对应的列为corpus和stemmedCorpus。



CREATE TABLE `search_documentfield` (
`phid` varbinary(64) NOT NULL,
`phidType` varchar(4) COLLATE {$COLLATE_TEXT} NOT NULL,
`field` varchar(4) COLLATE {$COLLATE_TEXT} NOT NULL,
`auxPHID` varbinary(64) DEFAULT NULL,
`corpus` longtext CHARACTER SET {$CHARSET_FULLTEXT} COLLATE {$COLLATE_FULLTEXT},
KEY `phid` (`phid`),
FULLTEXT KEY `corpus` (`corpus`)
) ENGINE=MyISAM DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};

我们看到,DDL语句中,创建全文索引的部分为“FULLTEXT KEY `corpus` (`corpus`)”,这使用的是默认的MySQL分词器,如果我们要使用NGRAM分词器,这个语句应该写成“FULLTEXT KEY `corpus` (`corpus`) WITH PARSER NGRAM”。

我已经有了一个已经安装好的Phabricator实例,我并不想重新安装Phabricator,所以,我的做法是删除掉corpus索引,然后重新建立以NGRAM作为分词器的corpus索引。

SQL语句如下(之前的版本)

USE phabricator_search;
DROP INDEX `corpus` ON `search_documentfield`;
CREATE FULLTEXT INDEX `corpus` ON `search_documentfield`(`corpus`) WITH PARSER NGRAM;

SQL语句如下(最新的版本)

USE phabricator_search;
DROP INDEX `key_corpus` ON `search_documentfield`;
CREATE FULLTEXT INDEX `key_corpus` ON `search_documentfield`(`corpus`,`stemmedCorpus`) WITH PARSER NGRAM;

我设置MySQL的ngram_token_size为2,因为中文中两个字的词非常多。

我的my.cnf中添加了以下配置项

[mysqld]
ngram_token_size=2

说说ITMS-90682: can’t contain 16-bit or P3 assets if the app supports iOS 8 or earlier

昨日,我在工作中听到我部门打包好的IPA,到了测试的时候,一旦测试的手机是iOS 8或者9.2的手机,应用总是会随机崩溃。最后,同事们找到了原因,具体原因见
http://stackoverflow.com/questions/39404285/xcode-8-build-crash-on-ios-9-2-and-below

When I build my app with Xcode 8 GM Seed and run it on an iOS 9.2 below device OR simulator, I get strange EXC_BAD_ACCESS crashes during app startup or a few seconds after the app launched. The crash always happens in a different spot (adding a subview, [UIImage imageNamed:], app delegate’s main method etc). I don’t get those crashes when I run it on iOS 9.3+ or 10 and I don’t get them when I build with Xcode 7 and run on iOS 9.2 and below. Has anyone else experiences something similar? Is this a known issue with Xcode 8?

当我用Xcode 8 GM Seed打包应用并在iOS 9.2或者更低版本的设备或模拟器运行的时候,我总是会在应用启动时或启动后的数秒之内遇到EXC_BAD_ACCESS,而崩溃的地方每次都不一样,如addSubView,[UIImage imageNamed:]甚至main方法。如果我在9.3或者10上运行,或者我用Xcode 7执行编译工作,然后运行在9.2以及以下的设备上,这些错误都不会发生。有没有人遇到类似的事情呢,这是不是Xcode 8的一个问题?

而解决的方法在这里讲了https://forums.developer.apple.com/thread/60919?start=0&tstart=0。简单说,Xcode认为你的图片资源中包含了16位或者P3的资源,所以打包的时候会进行相应的处理,但是,只有9.3或者更高版本才支持这样的资源,所以到了9.2就会崩溃。

此时,我想说说什么是P3资源。P3指的是DCI-P3或者说DCI/P3,一种数字电影投影所用的色域标准,它能比sRGB显示更多的颜色。苹果在推出5k显示屏的new iMac的时候(2015年10月),引入了DCI-P3,随后9.7寸的iPad Pro(2016年3月推出,推出时iOS 9.3系统)、iPhone 7(2016年9月推出,iOS 10系统)和2016的MacBook Pro都使用了DCI-P3色域。即苹果公司的iOS 9.3系统支持DCI-P3色域,而iPad Pro作为第一个支持DCI-P3的iPad,使用的是iOS 9.3。这也就解释了为什么我们之前打包的IPA到了9.2的设备上就会不正常。

那么,如何判断一个图片是不是P3的图片呢,其实很简单,就是要看这个图片的Color Profile是什么,通常来说,一个图片应该没有内嵌Color Profile或者内嵌sRGB的Color Profile。下面一个例子,我进行了两张图片的对照,左边为P3的png,右边为普通的png。P3和非P3图片对照

不难发现,左侧的图片有内嵌的Color Profile:Display,而右侧的常规的图片没有内嵌的Profile。那问题来了,为什么Color Profile: Display是P3资源呢?Display难道等于DCI-P3吗?

这里需要说明的是,Display指的是制作图片的人所使用的电脑的显示器所使用的Color Profile,具体这个Profile是什么,那要看制作者的电脑上的Display对应的Color Profile是什么,如果制作者使用的是5k的iMac,那么Display对应的Profile就是P3了。
下图为笔者的MacBook Pro的Photoshop新建一个图像的对话框。
display_and_display_p3
我们看到,默认的是Display,即用户当前显示器所使用的Color Profile,此外,还有许多Color Profile可以选择,如Display P3,这就是DCI-P3色域的Color Profile。如果你用的是5k的new iMac,那么Display就等于Display P3,如果你用的是别的Mac,比如笔者的2013的MacBook Pro,那么Display就等于sRGB。

下图为2016的MacBook Pro的技术规格,请大家注意“广色域(P3)”这几个字。这就是前面提到的P3 assets中P3的含义。
2016 MacBook Pro的显示屏采用了广色域P3

解决方法如下,摘录自https://forums.developer.apple.com/thread/60919?start=0&tstart=0,我就不翻译了。当然,最好是设计师在设计资源的时候,如果使用的是5k的iMac或者2016年新推出的MacBook Pro,那么,不要选择默认的Display选项,要选择sRGB,这样图片资源就可以用于支持iOS 8和iOS 9.2版本的工程了。

You can find 16-bit or P3 assets by running “assetutil” on the asset catalog named in the error message from iTunes Connect. The following steps outline the process:
1. Create an Inspectable .ipa file. In the Xcode Organizer (Xcode->Window->Organizer), select an archive to inspect, click “Export…”, and choose “Export for Enterprise or Ad-Hoc Deployment”. This will create a local copy of the .ipa file for your app.
2. Locate that .ipa file and change its the extension to .zip.
3. Expand the .zip file. This will produce a Payload folder containing your .app bundle.
4. Open a terminal and change the working directory to the top level of your .app bundle
cd path/to/Payload/your.app

5. Use the find tool to locate Assets.car files in your .app bundle as shown below:
find . -name ‘Assets.car’

6. Use the assetutil tool to find any 16-bit or P3 assets, in each Assets.car your application has as shown below. :
sudo xcrun –sdk iphoneos assetutil –info /path/to/a/Assets.car > /tmp/Assets.json

7. Examine the resulting /tmp/Assets.json and look for any contents containing “DisplayGamut”: “P3” and its associated “Name”. This will be the name of your imageset containing one or more 16-bit or P3 assets.

8. Replace those assets with 8-bit / sRGB assets, then rebuild your app.

Update: If your Deployment Target is set to either 8.3 or 8.4 and you have an asset catalog then you will receive this same error message, even if you do not actually have 16-bit or P3 assets. In this case you will either need to lower your Deployment Target to 8.2, or move it up to 9.x.