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.

2016年12月31日后,NSAllowsArbitraryLoads将被禁用,建议所有iOS App的Web API都升级为HTTPS

苹果的iOS 10正式版本马上就要发布了,而自iOS 10开始,苹果对于网络安全将更加重视,2016年12月31日以后,你将不能用NSAllowsArbitraryLoads来禁止App Transport Security了。

简单说,苹果坚持,你的所有的Web API请求都必须使用https,而如果你需要使用WebView加载外部的网页,考虑到你无法控制外部的网站,苹果引入了新的NSAllowsArbitraryLoadsInWebContent,允许WebView可以加载任意的网页。

The new NSAllowsArbitraryLoadsInWebContent key for your Info.plist file gives you a convenient way to allow arbitrary web page loads to work while retaining ATS protections for the rest of your app.

如果你需要在App中加载任意的网页,那么,你可以在Info.plist中添加NSAllowsArbitraryLoadsInWebContent,这样,你可以加载非安全的http页面,同时保证你的app的其他方面仍然受到App Transport Security的保护。

对于许多公司来说,API团队近期有一项非常重要的工作,也就是,如果你们的Web API仍然是通过明文的http进行调用的,那么,你们需要尽快将API的endpoint升级为https。

如果大家真的来不及升级到https,那怎么办呢。我突然想到,苹果只是禁止了NSAllowsArbitraryLoads,但是我们还是可以NSExceptionDomains,将API对应的域名添加到例外列表中的。

The following listing represents the overall structure of the NSAppTransportSecurity dictionary, showing all possible keys, all of which are optional. Keep this structure in mind as you configure each element of the dictionary, as needed, for your app:
以下代码是NSAppTransportSecurity字典的结构模型。该模型列举了所有可能的键值对,这些键值对都是非必须的。您可以根据这个结构中所示的键值对的信息来编辑Info.plist中的NSAppTransportSecurity字典。

NSAppTransportSecurity : Dictionary {
    NSAllowsArbitraryLoads : Boolean
    NSAllowsArbitraryLoadsInMedia : Boolean
    NSAllowsArbitraryLoadsInWebContent : Boolean
    NSAllowsLocalNetworking : Boolean
    NSExceptionDomains : Dictionary {
         : Dictionary {
            NSIncludesSubdomains : Boolean
            NSExceptionAllowsInsecureHTTPLoads : Boolean
            NSExceptionMinimumTLSVersion : String
            NSExceptionRequiresForwardSecrecy : Boolean   // Default value is YES
            NSRequiresCertificateTransparency : Boolean
        }
    }
}

解决一个WKWebView在iOS 10上的Crash: Invalid parameter not satisfying: targetNode

近日,我遇到了一个让我感到匪夷所思的问题。App使用了WKWebView展示内容,然而,一旦iOS 10的用户手指触摸了WKWebView,app就会立即崩溃,而iOS 9的用户就不会遇到这个问题。

经过调试,我发现Stack Trace和Exception如下


Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: targetNode'
*** First throw call stack:
(0x1864041c0 0x184e3c55c 0x186404094 0x186e8d82c 0x18cc98e4c 0x18c6f551c 0x18c78f364 0x18c20ec70 0x18c791b1c 0x18c791a78 0x18c790d34 0x18c20d34c 0x18c1ddf84 0x18c9b3008 0x18c9aca70 0x1863b2278 0x1863b1bc0 0x1863af7c0 0x1862de048 0x187d5f198 0x18c248bd0 0x18c243908 0x1000e58a0 0x1852c05b8)
libc++abi.dylib: terminating with uncaught exception of type NSException

而调试信息显示问题出现在UIGestureGraphEdge.m的第25行。

Assertion failure in -[UIGestureGraphEdge initWithLabel:sourceNode:targetNode:directed:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3599.6/Source/GestureGraph/UIGestureGraphEdge.m:25

这个文件是Apple的UIKit的一部分啊,难道这是Apple iOS 10的bug?

不管是不是iOS的系统的bug,我想还是找到一个解决方案或者说workaround的好。

最后发现,我原来在app启动的时候,在+(void)load方法中先创建好WKWebView,等到viewDidLoad的时候直接将WKWebView通过addSubView添加到View Controller的view上,这样就可以在viewDidLoad方法中节约了创建WKWebView的时间,可是这样子创建好的WKWebView,在iOS 10上,用户一旦进行了触摸操作,就会出现前面提到的崩溃。

如果我将创建WKWebView的时机稍微延后一些时间,如在load方法中,通过dispatch_after延迟1到2秒再创建WKWebView,那么,WKWebView就可以正常处理用户的触摸操作,不会崩溃。

static NSMutableArray *webViews;
#define WKWebView_Cache_Create_Delay 2
+ (void)load{
    if(SYS_VERSION_LESS(9))return;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSUInteger createDelay = 0;
        if(SYS_VERSION_GREATER_OR_EQUAL(10)){
            createDelay = WKWebView_Cache_Create_Delay;
        }
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(createDelay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            webViews = [[NSMutableArray alloc] init];
            [webViews addObject:[self createWKWebView]];
            [webViews addObject:[self createWKWebView]];
        });
        
    });

此外,在https://forums.developer.apple.com/thread/61432上,也有人遇到了只在iOS 10上才出现的Crash,而Crash的代码的位置也是UIGestureGraphEdge.m的第25行。


flykk
Aug 28, 2016 11:54 PM
When I clicked the NavigationBar in my App,it crashed.It only crashes in iOS 10 .The console log is:
Assertion failure in -[UIGestureGraphEdge initWithLabel:sourceNode:targetNode:directed:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3599.6/Source/GestureGraph/UIGestureGraphEdge.m:25

翻译如下
当我点击我的app的NavigationBar的时候,它就崩溃了。这仅仅发生在iOS 10上。控制台的日志是
Assertion failure in -[UIGestureGraphEdge initWithLabel:sourceNode:targetNode:directed:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3599.6/Source/GestureGraph/UIGestureGraphEdge.m:25

说实话,这个现象我没到过,但是,崩溃相关的文件和行数确是一样的。期待这位朋友找到解决方法。

Swift语言的兼容性问题

从Swift 3开始,可以认为Swift源代码级别兼容是没有问题的了,即Swift 4的编译器可以直接正确编译Swift 3的代码,而不需要像Swift 2到3或者Swift 1到2那样必须进行代码迁移。

原始信息在这:Swift Evolution,特别注意下面这段话

The primary goal of this release is to solidify and mature the Swift language and development experience. While source breaking changes to the language have been the norm for Swift 1 through 3, we would like the Swift 3.x (and Swift 4+) languages to be as source compatible with Swift 3.0 as reasonably possible. However, this will still be best-effort: if there is a really good reason to make a breaking change beyond Swift 3, we will consider it and find the least invasive way to roll out that change (e.g. by having a long deprecation cycle).

这段话的最重要的信息是,尽管Swift 1到3,经常出现源代码不兼容的情况,Swift社区还是决定从Swift 3.x开始,尽可能保证之版本的Swift与Swift 3是源代码级别兼容的。然而,如果Swift社区觉得非常有必要的话,也还是会做出一些语言上的重大变化,只不过这一次社区将尽可能的用温和的方式。所以,大家从Swift 3开始,不需要过于担心升级Xcode以后大量代码编译不通过的问题了。

然而,Swift 3到4的二进制兼容是没有保证的,所以,Swift 3编译出来的的framework,那是不可以用到Swift 4的工程中的。

详情见Rewrite the Swift 3 “goals” and drop the “nongoals” section, now that it的内容,大家可以看到

* **Stable ABI**

部分被删除了,也就是说Swift 3和以后版本的Swift的二进制兼容是大概率不可能的了,你用Swift 3编译的静态库和动态库,那是不能用到Swift 4工程上的。

如果你要对外发布framework,那么,暂时不要用Swift编写framework。

如果你的代码以开源的方式对外发布,并且他人的集成方式也是源代码集成的话,你大可以可以安心了,不需担心要像之前那样,swift1一个分支,swift2一个分支,swift3一个分支,并且三个分支互不兼容。Swift 4的工程也可以使用Swift 3的代码,正如你可以在C++源文件中写C代码,在Objective-C++的mm文件中写C++代码。

继续谈谈Found an unexpected Mach-O header code: 0x72613c21

之前一篇文章中,我提到,我使用Xcode 8 Beta 2打开之前的工程,并将Swift 2.2的代码转换为Swift 3之后,再度编译工程,Xcode就会报错误Found an unexpected Mach-O header code: 0x72613c21

正好,Apple Developer Forums也有一个Thread提到了0x72613c21错误。
该文指出问题出在Fabrics/Crashlytics上,如果你遇到了Found an unexpected Mach-O header code: 0x72613c21错误,那可能是你集成了Fabric/Crashlytics,并且版本不够新,因为许多人反应,移除Crashlytics后就好了,或者升级Crashlytics到3.7.2也可以。

不过,我当时的做法既不是移除Crashlytics,也不是升级Crashlytics,而是直接废除了Swfit代码,完全用Objective-C重新实现了原来Swift代码所实现的功能。

该Thread的最后一篇回复是这么说的:

diego.trevisan
Aug 1, 2016 2:02 PM
(in response to davidfromsparks)
This is finally fixed in Beta 4!

diego.trevisan
2016年8月1日,下午 2:02
(回复:davidfromsparks)
这个问题终于在Beta 4中解决了!

看到8月1日的回复,我可以郑重告诉大家,如果你遇到了0x72613c21错误,并且还在用Xcode 8 Beta 4之前的版本,那请首先尝试升级Xcode 8到Beta 4或更新版本。

diego.trevisan
Jul 31, 2016 3:43 PM
(in response to jmac)
I have just another dependency which is Alamofire and it is build from their new swift2.3 branch. Anyway, removing Fabric/Crashlytics solves the problem. Just tried adding them again, and the error appears :/
我的工程中有一个依赖Alamofire,而Alamofire是以swift2.3的分支编译的。无论如何,我将Fabric/Crashlytics从工程中移除了,Xcode 8就不会报错,而我一旦将Fabric/Crashlytics添加到工程中,那么错误就会重新出现。

jmac
Jul 31, 2016 4:33 PM
(in response to diego.trevisan)
This would be an interesting test:

– Take the Fabric.framework and Crashlytics.framework that you have from the installer, and try linking them by hand with a sample project with no other dependencies. Based on what you’re seeing, I would expect that it would not be able to build with Xcode 8 and that you would see the same Mach-O error.

如果你通过Fabric的安装包集成了Fabric.framework 和 Crashlytics.framework,并尝试在一个样例工程中仅仅链接这两个framework,且不包含其他的依赖. 那么,根据你所看见的,我预计Xcode 8无法成功编译该工程并且会报告Mach-O错误。

Then, try installing Fabric/Crashlytics in your project via Cocoapods. I was installing them via pods when they worked for me (and the older versions that I had were from the installer). I had assumed that the difference was just the old ones vs the new ones, but it’s possible that something about the way crashlytics links them ends up being different from what the installer does.

接下来,你可以试着用Cocoapods而不是Fabric的安装包集成Fabric/Crashlytics。我用Cocoapods集成Fabric/Crashlytics,这不会导致Mach-O错误(当然,我用Cocoapods安装的是新版本的Fabric/Crashlytics,而之前用安装包安装的Fabric/Crashlytics的版本旧一些)。我估计导致错误的原因仅仅是Fabric/Crashlytics的版本的新旧,新版本的没问题,旧版本的有问题,但是,或许也还有别的可能性,或许用安装包安装的Fabric/Crashlytics在Xcode链接静态库的时候与Cocoapods的方式有着细微的差别,而这个差别导致了Mach-O错误。