NetworkExtension5-App和Extension通信

环境

  • Xcode 11.6
  • iOS 13
  • MacOS 10.15

导航

1-总览

2-Client开发

3-Tunnel开发

4-Server开发

5-App和Extension通信

完整代码在此,熟悉的小伙伴可以直接试试。

共享数据方案

通过之前文章可以看到,发送和接收的数据都是在Tunnel进程的,而我们需要在主App进程展示发送和接收的数据,怎么拿到呢?

通过一番Google,推荐使用AppGroup,有下面两种方式:

  1. 通过NSUserDefaults

  2. 通过一个扩展与App都可以访问的共享容器,来存放文件,数据

其实就是一种。。

也可以使用类似MMWormhole等框架,使用起来很方便。

这里我们使用NSUserDefaults方式,也非常简单;但还需要一种垮进程的通知机制,最后选定Darwin Notification。

封装了一下,放到YYVPNLib中,功能如下:

#import 

typedef void(^YYDarwinNotificationManagerHandler)(void);

/// 如果是Darwin notification center, 无法传递参数
@interface YYDarwinNotificationManager : NSObject

+ (_Nonnull instancetype)sharedInstance;

- (void)registerNotificationForName:(NSString *_Nonnull)name callback:(YYDarwinNotificationManagerHandler _Nullable)callback;
- (void)postNotificationForName:(NSString *_Nonnull)name;

@end

这里有个坑,最开始想用这个通知直接搞定参数传递的,但是发现怎么也不行,最后看到Api有对应说明,对于object,userInfo,deliverImmediately这些参数:

If center is a Darwin notification center, this value is ignored.

实现代码

确定了方案,可以开始撸了,先添加一些扩展:

public extension UserDefaults {
    var readPackets: String? {
        get {
            string(forKey: #function)
        }
        set {
            set(newValue, forKey: #function)
        }
    }
    
    var receivePackets: String? {
        get {
            string(forKey: #function)
        }
        set {
            set(newValue, forKey: #function)
        }
    }
}

public extension YYVPNManager {
    static let didChangeStatusNotification = "YYVPNManager.didChangeStatusNotification"
    static let didReadPacketsNotification = "YYVPNManager.didReadPacketsNotification"
    static let didReceivePacketsNotification = "YYVPNManager.didReceivePacketsNotification"
}

扩展中的实现

PacketTunnelProvider中添加一个属性:

private lazy var dataStorage = UserDefaults(suiteName: YYVPNManager.groupID)!

然后在读取到流量和收到服务器响应的地方设置数据,发送通知:

func remotePacketsToLocal() {
        udpSession.setReadHandler({ [weak self] packets, _ in
            if let packets = packets {
                packets.forEach {
                    self?.dataStorage.receivePackets = ($0 as NSData).description
                    YYDarwinNotificationManager.sharedInstance().postNotification(forName: YYVPNManager.didReceivePacketsNotification)
                    self?.packetFlow.writePackets([$0], withProtocols: [AF_INET as NSNumber])
                }
            }
        }, maxDatagrams: .max)
    }
    
func localPacketsToServer() {
    os_log(.default, log: .default, "LocalPacketsToServer")
    packetFlow.readPackets { [weak self] packets, _ in
        os_log(.default, log: .default, "readPackets")
        packets.forEach {
            self?.dataStorage.readPackets = ($0 as NSData).description
            YYDarwinNotificationManager.sharedInstance().postNotification(forName: YYVPNManager.didReadPacketsNotification)
            self?.udpSession.writeDatagram($0) { error in
                if let error = error {
                    os_log(.default, log: .default, "udpSession.writeDatagram error: %{public}@", "\(error)")
                }
            }
        }

        self?.localPacketsToServer()
    }
}

主App中的实现

包含2个List,监听didReadPacketsNotification和didReceivePacketsNotification,添加数据并展示。

界面如下:

image

直接上代码:

import SwiftUI
import YYVPNLib

struct PacketView: View {
    @State var sendPackets: [YYListView.Model] = []
    @State var receviedPackets: [YYListView.Model] = []
    private let dataStorage = UserDefaults(suiteName: YYVPNManager.groupID)!

    var body: some View {
        VStack {
            Group {
                HStack {
                    Text("发送的包")
                    Button(action: cleanSendPackets) {
                        Text("clean")
                    }
                }.fixedSize()
                YYListView(items: $sendPackets)
            }
            Group {
                HStack {
                    Text("接收的包")
                    Button(action: cleanReceviedPackets) {
                        Text("clean")
                    }
                }.fixedSize()
                YYListView(items: $receviedPackets)
            }
        }.onAppear(perform: starListening)
    }

    private func cleanSendPackets() {
        sendPackets.removeAll()
    }

    private func cleanReceviedPackets() {
        receviedPackets.removeAll()
    }

    private func starListening() {
        YYDarwinNotificationManager.sharedInstance().registerNotification(forName: YYVPNManager.didReadPacketsNotification) {
            if let data = self.dataStorage.readPackets {
                DispatchQueue.main.async {
                    self.sendPackets.append(.init(text: data))
                }
            }
        }

        YYDarwinNotificationManager.sharedInstance().registerNotification(forName: YYVPNManager.didReceivePacketsNotification) {
            if let data = self.dataStorage.receivePackets {
                DispatchQueue.main.async {
                    self.receviedPackets.append(.init(text: data))
                }
            }
        }
    }
}

最近再添加一个入口,可以在ConfigView里面添加一个判断,如果VPN连上了,就展示跳转入口:

Form {
    ...
    if viewModel.status == .on {
    Section {
        NavigationLink(destination: PacketView()) {
            Text("Show packets View")
        }
    }
    }
    ...
}

搞定~ ~

你可能感兴趣的:(NetworkExtension5-App和Extension通信)