在 iOS 扩展和应用程序之间使用 UIApplication.shared 共享 CocoaPod

Posted

技术标签:

【中文标题】在 iOS 扩展和应用程序之间使用 UIApplication.shared 共享 CocoaPod【英文标题】:Sharing CocoaPod using UIApplication.shared between iOS Extension and app 【发布时间】:2019-02-19 19:21:38 【问题描述】:

我有一个自己编写的定制私有 CocoaPod。我正在尝试在我的 ios 应用程序中使用它,它运行良好。但是当我将它添加到我的 iMessage 应用程序或共享扩展程序时,它会失败并在尝试使用 UIApplication.shared 时给我一个错误 'shared' is unavailable: Use view controller based solutions where appropriate instead.

我首先想到如何解决这个问题是添加一个 Swift Flag IN_EXTENSION 或类似的东西。然后将代码包装在 #if 块中。

问题是 CocoaPod 源的目标是在某种类型的框架中。源不是应用程序或扩展程序的直接部分。所以添加那个标志并没有真正的帮助。

以下是我的 Podfile 的示例。

source 'https://github.com/CocoaPods/Specs.git'
source 'git@github.com:CUSTOMORG/Private-CocoaPods-Spec.git'
platform :ios, '9.0'
use_frameworks!
inhibit_all_warnings!

target 'MyApp' do
  pod 'MyCustomSwiftPackage', '1.0.0'
end

target 'MyApp Share Extension' do
  pod 'MyCustomSwiftPackage', '1.0.0'
end

如果我在MyApp Share Extension 下注释掉pod 'MyCustomSwiftPackage', '1.0.0' 行,它可以正常工作。但如果我不加注释,它就会失败。

我的共享扩展中确实需要这个包。

我曾考虑编写一个单独的 pod 来处理 UIApplication.shared 逻辑并将该 pod 添加到 MyApp。但这似乎是一种真正的痛苦。特别是因为我不知道如何在一个依赖相同源文件的项目中部署 2 个 CocoaPods。

如果这是唯一的解决方案,那么使用 Git 子模块并将源代码直接放在应用程序中似乎更好,所以我可以直接将它作为这些目标的一部分,然后 #if 应该可以工作。问题是如果我使用 Git 子模块,将无法处理 CocoaPod 的依赖项。所以我真的必须以某种方式使用 CocoaPods。

我更喜欢一个简单的解决方案,感觉不像那些解决方案那么老套。那么有没有更好的方法来处理这个问题并修复该错误而无需重写大量代码,这不是一个超级hacky的解决方案吗?


在 cmets 中提到将 NSSelectorFromStringUIApplication.respondsUIApplication.perform 一起使用。问题在于,如果 Apple 更改 API,代码将会中断,即使对于以前版本的应用程序也是如此,因为它是在没有 API 未来证明的情况下动态调用的。尽管该解决方案听起来很简单,但它似乎是一个非常糟糕的决定。


answer below 看起来很有前途。遗憾的是,在 cmets 中概述了一些更改后,它仍然无法正常工作,主应用程序同时具有 Core 子规范和 AppExtension 子规范。

【问题讨论】:

这里可能的替代方案:***.com/a/52257828/603977,AFNetworking 也有类似的问题,可以使用 post_install 挂钩解决:***.com/questions/28987586/… @JoshCaswell 第二个链接看起来更有希望,但没有奏效。第一个链接似乎真的很hacky,但最终可能是一个不错的解决方案。使用第一种解决方案,您将失去编译器等的所有安全性。 你检查过这个吗? ***.com/questions/42114122/… @SagarChauhan 我不认为那是一回事。 它正在使用的那个框架,你不能fork它并添加IF_EXTENTION吗? 【参考方案1】:

假设您是 MyLibrary 的所有者:

Pod::Spec.new do |s|
  s.name             = "MyLibrary"
  # Omitting metadata stuff and deployment targets

  s.source_files = 'MyLibrary/*.m,h'
end

您使用了不可用的 API,因此代码会根据名为 MYLIBRARY_APP_EXTENSIONS 的预处理器宏有条件地编译某些部分。我们声明了一个名为 Core 的子规范,其中包含所有代码,但标记为关闭。如果用户未指定一个,我们将该子规范设为默认值。然后我们将声明一个额外的子规范,称为 AppExtension,包括所有代码,但设置预处理器宏:

Pod::Spec.new do |s|
  s.name             = "MyLibrary"
  # Omitting metadata stuff and deployment targets
  s.default_subspec = 'Core'

  s.subspec 'Core' do |core|
    core.source_files = 'MyLibrary/*.m,h'
  end

  s.subspec 'AppExtension' do |ext|
    ext.source_files = 'MyLibrary/*.m,h'
    # For app extensions, disabling code paths using unavailable API
    ext.pod_target_xcconfig =  'GCC_PREPROCESSOR_DEFINITIONS' => 'MYLIBRARY_APP_EXTENSIONS=1' 
  end
end

然后在您的应用程序 Podfile 中,您将在主应用程序目标中链接 Core,并在扩展程序中链接 AppExtension,如下所示:

abstract_target 'App' do
  # Shared pods between App and extension, compiled with same preprocessor macros
  pod 'AFNetworking'

  target 'MyApp' do
    pod 'MyLibrary/Core'
  end

  target 'MyExtension' do
    pod 'MyLibrary/AppExtension'
  end
end

就是这样!

【讨论】:

太棒了。然后我可以在我的代码库中使用#if MYLIBRARY_APP_EXTENSIONS 来检查它是否在扩展中? 是的,正是 (; 我还没有机会测试这个。如果我在奖励时间到期之前没有机会测试,并且在此期间没有更好的答案,我将奖励你。一旦我真正开始测试它,我将接受并投票。我认为它应该可以完美运行。 很高兴能帮上忙 (: 我在我的应用扩展中遇到了 No such module 'MyLibrary' 的奇怪编译错误。看起来它不会影响我的主要应用目标。【参考方案2】:

因为您只需要访问UIApllication.shared 才能获得topViewController。您不能将其作为用户需要提供的框架的依赖项。

让我们声明提供者并使用precondition 确保开发人员不会忘记设置此属性:

protocol TopViewControllerProvider: class 
    func topViewController() -> UIViewController


enum TopViewController 
    static private weak var _provider: TopViewControllerProvider?
    static var provider: TopViewControllerProvider 
        set 
            _provider = newValue
        
        get 
            precondition(_provider != nil, "Please setup TopViewController.provider")
            /// you can make provider optional, or handle it somehow
            return _provider!
        
    

然后在你的应用中你可以这样做:

class AppDelegate: UIApplicationDelegate 
    func applicationDidFinishLaunching(_ application: UIApplication) 
        ...
        TopViewController.provider = self
    

extension AppDelegate: TopViewControllerProvider  ... 

在扩展中,你总是可以返回自我。

任意视图获取topViewController的另一种方式:

extension UIView 
    func topViewController() -> UIViewController? 
        /// But I not sure that `window` is accessible in extension
        let root = window?.rootViewController
        ....
    

【讨论】:

我更喜欢主要基于 Pod 而不是客户端应用程序的解决方案。 rootViewController 和栈顶视图控制器也不一样。 @CharlieFish 你的topViewController 算法需要一些启动控制器。我展示了如何获得这个起点 有道理。但似乎应该有一种方法可以做到这一点,而不需要在客户端应用程序中这样做。它应该都在 Pod 中。 @CharlieFish 另一方面,您试图放入 Pod 代码,该代码将仅由一个特定客户端使用,而不会被其他客户端使用。有意义吗?我会说这是特定于项目的。所以你的电话 我不明白你的意思。另一个答案看起来很有希望。因为它基本上都在 Pod 内。但它不起作用。

以上是关于在 iOS 扩展和应用程序之间使用 UIApplication.shared 共享 CocoaPod的主要内容,如果未能解决你的问题,请参考以下文章

在 iOS 应用程序及其扩展程序之间共享 Storyboard

如何在 iOS 应用程序和今日视图扩展程序之间共享数据

如何在主 iOS 应用程序及其共享扩展程序之间使用相同的经过身份验证的用户令牌

NSUserDefaults 线程在 IOS 上的扩展之间共享数据是不是安全?

iOS 广播上传扩展、框架和应用程序之间的通信

IOS 扩展 (Extension)