PAYATHON 2026

如何在 iOS app 中集成 Atom payment gateway 并解决 nb.h not found?

支付小周

明确结论

.a 文件里只有编译后的机器代码,不会自动向项目提供 nb.h、card.h 等头文件。即使静态库已经正确链接,编译器仍然需要单独找到这些头文件,否则会报错:nb.h file not found。

resourcesLib.bundle 也不包含在 .a 文件中。Atom Tech 应当把它与 SDK 的静态库、头文件一并提供。按照文档描述,这些文件通常放在 payAtom 目录下。如果收到的 SDK 只有两个 .a 文件,说明安装包不完整,需要向服务商索取以下内容:

  • 公共头文件,例如 nb.h、card.h、NSString+DES.h、NSData+DES.h
  • resourcesLib.bundle
  • SDK 依赖的系统 framework/library 清单
  • 支持的 Xcode、iOS 和 CPU 架构说明
  • 当前有效的接入文档及示例项目

不要自行创建同名的空 Bundle 来代替 resourcesLib.bundle。真实的 Bundle 很可能包含图片、XIB、Storyboard、本地化文件或其他运行时资源。

推荐的 SDK 目录结构

拿到完整 SDK 后,可以按下面的结构整理到项目目录中:

Vendor/
└── AtomPay/
    ├── Headers/
    │   ├── nb.h
    │   ├── card.h
    │   ├── NSString+DES.h
    │   └── NSData+DES.h
    ├── Libraries/
    │   ├── libAtomPayLib(Live).a
    │   └── libAtomPayLib(UAT).a
    └── Resources/
        └── resourcesLib.bundle

Xcode 中的 Group 只负责文件的逻辑展示,不能代替 Header Search Paths,也不能代替实际的文件路径配置。添加文件时,要确认文件确实复制到了项目目录,或者项目中保存的是正确的外部路径。

添加头文件并解决 nb.h not found

先把服务商提供的头文件加入项目,再选择应用 target,打开:

Build Settings → Search Paths → Header Search Paths

添加头文件所在的目录,例如:

$(PROJECT_DIR)/Vendor/AtomPay/Headers

如果头文件放在更深的子目录中,可以把该路径设为 recursive。目录结构清晰时,优先使用 non-recursive,以免错误匹配其他 SDK 中的同名头文件。

同时保留:

$(inherited)

完成配置后,下面这些导入语句才可能正常工作:

#import "nb.h"
#import "card.h"
#import "NSString+DES.h"
#import "NSData+DES.h"

如果这些头文件已经勾选了应用 target 的 Target Membership,项目也可能不需要额外配置 Header Search Paths。不过,第三方 SDK 通常更适合集中放在独立目录中,再通过搜索路径引用。

把 .h 文件加入 Xcode,不代表它们被包含进了 .a 文件。头文件负责向编译器声明类、协议和方法,实现代码则由 .a 文件在链接阶段提供。

正确链接 Live 和 UAT 静态库

同一个构建配置中不要同时链接 Live 和 UAT 两个库。它们通常包含相同的类和符号,同时链接可能引发 duplicate symbols,也可能让链接结果变得不可预测。

较稳妥的配置方式是:

  • Debug/UAT 构建链接 libAtomPayLib(UAT).a
  • Release/生产构建链接 libAtomPayLib(Live).a

可以分别创建两个 .xcconfig 文件:

// Debug.xcconfig
ATOM_LIBRARY = $(PROJECT_DIR)/Vendor/AtomPay/Libraries/libAtomPayLib(UAT).a
// Release.xcconfig
ATOM_LIBRARY = $(PROJECT_DIR)/Vendor/AtomPay/Libraries/libAtomPayLib(Live).a

然后在对应配置的 Other Linker Flags 中使用:

$(inherited)
-ObjC
"$(ATOM_LIBRARY)"

也可以在下面的位置按构建配置管理静态库:

Build Phases → Link Binary With Libraries

无论采用哪种方法,都要确认每个最终构建只链接当前环境对应的一个库。

在 Xcode 的 Other Linker Flags 设置中,一般分别添加以下两项即可:

$(inherited)
-ObjC

不要在 OTHER_LDFLAGS 中再次写入 $(OTHER_LDFLAGS)。旧文档里的写法:

$(OTHER_LDFLAGS) -ObjC

更接近早期工程模板。现代工程使用 $(inherited) 更清楚,也可以继承上级配置。

-ObjC 会让链接器载入静态库中的 Objective-C category 和相关对象文件。它不会处理头文件搜索,因此无法消除 nb.h not found。

添加 resourcesLib.bundle

拿到真实的 resourcesLib.bundle 后,按以下步骤添加:

  1. 将它拖入 Xcode 的 Project Navigator。
  2. 在弹窗中选择 Copy items if needed。
  3. 勾选应用 target。
  4. 打开应用 target。
  5. 进入 Build Phases → Copy Bundle Resources。
  6. 单击 +,选择 resourcesLib.bundle。
  7. 确认列表中只有一项,避免出现 “will be copied twice” 警告。

构建完成后,可以用下面的代码检查 Bundle 是否进入了应用包:

NSString *bundlePath =
    [[NSBundle mainBundle] pathForResource:@"resourcesLib"
                                    ofType:@"bundle"];

NSLog(@"resourcesLib path: %@", bundlePath);

如果输出为 nil,常见原因包括:

  • Bundle 没有加入当前 target;
  • Bundle 没有出现在 Copy Bundle Resources 中;
  • 文件名或扩展名不一致;
  • SDK 代码需要的 Bundle 名称不是 resourcesLib;
  • Bundle 被放进了其他 framework/bundle,但 SDK 从 main bundle 中查找。

不要把 .a 文件放入 Copy Bundle Resources。静态库应该参与链接,只有 Bundle 才属于资源复制阶段。

检查静态库是否支持当前架构

旧版支付 SDK 还可能只包含真机架构,不支持模拟器。可以用下面的命令检查静态库包含哪些架构:

lipo -info "libAtomPayLib(UAT).a"
lipo -info "libAtomPayLib(Live).a"

如果库中只有 arm64,通常只能用于真机,无法直接在 Intel 模拟器或缺少对应 simulator slice 的环境中链接。遇到下面这类错误时:

building for iOS Simulator, but linking in object file built for iOS

问题不在 Header Search Paths,而是二进制平台或架构不匹配。此时应要求服务商提供同时包含真机和模拟器 slice 的 XCFramework,不建议长期依靠排除架构之类的临时方案。

还需要向服务商确认 SDK 是否依赖其他系统组件,例如:

Security.framework
SystemConfiguration.framework
libz.tbd

仅凭这份简略文档无法确定具体依赖,应以 SDK 当前版本的说明或示例项目为准。

建议的 Objective-C 引用方式

如果只有某个页面会使用这些 SDK 类型,可以尽量把导入语句放在 .m 文件中,减少头文件对第三方 SDK 的依赖。

不过,当前类声明使用了 nbDelegate 和 cardDelegate。编译器在解析 ViewController.h 时必须知道这两个协议,因此仍需导入声明协议的头文件:

#import <UIKit/UIKit.h>
#import "nb.h"
#import "card.h"

@interface ViewController : UIViewController
    <NSXMLParserDelegate, nbDelegate, cardDelegate>

@property (weak, nonatomic) IBOutlet UIButton *InitiateRequest;
@property (nonatomic, strong) NSXMLParser *parser;

- (IBAction)callVC:(id)sender;

@end

在实现文件中,再按实际需要导入分类头文件:

#import "ViewController.h"
#import "NSString+DES.h"
#import "NSData+DES.h"

@implementation ViewController

- (IBAction)callVC:(id)sender
{
    nb *netB = [[nb alloc] init];
    netB.myDelegate = self;

    // 以下参数仅表示 SDK 的调用结构。
    // 实际值、环境地址和签名方式必须以服务商当前文档为准。
    netB.loginid = @"...";
    netB.password = @"...";
    netB.prodid = @"...";
    netB.txncurr = @"INR";
    netB.clientcode = @"...";
    netB.custacc = @"...";
    netB.amt = @"100.000";
    netB.txnid = @"UNIQUE_TRANSACTION_ID";
    netB.date = @"...";
    netB.bankid = @"...";
    netB.ru = @"...";

    [self presentViewController:netB animated:YES completion:nil];
}

- (void)secondviewcontrollerDissmissed:(NSString *)result
{
    NSLog(@"received---->%@", result);
}

@end

文档中的 • (void)viewDidLoad 使用了项目符号 •。正确的 Objective-C 实例方法声明必须以减号 - 开头:

- (void)viewDidLoad
{
    [super viewDidLoad];
}

示例里的注释还可能意外注释掉 bankid、ru 等赋值语句。复制代码后要逐行整理,不能直接原样使用。

如果拿不到头文件和 Bundle

如果服务商确认只交付两个 .a 文件,那么仅靠这两个文件无法按照该文档完成正常集成:

  • 没有头文件,编译器无法识别 nb、nbDelegate、cardDelegate 及其属性和方法声明;
  • 没有 resourcesLib.bundle,SDK 的界面或资源加载可能在运行时失败;
  • 从二进制中猜测接口或手工编写头文件并不可靠,还可能造成方法签名、内存管理或回调协议不匹配。

应要求服务商重新提供完整的 payAtom SDK 包或官方示例项目。提出请求时,可以明确列出:

1. Public headers
2. resourcesLib.bundle
3. UAT and Live binaries
4. Required frameworks/libraries
5. Supported architectures and minimum iOS version
6. Current integration guide
7. A runnable sample project

上线前注意事项

示例把 loginid、password 等值直接写在 App 中。如果这些值属于商户密钥、签名密钥,或者是可用于发起交易的敏感凭据,就不应硬编码或保存在客户端。iOS 应用中的字符串和资源可以被提取。

订单创建、金额校验、签名生成和支付结果确认通常应由商户服务端完成,客户端只接收短期有效的支付参数。

支付是否成功,也不能只根据 SDK 回调或返回字符串判断。最终订单状态应由服务端通过支付服务商提供的查询或通知机制确认,具体流程以 Atom Tech 当前有效的服务端接入规范为准。

所以,当前错误不能靠继续调整 -ObjC 来解决。首先需要取得完整的 payAtom 包,然后分别配置头文件搜索路径、静态库链接和资源 Bundle。这三项对应不同的构建阶段,缺少任何一项,都可能导致编译、链接或运行时失败。

备注:内容仅供参考。