# Tencent Qian H5 容器 — iOS Objective-C Quick Start

> 面向纯 Objective-C 工程的集成版本,适用于 100% OC 代码库的存量客户(避免引入 Swift 运行时)。

## 工具链

- **Xcode 15+**(LTS:Xcode 15.4 / 16.x 均可)
- **iOS Deployment Target 14.3**(对齐人脸核身最低支持)
- **XcodeGen**(用于生成 `.xcodeproj`,本仓库**不提交** `.xcodeproj`)
- **纯 Objective-C / ARC**(不混编 Swift)

## 1. 安装 XcodeGen(仅一次)

```bash
brew install xcodegen
```

## 2. 生成 Xcode 工程

```bash
cd ios-objective-c
xcodegen generate
```

执行后会在当前目录生成 `Demo.xcodeproj`(`.xcodeproj` 已在 `.gitignore` 中)。

## 3. 打开 + 跑 Demo

```bash
open Demo.xcodeproj
```

在 Xcode 中:
1. 选 `Demo` scheme
2. 选目标设备(模拟器或真机)
3. ⌘ + R 运行

Demo 演示三种集成方式:容器 / 配置器 / 兼容性测试。

## 4. 命令行构建 / 测试

仓库根目录提供 OC 专用脚本:

```bash
bash scripts/build-ios-objectivec.sh           # 构建 SDK framework
bash scripts/build-ios-objectivec.sh demo      # 构建 Demo
bash scripts/build-ios-objectivec.sh all       # 全量
bash scripts/test-ios-objectivec.sh            # 跑单测
```

## 5. 接入(纯 OC 工程)

### 5.1 拉框架

由于 OC 版本不发布到 SPM(SPM 默认假设 Swift),按以下任一方式拉取:

- **Submodule + 手工 link**:把 `TencentQianWebView.framework` 产物拖入客户工程,General → Frameworks 标 *Embed & Sign*
- **CocoaPods**:发版时本目录下提供 `TencentQianWebView.podspec`(本期占位待发版)

### 5.2 调 API

```objc
#import <TencentQianWebView/TencentQianWebView.h>

TQWebViewConfig *config = [TQWebViewConfig defaultConfig];
config.topBarTitle = @"签署中";

TQSignCallbacks *callbacks = [TQSignCallbacks
    callbacksWithOnSignResult:^(TQSignResult *result) {
        NSLog(@"sign result action=%@ result=%@ flowId=%@",
              result.action, result.result, result.flowId);
    }
    onEvent:^(id<TQSdkEvent> event) {
        if ([event isKindOfClass:[TQPageStartedEvent class]]) {
            NSLog(@"page started %@", ((TQPageStartedEvent *)event).url);
        }
    }];

[TQWebView openFromPresenter:self
                         url:@"https://quick.qian.tencent.cn/sign?flowId=xxx"
                      config:config
                   callbacks:callbacks
                    animated:YES
                  completion:nil];
```

## 目录结构

```
ios-objective-c/
├── project.yml                      # XcodeGen 配置
├── TencentQianWebView/              # SDK Framework target 源码
│   ├── Sources/
│   │   ├── Public/                  # 对外公共 API(.h 暴露给客户)
│   │   ├── Configuration/           # WKWebViewConfiguration 工厂
│   │   ├── Internal/                # navigation/UI delegate / 国际化 / 日志
│   │   ├── Download/                # 下载策略 + 下载管理器
│   │   ├── Scheme/                  # qianapp / external scheme 拦截
│   │   └── Resources/               # 国际化字符串(Base / zh-Hans)
│   ├── TencentQianWebView.h         # Umbrella header
│   └── Info.plist
├── TencentQianWebViewTests/         # SDK 单测(XCTest)
├── Demo/                            # OC Demo App target 源码
└── DemoSwift/                       # Swift Demo App target(纯 Swift 消费 OC SDK,见下文「DemoSwift」节)
```

## 命名约定

类、协议、常量统一加 **`TQ`** 前缀(Tencent Qian),避免与客户代码的 `Qian*` / `WV*` 等命名冲突。

## DemoSwift（纯 Swift 消费 OC SDK 参考范本）

`ios-objective-c/` 工程除纯 OC `Demo` 外,另含 `DemoSwift` target:一个**纯 Swift** app,通过 framework module 消费 OC 版 `TencentQianWebView`,供 Swift 主语言的存量客户作集成范本。

### 为什么不需要 bridging header

OC SDK 是 `type: framework` + `DEFINES_MODULE: YES`,产物含 `Modules/module.modulemap`,Swift 侧 `import TencentQianWebView` 即可调用,编译器自动读 modulemap 合成 Swift 视图。**bridging header 与本 SDK 无关。**

Swift 调 OC 的路径由"被调 OC 是否为 module"决定,决策规则:

| 场景 | 机制 |
|---|---|
| Swift 调 framework(哪怕 OC 写的) | `import ModuleName`,**不需要** bridging header |
| Swift 调同 target 内裸 OC(`.m/.h`) | bridging header(`SWIFT_OBJC_BRIDGING_HEADER`) |
| OC 调同 target 内 Swift | `#import "<TargetName>-Swift.h"` + Swift 类标 `@objc` |

> ⚠️ 不要在 bridging header 里 `#import <TencentQianWebView/...>`:SDK 已是 module,重复声明会触发 umbrella header 警告。bridging header 只放"同 target 内、自己写的"OC 头。

`DemoSwift` 因只调 SDK framework、无同 target 裸 OC,故无 bridging header。

### 构建 DemoSwift

```bash
bash scripts/build-ios-objectivec.sh demoswift     # 模拟器 Debug
bash scripts/build-ios-objectivec.sh deviceswift   # 真机 Release unsigned ipa
bash scripts/build-ios-objectivec.sh all           # SDK + OC Demo + DemoSwift 全量
```

### OC SDK 在 Swift 下的写法要点

OC SDK 的 `TQ` 前缀、`NSString *const` 常量、marker protocol 事件在 Swift 下形态如下(详见 `DemoSwift/DemoViewController.swift` 注释):

- 工厂 `+defaultConfig` → `TQWebViewConfig.default()`
- `+openFromPresenter:url:...` → `TQWebView.open(fromPresenter:url:...)`
- `+callbacksWithOnSignResult:onEvent:` → `TQSignCallbacks(onSignResult:onEvent:)`
- `TQSignActionSign` 等 `NSString *const` → Swift `String`,用 `==` 比较(非 enum,无法 `switch` 穷举)
- 事件 `id<TQSdkEvent>` + 具体类 → Swift `event is TQWebContentProcessTerminatedEvent` 判断
- `+applyToWebView:` → `apply(to:hostController:...)`("WebView" 匹配参数名被吸收)
- `-loadURL:` → `load(_:)`("URL" 匹配参数名 "url" 被吸收)

> 桥接名不完全可推断(如 `openFromPresenter:` 未拆分而 `applyToWebView:` 拆分),以编译器实测为准。
