前言

相比于Android应用商店(Google Play除外),iOS应用商店的管理更加规范,仔细,审核也更加严格。以下列出了iOS游戏上架AppStore需要注意的一些地方(重点针对基于Unity开发的iOS游戏)。

IPv6支持

上架AppStore的应用需支持IPv6环境。以下为从网上收集到的解决方案,已经过测试可用。 编写iOS Native Pluginipv6.mm

#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <err.h>

#define NSSTRING_TO_STR_OR_EMPTY(t) \
	(((t) == nil) ? NULL : strdup([(t) UTF8String]))

extern "C"
{
    const char* _IOSNativeExt_CheckIPAddress(const char* host);
}

const char* _IOSNativeExt_CheckIPAddress(const char* host)
{
    if( nil == host )
        return NULL;
    const char *newChar = "No";
    struct addrinfo* res0;
    struct addrinfo hints;
    struct addrinfo* res;
    int n, s;

    memset(&hints, 0, sizeof(hints));

    hints.ai_flags = AI_DEFAULT;
    hints.ai_family = PF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if((n=getaddrinfo(host, "http", &hints, &res0))!=0)
    {
        printf("getaddrinfo error: %s\n",gai_strerror(n));
        return NULL;
    }

    struct sockaddr_in6* addr6;
    struct sockaddr_in* addr;
    NSString * NewStr = NULL;
    char ipbuf[32];
    s = -1;
    for(res = res0; res; res = res->ai_next)
    {
        if (res->ai_family == AF_INET6)
        {
            addr6 =( struct sockaddr_in6*)res->ai_addr;
            newChar = inet_ntop(AF_INET6, &addr6->sin6_addr, ipbuf, sizeof(ipbuf));
            NSString * TempA = [[NSString alloc] initWithCString:(const char*)newChar
                                                        encoding:NSASCIIStringEncoding];
            NSString * TempB = [NSString stringWithUTF8String:"&ipv6"];

            NewStr = [TempA stringByAppendingString: TempB];
            printf("%s\n", newChar);
        }
        else
        {
            addr =( struct sockaddr_in*)res->ai_addr;
            newChar = inet_ntop(AF_INET, &addr->sin_addr, ipbuf, sizeof(ipbuf));
            NSString * TempA = [[NSString alloc] initWithCString:(const char*)newChar
                                                        encoding:NSASCIIStringEncoding];
            NSString * TempB = [NSString stringWithUTF8String:"&ipv4"];

            NewStr = [TempA stringByAppendingString: TempB];
            printf("%s\n", newChar);
        }
        break;
    }

    freeaddrinfo(res0);

    NSString * mIPaddr = NewStr;
    return NSSTRING_TO_STR_OR_EMPTY(mIPaddr);
}

C#脚本里面,进行如下判断

if(isIPv6)
{
	var socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
	socket.Bind(new IPEndPoint(IPAddress.IPv6Any, 0));
}
else
{
	var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
	socket.Bind(new IPEndPoint(IPAddress.Any, 0));
}

Build检查

  • BundleID
  • IL2CPP:上架AppStore必须使用IL2CPP。iOS 11之后,使用Mono后端,无法真机调试。
  • Version & Build:Version 和 Build 不能重复。即已经上传了一个Version为0.0.1,Build为1的ipa包到iTunes Connect之后,若要再重新上传一个Version0.0.1的包,须将Build改为2或其他值。(Android同理)
  • 权限声明:
    • NSPhotoLibraryUsageDescription:访问相册
    • NSMicrophoneUsageDescription:访问麦克风
    • NSCameraUsageDescription:访问相机
    • NSLocationWhenInUseUsageDescription:访问位置信息
  • 取消 Development Build

Xcode设置

因为XUPorter已经不更新了,建议在Unity里面使用Unity官方提供的XcodeAPI来设置Xcode工程。 以下是一个简单的设置Demo

public static void XcodeSetup(string pathToBuiltProject)
{
	string projPath = PBXProject.GetPBXProjectPath(pathToBuiltProject);
	string targetName = PBXProject.GetUnityTargetName();

	PBXProject proj = new PBXProject();
	proj.ReadFromString(File.ReadAllText(projPath));

	string targetGuid = proj.TargetGuidByName(targetName);

	// libz.tbd
	proj.AddFrameworkToProject(targetGuid, "libz.tbd", false);

	// SystemConfiguration.framework
	proj.AddFrameworkToProject(targetGuid, "SystemConfiguration.framework", false);

	// 普通文件
	var shareIcon = Path.Combine(Application.dataPath, "../shareicon.png");
    var shareIconFile = proj.AddFile(shareIcon, "SDK/shareicon.png", PBXSourceTree.Source);
    proj.AddFileToBuild(targetGuid, shareIconFile);

    // 编译选项
    proj.AddBuildProperty(targetGuid, "OTHER_LDFLAGS", "-ObjC");
    proj.SetBuildProperty(targetGuid, "ENABLE_BITCODE", "NO");

    // 证书签名
    // cert: "iPhone Developer: name (id)"
    // provision: "provisioning_profile_name"(非ID)
    proj.SetBuildProperty(targetGuid, "PROVISIONING_PROFILE_SPECIFIER", provision);
	proj.SetBuildProperty(targetGuid, "CODE_SIGN_IDENTITY", cert);
	proj.SetTeamId(targetGuid, teamID);

	proj.WriteToFile(projPath);



	// Info.plist
	var plistPath = Path.Combine(pathToBuiltProject, "Info.plist");
    var plist = new PlistDocument();
    plist.ReadFromFile(plistPath);

    // CFBundleURLTypes
    var array = plist.root.CreateArray("CFBundleURLTypes");

    var urlDict = array.AddDict();
    urlDict.SetString("CFBundleTypeRole", "Editor");
    urlDict.SetString("CFBundleURLName", "weixin");
    var urlInnerArray = urlDict.CreateArray("CFBundleURLSchemes");
	urlInnerArray.AddString("wx_app_id");

    // CFBundleURLTypes
    array = plist.root.CreateArray("LSApplicationQueriesSchemes");
    array.AddString("mw");

    plist.root.SetString("NSPhotoLibraryUsageDescription", "此 App 需要您的同意才能读取媒体资料库");

    plist.WriteToFile(plistPath);



    // Capability
    var postfix = "BundleIDPostfix"; // com.abc.xyz -> xyz
	ProjectCapabilityManager capManager = new ProjectCapabilityManager(projPath, postfix + ".entitlements", targetName);

    capManager.AddInAppPurchase();

    capManager.WriteToFile();
}

生成ipa

旧版xcrun命令已经失效,打出来的包签名验证不过。建议使用xcodebuild命令完成buildarchive操作。 Xcode设置好之后,可以使用下面的命令,生成商店ipa包。

  1. Clean:xcodebuild -verbose -project /path/to/UnityBuildOutput/Unity-iPhone.xcodeproj -target Unity-iPhone -configuration Release clean
  2. Build:xcodebuild archive -project /path/to/UnityBuildOutput/Unity-iPhone.xcodeproj -scheme Unity-iPhone -archivePath /path/to/xcodeProjectArchive/bundleIDPostfix.xcarchive -configuration Release -jobs 8
  3. Archive:xcodebuild -verbose -exportArchive -archivePath /path/to/xcodeProjectArchive/bundleIDPostfix.xcarchive -exportOptionsPlist /path/to/exportOptionFiles/exportAppStore.plist -exportPath /path/to/exportIPA

exportAppStore.plist的文件内容如下

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>compileBitcode</key>
    <false/>
    <key>uploadBitcode</key>
    <false/>
    <key>uploadSymbols</key>
    <false/>
    <key>method</key>
    <string>app-store</string>
</dict>
</plist>

提交ipa

iTunes Connect上完成基本设置。
在Mac机器上,通过Xcode->Open Developer Tool启动Application Loader,选择交付您的应用,选取对应的ipa包,然后一直点下一步就可以了。
上传完成之后,需要经过一段时间的处理,处理完成之后就可以选择该构建版本进行审核发布。
关于iTunes Connect设置的具体细节,可以自行搜索。

关于iOS内购

准备工作

  • 在[iTunes Connect]上填写协议、税务和银行业务信息,未填写会导致invalidProductIdentifiers错误。
  • 添加产品:在iTunes Connect上添加产品,产品ID可以自定义。
  • 添加沙盒测试账号:需用真实存在的,且未作为AppleID的邮箱,后续需要邮件验证。
  • 开发者后台为App ID添加In-App Purchase服务,DevelopmentDistribution都要开通。
  • 更新Provisioning Profiles

iTunes Connect内购设置参考

开发

内购代码示例可自行搜索。