最近笔者使用一些iOS 8的应用程序扩展进行了开发,遇到了一些隐藏得很深的坑,这些技巧可能让读者在碰到相同状况时派上用场。下述的某些BUG只会在一段时间内出现,随着更新它们可能将不复存在,但是目前它们仍然系统中出现。
应用扩展工作的方法很简单:当您启动应用扩展的时候,Xcode会询问您想要关联的主应用程序。接下来应用程序启动,在测试设备上激活应用扩展,随后Xcode将关联应用扩展以便于开发者进行调试。
那么我们的问题来了,断点往往不能在真正意义上暂停应用扩展的运行,即使Xcode已经和应用扩展的进程相关联。这个BUG并不会影响所有人。这个情况可能会导致——由于缺少其他更好的调试方法,程序员只能选择古老的基于NSLog的调试方式。还有一个问题,Xcode并不能定位应用扩展的dSYM
文件(即使Xcode自己生成了该文件)。Xcode知道您想要打断点的地方,但是又并不认为它已经加载了任何匹配这些断点的代码。
在和这些个问题做了无数斗争之后,笔者终于摸索出一个结论:断点只能工作在Xcode的导出数据首选项设置为“default”的情况下。我在很长一段时间里都是在使用自定义的导出位置,并没有出现任何问题,但是在应用扩展中就出现了问题。但是当我换回默认设置之后,断点又可以正常运作了。
即使解决了上述的问题,调试“Today”应用扩展仍然会比较棘手。和其他主应用程序不同,Xcode似乎并不会中止“Today”应用扩展的运行。让Today应用扩展加载新版本貌似也遇到了问题。当您调整了您的代码之后却看到运行结果什么都没变,这一点确是会让人感到十分困惑。
下面是一些可能有用的技巧来解决上述问题:
虽然上述方法不一定完全可靠。因此,在调试Today应用扩展的时候,将断点或者NSLog及早置于视图控制器生命周期内是及其重要的。总之我需要使用initWithCoder:
。这个方法给出一个明显的迹象:新的代码将切实被加载。这就自然而然地消除代码没被更新的疑虑。
如果您正在开发分享(Share)或者Action应用扩展,现在最好不要选择手机状态栏的样式。您可能正在使用亮色系或者暗色系的全屏UI,但是应用扩展的状态栏样式并不是您自己所能决定的。无论主应用程序使用何种状态栏样式,应用扩展都会继承这种样式。总之,请求一个首选样式来设计您的应用扩展可能是一个好主意,当这个BUG修复后您就可以继续使用状态栏样式了。在此期间,您将不得不忍受这个现象。
避免在插件中放置滚动视图,因为用户在滚动插件中的内容时,会很容易误滚动整个Today视图。
接着文章又说了:
通常情况下,你不应该让插件过高,因为用户必须滚动才能查看全部内容。
这听起来是避免让你的UI毁掉的友善建议。实际上,这些准则会被框架自动实施:
在查看所有应用扩展类型的时候,也许您会为是使用“Action”应用扩展还是“Share”应用扩展而感到困惑。这两者的区别似乎并不是那么的明显。这是因为在实际操作中,这两者的区别其实是很小的。
在使用分享扩展的时候,Xcode为目标建立的模板使用的是SLComposeServiceViewController
。这个类提供了相当不错的基础UI,比如说苹果的Twitter或Facebook分享按钮。如果它恰好是您所需要的那么这个UI就会十分方便,但是这并不是必需的。分享扩展可以直接继承自UIViewController
,以便可以进行一个完全地自定义设计。相反,Action扩展使用的是SLComposeServiceViewController
。
我发现这两者的明显区别是:
如果上述特性对您来说并不重要,那么这两者的差别就不是很明显了。我创建了一个Action扩展,但是随后又觉得是不是可以用Share扩展来代替。从Action转变为Share扩展,我需要做的仅仅只是将应用扩展的Info.plist
中的分享点(share point)由com.apple.ui-services
变更为com.apple.share-services
。其他的东西我都没有进行改变,但是运行的却是不同的扩展类型了。
在iOS 8(以及其他最近的版本)中,在Xcode的编译设置(build settings)中的Modules栏可以让您不必明确列出所有你想要使用的框架。这些框架都会被自动找到。
但是,这并不适用于Today应用扩展使用的NotificationCenter.framework
。如果您在编译设置中移除了它,您不会得到任何编译警告或者错误。但是当您尝试运行这个应用扩展的时候,您会得到一个来自libextension.dylib
的异常警告,应用扩展并不能成功运行。这个异常消息并不能帮助解决问题:
2014-08-16 12:06:53.793 TodayTestExtension[41313:6111763]Terminating
app due to uncaught exception 'NSInvalidArgumentException', reason: '
setObjectForKey: object cannot be nil (key: <__NSConcreteUUID 0x7fd729422390> ED3B42F8-66CD-4CB0-BCD5-F3DBA6F34DB5)'
如果您正在使用Today扩展,请将那个框架留在编译设置中。它本不应该在那儿的,但是它还是出现了。
应用程序扩展程序指南-应用扩展如何工作说明了某些API不能够在应用扩展中使用。一些例子和说明列出了只要在顶部标有NS_EXTENSION_UNAVAILABLE
标识的框架将不能使用。
这就会导致您在应用程序和应用扩展之间分享代码时会感到无所适从。NS_EXTENSION_UNAVAILABLE
指令意味着您不能编写运行时检查代码,因为引用了被禁用的API将会导致编译错误。解决这个问题的一个方法是将共享类重构为层次结构,使用一个两者共用且常用的父类,并且在不同的构建对象中使用不同的子类。另一种方法是使用预处理命令#ifdef
来进行检查。Xcode并没有内置诸如#if TARGET_IOS_EXTENSION
之类的条件语句,因此您选择这个方法的话就必须自行创建。
同样的,[UIApplication sharedApplication]
也受到限制。这在逻辑上说得过去。但是这就导致在UIApplication
上的许多有用的API突然没法用了。比如说,没有preferredContentSizeCategory
说明您无法查看用户的首选字体大小。由于openURL:
方法不能使用,因此这个方法会被NSExtensionContext
方法覆盖掉(并不是所有扩展类型可以用),但是目前并不存在canOpenURL:
的替代方法。如果您需要打开一个URL,您只能祈祷它能够正常工作,别无他法。
如果您要在应用程序和应用扩展之间分享代码,用要分享的代码来创建自己的内含框架是一个极佳的方法。在iOS 8中这个框架就会被两者动态加载。
如果您仍然支持iOS 7(或者更早的版本),这个方法仍然不是一个好的方法。这个内含框架将不会自行运行,应用程序扩展程序指南-常见问题的处理方案轻描淡写地指出可以用dlopen
来解决这个问题。如果您证实您的代码能在支持这样做的iOS版本上运行,请使用这种方法在运行时编写代码来动态地加载框架,而不是依赖于iOS的加载。
但是如何在iOS 7中使用那个代码呢?很遗憾的是不能这样做。如果您用内含框架来分享代码,其实是没有办法让它在iOS 7上运行的。这个操作是被禁止掉的。
使用dlopen
方法来分享代码“可能”在iOS 8中很好用。但是如果要在iOS 7上使用它的话,您需要将它包含在应用程序对象中。一旦您这样做,您就不能使用自定义的框架了。您仍然可以在应用程序扩展中使用自定义的框架,但是这样做实际上并不是很有用。您花了大量的时间构建了这个框架但是却没有从中得到相应的好处。就把需要共享的代码包含在两个对象当中就行了。
笔者已经提交了关于这些问题的报告,所以如果幸运的话在不久的将来这些BUG将会被修复。应用扩展仍然是一个全新的玩意儿,所以有一些“成长的烦恼”还是很正常的事情。
联系客服