Cocos Creator iOS APP开发详解
版本 v1.0
标签(空格分隔): iOS cocos_creator
目录 [toc]
1. pod相关 1.1 生成podfile 终端cd到指定路径,然后输入pod init即可在该路径下生产一份空Podfile文件,直接用记事本编辑保存即可 。
也可以在终端中直接编辑Podfile,输入vi Podfile,回车,会在终端中显示Podfile的全部内容。
按下按键i,进入编辑模式(插入模式)
按下按键esc,退出编辑模式
退出编辑模式后,常见的保存等命令如下:
``objective-c 以下均为按下esc退出编辑模式后,再输入。前面的冒号也要输入
:wq 保存后退出vi,若为 :wq! 则为强制储存后退出(常用)
:w 保存但不退出(常用)
:w! 若文件属性为『只读』时,强制写入该档案
:q 离开 vi (常用)
:q! 若曾修改过档案,又不想储存,使用 ! 为强制离开不储存档案。
:e! 将档案还原到最原始的状态!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 ### **1.2 使用pod导入第三方库** 在导入第三方库(下面简称pod库)之前,先做好准备工作: 1、设置头文件、库文件等的搜索路径 2、清空debug-xconfig、release-xconfig 3、配置终端网络环境 4、退出XCode <a id="set_search_path"></a> #### **1.2.1 设置搜索路径** 在项目的Target -> Build Settings下,搜索`SEARCH_PATH`, `FRAMEWORK_SEARCH_PATHS`、`HEADER_SEARCH_PATHS`、`LIBRARY_SEARCH_PATHS`、`USER_HEADER_SEARCH_PATHS`,都加入`$(inherited)`。如果是旧工程或者手动添加了pod的search_path,还需要把手动输入的pod相关的路径全部删掉,然后再执行`pod install`操作。 **why?** 因为自动生成的路径,会把`framework`的版本号也写进去,pod库如果更新了,会因为版本号不一致,找不到framework,导致编译报错! <a id="clear_configuration"></a> #### **1.2.2 清空配置** 清空debug-configuration、release-configuration配置。 点击Project -> Info,找到Configuration,点开debug和release,将下面的所有项都设置为None。无需担心有什么问题,pod导入后会自动生成新的配置。 <a id="set_cmd_proxy"></a> #### **1.2.3 配置终端网络环境** 因为git时网络环境不稳定,建议将终端配置网络代理后再执行pod导入。<font color=RED>**终端默认是不使用网络代理的,即使代理工具挂全局代理,终端也不会使用**</font>。 在终端中输入`export all_proxy=socks5://127.0.0.1:1080`,即可使<font color=RED>**当前的终端**</font>使用网络代理,其他终端依然没有使用网络代理。命令中的`1080`是mac的默认端口号,实际端口号看代理工具配置。 有时候还需要额外设置git代理,详细终端网络环境配置内容见本文后续章节[终端设置代理](#cmd_proxy)。 #### **1.2.4 退出XCode** pod导入后会生成新的XCode工程文件,且项目要用新的工程文件,因此要退出旧的工程。 #### **1.2.5 开始导入第三方库** podfile编辑完成,前面的准备工作完成后,在终端输入`pod install --repo-update`就会自动导入pod库,并生成新的工程文件,直接打开新的工程文件进行项目开发即可。 ### **1.3 pod命令详解** #### **1.3.1 基本说明** Podfile文件中,是可以指定pod库的版本的。示例如下:
pod ‘AFNetworking’ // 不显式指定依赖库版本,表示每次都获取最新版本 pod ‘AFNetworking’, ‘2.0’ // 只使用2.0版本 pod ‘AFNetworking’, ‘> 2.0’ // 使用高于2.0的版本 pod ‘AFNetworking’, ‘>= 2.0’ // 使用大于或等于2.0的版本 pod ‘AFNetworking’, ‘< 2.0’ // 使用小于2.0的版本 pod ‘AFNetworking’, ‘<= 2.0’ // 使用小于或等于2.0的版本 pod ‘AFNetworking’, ‘> 0.1.2’ // 使用大于等于0.1.2但小于0.2的版本 pod ‘AFNetworking’, ‘> 0.1’ //使用大于等于0.1但小于1.0的版本 pod ‘AFNetworking’, ‘~> 0’ // 使用最新版本,与不显示指定依赖库版本相同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 首次导入pod库,会生成`Podfile.lock`文件,会记录当前使用的pod库的版本。在不使用`pod update`的情况下,重复导入pod库时,会根据Podfile.lock文件记录的版本去进行的对比,有对应版本的pod库就直接跳过该pod库的导入流程。 `pod install`看字面意思很容易误解为仅第一次导入pod库时才使用。实际上,即使导入过一些pod库,后续要新增或者<font color=RED>**删除**</font>pod库时,都可以使用`pod install`命令。在新增或删除pod库时,`pod install`和`pod upddate`并没有区别。 #### **1.3.2 常用pod命令** - 一般情况下均使用<font color=RED>**`pod install --repo-update`来导入新的pod库,并把旧的pod库更新到最新版本**</font> - 不想升级已导入的pod库的版本,就用`pod install`来导入新的pod库 - 可以在`pod install`后加入`--verbose`,用于看具体的下载进度 #### **1.3.3 常见pod命令详解** - **pod install** 用于添加或移除第三方库框架 工作原理如下: 1、如果`Podfile.lock`文件存在,则直接从此文件中读取pod库的信息,并且它会只下载`Podfile.lock`文件中指定的版本安装。对于不在`Podfile.lock`文件中的pod库,`pod install`命令会搜索这个pod库在`Podfile`文件中指定的版本来安装。 2、如果`Podfile.lock`不存在, 则会读取`Podfile`文件内的框架信息,然后执行下载。再根据下载好的框架信息,生成`Podfile.lock`文件。 - **pod install --repo-update** 添加新库并更新旧库 根据`Podfile`文件或者`Podfile.lock`下载并导入对应的pod库,并检查旧的pod库是否有更新,如有更新,则下载最新版本的pod库。 - **pod update** 用于更新`Podfile`文件中所有的pod库的版本 工作原理如下: 1、先拉取远程最新目录,再根据目录中的资源重新更新一遍pod。 2、它不管`Podfile.lock`是否存在,都会读取`Podfile`文件的框架信息。如果你的`Podfile`中每个库都指定了版本还好,如果没有,那么每次都是拉一遍最新库。 - **pod update --no-repo-update** 不拉取远端新版本的pod库 只根据本地目录更新库,不拉取远程的新版库。 - **pod update xxx** 更新指定名称的库到最新版本 如`pod update SDWebImage`。 - **pod repo update** 用来更新本地`cocoapods`的`spec`资源配置信息 可能你从来不会用`pod repo update`也可以拿到最新的库,那是因为使用`pod update`默认会执行一遍`pod repo update`,所以会发现如果你的pod库引用多了,每次`pod update`都很慢。网络如果不好更是经常会断掉,就是因为你在更新pod时,也更新了资源目录。 - **pod repo update ~/.cocoapods/repos/\*\*\*/** 更新某个具体的私有库。 有时候会引用一些私有库,`pod repo update`会把 `~/.cocoapods/repos/`这个目录下所有的资源库目录都更新一遍,而`pod repo update ~/.cocoapods/repos/***/`则只更新某个具体的私有库。 ### **1.4 pod导入常见问题** pod导入完成后会在终端<font color=GREEN>绿字</font>输出下面内容:
Pod installation complete! There are 21 dependencies from the Podfile and 36 total pods installed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 同时还会用<font color=RED>红字</font>输出报错,<font color=#F0E68C>黄字</font>输出警告信息。需要注意的是,部分黄字警告必须解决,否则项目编译时会报错,比如库文件找不到等等。 #### **1.4.1 连接超时** 通常安装失败的红字,包含`connected out of time`,那就是连接超时了。 - 如果没有设置终端代理,参照前面的[终端网络环境配置](#set_cmd_proxy)设置终端代理; - 如果是git相关的链接超时,参照[终端设置代理](#cmd_proxy)章节,设置git代理; - 如果已经设置了代理,可能是网络代理工具不稳定导致的,只能多试几次。 #### **1.4.2 没有设置$(inherited)** pod安装完后有黄字警告,内容如下:
[!] The xmb-mobile [Debug] target overrides the FRAMEWORK_SEARCH_PATHS build setting defined in Pods/Target Support Files/Pods-client_ios_fm_a/Pods-client_ios_fm_a.debug.xcconfig'. This can lead to problems with the CocoaPods installation - Use the $(inherited)` flag, or - Remove the build settings from the target.
[!] The xmb-mobile [Debug] target overrides the OTHER_CFLAGS build setting defined in Pods/Target Support Files/Pods-client_ios_fm_a/Pods-client_ios_fm_a.debug.xcconfig'. This can lead to problems with the CocoaPods installation - Use the $(inherited)` flag, or - Remove the build settings from the target.
[!] The xmb-mobile [Debug] target overrides the HEADER_SEARCH_PATHS build setting defined in Pods/Target Support Files/Pods-client_ios_fm_a/Pods-client_ios_fm_a.debug.xcconfig'. This can lead to problems with the CocoaPods installation - Use the $(inherited)` flag, or - Remove the build settings from the target.
1 2 3 4 5 6 7 8 9 这个警告必须处理,否则`@import`导入pod库时,可能会找不到库。造成这个警告的原因,就是没有在search_path里添加`$(inherited)`,参照[设置搜索路径](#set_search_path)设置后,重新运行刚才的pod命令即可。 #### **1.4.3 没有清空debug-configuration、release-configuration** pod安装完后有黄字警告,内容如下:
[!] CocoaPods did not set the base configuration of your project because your project already has a custom config set. In order for CocoaPods integration to work at all, please either set the base configurations of the target ParentPortal to Pods/Target Support Files/Pods/Pods.debug.xcconfig or include the Pods/Target Support Files/Pods/Pods.debug.xcconfig in your build configuration.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 这个警告同样需要处理,参照[清空配置](#clear_configuration)小节设置后,重新运行刚才的pod命令即可。 #### **1.4.4 其他说明** 如果后期要删除部分已经导入的pod库,直接在`Podfile`文件里删除相应的导入语句,然后运行`pod install --repo-update`语句即可。 如果执意要删除所有已导入的pod库,将项目的整个pod库重新安装,则直接删除`Podfile.lock`文件、`*.xcworkspace`文件、`Pod`文件夹即可。然后再运行`pod install --repo-update`语句。 ---------- ## **2. XCode相关** ### **2.1 生成jsb-link模板** 先用`cocos creator`构建生成`jsb-default`的XCode工程,然后将工程整个拷贝到`build-templete`文件夹下,并改名`jsb-link`,接着删除多余的文件。 - jsb-link文件夹下,只保留`frameworks`文件夹和`main.js`文件。 - ios相关的内容在jsb-link/frameworks/runtime-src/proj.ios_mac文件夹下。该文件夹下仅保留`ios`、`mac`、`plugins`文件夹和`Podfile`、`*.xcodeproj`文件,这些都要提交到git。 - pod生成的`*.xcworkspace`文件(白色的)无需同步到jsb-link ### **2.2 #pragma mark** `##pragma mark`语句在Objective-C中并无特殊含义,是XCode编辑器用于文件导航栏的标识。 - `##pragma mark -` ```objective-c - (void)testFunc1 { } ##pragma mark - - (void)testFunc2 { }
会在文件的顶部导航栏里,用一条横线隔开testFunc1和testFunc2。
#pragma mark XXX XXX代指任何正常的文本内容。
1 2 3 4 5 6 7 - (void)testFunc1 { } #pragma mark testDelegate - (void)testFunc2 { }
会在文件的顶部导航栏里,出现一个区块testDelegate,并且testFunc2在testDelegate区块内。
1 2 3 4 5 6 7 - (void)testFunc1 { } #pragma mark - testDelegate - (void)testFunc2 { }
前面两个标识的混用,会有一条横线出现在区块testDelegate上方,testFunc2在testDelegate区块内。
2.3 app运行时查看实时日志 直接run在真机上调试时,控制台会输出JS及OC的全部log信息,但是在手机上直接直接运行app时,不像Android Studio,这时候控制台并没有任何log输出。但是依然有办法看到log日志,步骤如下:
但是,此方法不能输出JS的log日志。
2.4 常见报错 2.4.1 @import报错 使用@import导入pod库时报错,报错内容如下:
1 Use of '@import' when C++ modules are disabled, consider using -fmodules and -fcxx-modules
解决方案: 在Target -> Build Settings里将Enable modules(C and objective-C)改为YES,若使用@import的文件后缀为.mm,则还需要在Target -> Build Phases -> Compile Sources里找到该文件,然后添加-fcxx-modules到该文件的Compiler Flags列。
2.4.2 找不到头文件 使用#include "xxx.h"添加头文件时,如果编译时找不到头文件,或者找不到这个头文件引用的头文件,但是在项目里有这个头文件,相关依赖也添加了,HEADER_SEARCH_PATHS也有对应的查找路径,那么把*.m文件改成*.mm文件。
说明 :
#include "xxx.h"是C++的引入头文件方式,*.m文件是Objective-C(下面简称OC)文件,而*.mm文件是兼容OC与C++的OC文件,因此改后缀名后才会找得到对应的头文件。
2.4.3 真机闪退:Terminated due to memory issue 具体报错信息如下:
1 2 3 4 5 6 7 8 9 Details The app “xmb-mobile” on iPhone quit unexpectedly. Domain: IDEDebugSessionErrorDomain Code: 4 Failure Reason: Message from debugger: Terminated due to memory issue User Info: { IDERunOperationFailingWorker = DBGLLDBLauncher; }
说明 :
这个是内存不足的报错,如果只出现在6s这种很老的机型上,可以忽略不管。如果在iPhoneX等不算老的机型上,那就需要排查项目的内存泄露,手动释放内存。
2.5 其他说明 1、如果只改了cocos的脚本、图片资源、场景等cocos creator项目的内容,构建发布后,无需重新启动XCode/导入pod库等操作,因为项目的东西都是资源的形式被XCode引用。
3. Objective-C相关 3.1 基本数据类型及NS类 3.1.1 简介 基本数据类型int、float、double、BOOL(注意BOOL全部大写)等,与其他强类型语言没有区别;NS类则是指NS开头的OC特有的数据类型,例如:NSSttring、NSNumber、NSArray、NSDictionary等。它们都是一个类,对基本数据类型进行了封装。例如NSNumber就是封装了int、unsignen int、long、char等。
但是,NSInteger是个例外,虽然它以NS开头,但NSIntefer是基本数据类型,等价于int or long,详见下面代码。
``obejective-c #if LP64 || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64 typedef long NSInteger; typedef unsigned long NSUInteger; #else typedef int NSInteger; typedef unsigned int NSUInteger; #endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 注意: - <font color=RED>**这些NS开头的数据类型均为类类型,不是基本数据类型。**</font> - OC里类的实例对象的声明、定义时均要带 \* 号。NSString等是类,自然也要带 \* 号。 - NS类的值大部分都是@开头。 例: ```objective-c NSString *str = @"2"; NSNumber *num = @4; NSArray *arr = @[@1, str, @"3", num, @true];
3.1.2 NSNumber 数字类 NSNumber类封装了int、unsigned int、long、unsigned long、float、ddouble、char等与数值相关的基本数据类型。
它可以与基本数据类型相互转化,常见操作如下:
``objective-c int testIntValue = 12345; float testFloatValue = 123.45; double testDoubleValue = 123.45; unsigned long testULValue = 12345;
// 基本数据类型 转 NSNumber NSNumber *nsInt = [NSNumber numberWithInt:testIntValue]; NSNumber *nsFloat = [NSNumber numberWithFloat:testFloatValue]; NSNumber *nsDouble = [NSNumber numberWithDouble:testDoubleValue]; NSNumber *nsUL = [NSNumber numberWithUnsignedLong:testULValue];
// NSNumber 转 基本数据类型 testIntValue = [nsInt intValue]; testFloatValue = [nsFloat floatValue]; testDoubleValue = [nsDouble doubleValue]; testULValue = [nsUL unsignedLongValue];
1 2 3 4 5 6 7 8 9 10 11 12 13 #### **3.1.3 NSString 字符串类** oc里没有c++等语言的字符串类型string,NSString就是oc里的字符串类。 - 判断两个字符串是否相等,使用函数`isEqualToString`。 ```objective-c NSString *str1 = @"str1"; NSString *str2 = @"str2"; BOOL result = [str1 isEqualToString:str2];
判断字符串是否为空字符串,建议使用下面的自定义方法,添加到工具类中当作类方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 + (BOOL)isEmptyString:(NSString *)str { NSString *string = str; if (nil == string || NULL == string) { return YES; } // 判断是否为NSNull,NSNull是一个值为空的类,继承自NSObject类 if ([string isKindOfClass:[NSNull class]]) { return YES; } // 去掉空格后,长度是否为0 if ([[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length]==0) { return YES; } return NO; }
对于纯数字字符串,可以与 int、float 等基本数据类型相互转化。
1 2 3 4 5 6 7 8 9 10 // NSString 转 int 等基本数据类型 NSString *str1 = @"123"; int intNum = [str1 intValue]; NSString *str2 = @"123.45"; float floatNum = [str2 floatValue]; // int 等基本数据类型 转 NSString str2 = [NSString stringWithFormat:@"%d", intNum]; NSString *str3 = [NSString stringWithFormat:@"%f", float];
1 2 3 4 5 6 7 8 9 10 11 12 13 int testIntValue = 12345; float testFloatValue = 123.45; double testDoubleValue = 123.45; unsigned long testULValue = 12345; NSString *testStr = @"testStr"; NSNumber *testNum = @67890; NSString *resStr1 = [NSString stringWithFormat:@"%d", testIntValue]; NSString *resStr2 = [NSString stringWithFormat:@"%f", testFloatValue]; NSString *resStr3 = [NSString stringWithFormat:@"%f", testDoubleValue]; NSString *resStr4 = [NSString stringWithFormat:@"%lu", testULValue]; NSString *resStr5 = [NSString stringWithFormat:@"%@", testStr]; NSString *resStr6 = [NSString stringWithFormat:@"%@: %d%@", testStr, testIntValue, testNum];
C++ string 可与 NSString 进行互相转换操作,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 std::string cppStr1 = "cppStr1"; // C++ string 转 NSString NSString *nsStr1 = [NSString stringWithUTF8String:cppStr1]; // NSString 转 C++ string std::string *cppStr2 = new std::string([nsStr1 UTF8String]); // C++ string 转 C string // 注意,cppStr1是实例对象,所以是 . 操作访问成员、成员函数c_str()函数 // cppStr2是指针,所以是 -> 操作访问成员、成员函数c_str()函数 cppStr1.c_str(); cppStr2->c_str();
3.1.4 NSArray 数组类 分为NSArray不可变数组和NSMutableArray可变数组,NSMutableArray继承自NSArray。
数组的每个成员的类型可以不同,但必须是对象(NSObject) ,不能是基本数据类型。NSArray是不可变数组,初始化后不可以对数组的成员进行修改,但是可以修改NSArray实例对象所指向的内容。NSMutableArray是可变数组,长度可变化,可对数组成员进行修改。
初始化操作如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 // NSArray的成员可以是任意类型的对象,@1, @2是NSNumber,@"3"是NSString // 注意:用arrayWithObjects初始化NSArray结尾必须有nil,表示结束 NSArray *arr1 = [NSArray arrayWithObjects:@1, @2, @"3", @true, nil]; // 简化初始化的方式 NSArray *arr2 = @[@1, @"2", @"3"]; // 看起来是改变了NSArray,但是实际上是创建了新的NSArray,然后赋值给arr2 arr2 = @[@1, @"2", @"3", @"4", @5]; // 错误,NSArray的成员只能是对象,不能是基础数据类型 // 因此1, 2是无法填入数组的,会报错。 NSArray *arr3 = [NSArray arrayWithObjects:1, 2, @"3", nil];
使用类方法arrayWithObjects初始化NSArray对象时,使用nil或其他空值表示初始化结束 。因此,当成员列表中有nil或空值时,初始化会结束,会导致实际生成的数组比预期的数组要短。
1 2 // 因为nil表示结束,因此arr2实际上只有@1, @2两个成员 NSArray *arr2 = [NSArray arrayWithObjects:@1, @2, nil, @"3", @true, nil];
而使用简化的方式初始化NSArray时,遇到空值,会发生更严重的问题。在运行时会直接报错,抛出异常 。
例:
1 2 3 4 5 6 NSNumber *num = nil; NSString *str = @"4"; // 这段代码在编辑、编译时均不会有任何错误信息 // 但是当程序运行时,会直接因为num为空值,抛出异常 NSArray *arr = @[@1, @"2", num, str];
NSArray的一些常见操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 NSArray *array = [NSArray arrayWithObjects:@"Hola", @"two", @"three", @"for", nil]; // 获取数组的元素个数 使用count NSLog(@"%ld",[array count]); // 根据下标获取下标对应的对象 NSS[array objectAtIndex:2]); // 通过元素找下标 NSUInteger r = [array indexOfObject:@"three"]; NSLog(@"%ld",r); // 判断数组中是否包含了某个元素 BOOL r1 = [array containsObject:@"one"];
NSMutableArray继承自NSArray,是可变数组,初始化方式略有不同,如下:
1 2 3 4 5 6 7 8 9 10 // 方法1 NSMutableArray *nsmArr1 =[NSMutableArray array]; // 创建空数组 [nsmArr1 addObject:@1]; // 在数组末尾新增成员 // 方法2 NSMutableArray *nsmArr2 = [NSMutableArray arrayWithCapacity:5]; // 创建5个成员的空数组 [nsmArr2 addObject:@1]; // 新增成员 nsmArr2[1] = @2; // 修改成员 ... nsmArr2[4] = @"5";
NSMutableArray可以对数组成员进行删改,可以对数组进行新增、插入操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 NSArray *arr = @[@"1", @"2", @"3", @"4", @"5"]; NSMutableArray *nsmArr = [[NSMutableArray alloc]initWithArray:arr]; // 插入 insertObject [nsmArr insertObject:@6 atIndex:0]; // 此时值为[@"6", @"1", @"2", @"3", @"4", @"5"] // 替换 replaceObjectAtIndex [nsmArr replaceObjectAtIndex:0 withObject:@0]; // 此时值为[@"0", @"1", @"2", @"3", @"4", @"5"] // 删除元素 [nsmArr removeLastObject]; // 此时值为[@"0", @"1", @"2", @"3", @"4"] [nsmArr removeObjectAtIndex:2]; // 此时值为[@"0", @"1", @"4"] [nsmArr removeObject:@"3"]; // 此时值为[@"0", @"1", @"4"] // 交换元素 exchangeObjectAtIndex [nsmArr exchangeObjectAtIndex:0 withObjectAtIndex:2]; // 此时值为[@"4", @"1", @"0"]
3.1.5 NSDictionary 字典类 也分为NSDictionary不可变字典和NSMutableDictionary可变字典,NSMutableDictionary继承自NSDictionary。NSDictionary的key只能是NSString类对象,而value可以是任意类型的对象。因此,NSDictionary经常当作json对象来使用 。特别是在与js进行交互时,用NSString接受格式化后的json string,然后解析成NSDictionary。
初始化操作如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // NSDictionary的成员可以是任意类型的对象,@123,@true是NSNumber,@"test1"是NSString // 注意:用initWithObjectsAndKeys初始化NSDictionary结尾必须有nil,表示结束 NSDictionary *dict1 = [NSDictionary initWithObjectsAndKeys:@123, @"intValue", @true, @"boolValue", @"test1", @"strValue", nil]; // 简化的初始化方式 NSDictionary *dict2 = @{@"strValue":@"testString", @"boolValue":@true, @"intValue":@123}; // 看起来是改变了NSDictionary,但是实际上是创建了新的NSDictionary,然后赋值给dict2 dict2 = @{@"strValue1":@"test1", @"strValue2":@"test2"}; // 错误,NSDictionary的成员只能是对象,不能是基础数据类型 // 因此123, 456是无法填入数组的,会报错。 NSDictionary *dict2 = @{@"value1":123, @"value2":456, @"value3":@789}; // 取值,取key NSNumber *num = [dict1 objectForKey:@"intValue"]; NSArray *keys = [dict1 allKeysForObject:@true];
int为0时,设置给NSDictionary不会报错,但也不会写入,会当作空值自动去掉这个键值对
int值、枚举值设置给NSDictionary时,编辑器不会红字报错(float等类型直接设置时,会红字报错,提示要转为object)。但是,在运行时,会直接报错EXC_BAD_ACCESS。很神奇的是,调试界面此时是能打印出该值的。只能转换成NSNumber来解决这个报错。因此,所有的基本数据类型,必须手动显式转换成对象后,才能设置给NSDictionary。
使用类方法initWithObjectsAndKeys初始化NSDictionary对象时,使用nil或其他空值表示初始化结束 。因此,当成员列表中有nil或空值时,初始化会结束,会导致实际生成的数组比预期的数组要短。
1 2 // 因为nil表示结束,因此dict1实际上只有@123, @true两个成员 NSDictionary *dict1 = [NSDictionary initWithObjectsAndKeys:@123, @"intValue", @true, @"boolValue", nil, @"strValue", nil];
使用简化的方式初始化NSDictionary时,遇到空值,会发生更严重的问题。在运行时会直接报错,抛出异常 。
例:
1 2 3 4 5 6 7 8 NSString *str1 = @"test1"; NSString *str2 = @""; // str2是空字符串,不是空值,能正常赋值给dict NSString *str3 = nil; // 这段代码在编辑、编译时均不会有任何错误信息 // 但是当程序运行时,会直接因为str3为空值,抛出异常 NSDictionary *dict = @{@"strValue1":str1, @"strValue2":str2, @"strValue3":str3};
NSMutableDictionary继承自NSDictionary,是可变字典,初始化方式略有不同,如下:
1 2 3 4 5 6 7 8 9 // 方法1 NSMutableDictionary *dict1 =[NSMutableDictionary dictionary]; // 创建空字典 [dict1 setValue:@123 forKey:@"intValue"]; NSString *str = nil; [dict2 setValue:str forKey:@"strValue"]; // 方法2 NSMutableDictionary *dict2 = [NSMutableDictionary dictionaryWithDictionary:dict1];
NSMutableDictionary可以对字典成员进行增、删、改操作。
1 2 3 4 5 6 7 8 9 10 11 NSDictionary *dict1 = @{@"strValue":@"testString", @"boolValue":@true, @"intValue":@123}; NSMutableDictionary *dict2 = [[NSMutableDictionary alloc]initWithDictionary:dict1]; // 增 [dict2 setValue:@"123.45" forKey:@"floatValue"]; // 删 [dict2 removeObjectForKey:@"intValue"]; // 改 [dict2 setValue:@"78.9" forKey:@"floatValue"];
在OC与JS交互时,OC的函数参数只能使用NS类来接受参数,因此标准JSON格式的 NSString 和当作JSON对象来使用的 NSDictionary 经常互相转换。
下面是两者的转换函数,建议加到工具类中当类函数使用。
NSDictionary 转成JSON格式的 NSString:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 /** * NSDictionary 转成JSON格式的 NSString */ + (NSString *)NSDictionary2NSString:(NSDictionary *)jsonObj { NSLog(@"NSDictionary2NSString jsonObj: %@", jsonObj.description); // 判断非空 if (nil == jsonObj) { NSLog(@"NSDictionary2NSString error: jsonObj nil"); return @""; } // 判断格式是否合法 BOOL flag = [NSJSONSerialization isValidJSONObject:jsonObj]; if (!flag) { NSLog(@"%NSDictionary2NSString error: jsonObj is invalid NSDictionary data"); return @""; } // 开始转换 NSError *err = nil; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonObj options:NSJSONWritingPrettyPrinted error:&err]; // 异常处理 if (!!err) { NSLog(@"NSDictionary2NSString error: parse jsonObj failed, error: %@", err.localizedDescription); return @""; } // 完成转换 NSString *jsonStr = [[NSString alloc]initWithData:jsonData encoding:NSUTF8StringEncoding]; NSLog(@"%NSDictionary2NSString result jsonStr: %@", jsonStr); return jsonStr; // 以下内容可选,去掉字符串中的空格和换行,对结果没有影响 NSMutableString *mutStr = [NSMutableString stringWithString:jsonStr]; // 去掉字符串中的空格 NSRange range = {0, jsonStr.length}; [mutStr replaceOccurrencesOfString:@" " withString:@"" options:NSLiteralSearch range:range]; // 去掉字符串中的换行符 NSRange range2 = {0, mutStr.length}; [mutStr replaceOccurrencesOfString:@"\n" withString:@"" options:NSLiteralSearch range:range2]; NSLog(@"NSDictionary2NSString result mutStr: %@", mutStr); return mutStr; }
JSON格式的 NSString 转成 NSDictionary:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 /** * JSON格式的 NSString 转成 NSDictionary */ + (NSDictionary *)NSString2NSDictionary:(NSString *)jsonStr { NSLog(@"%@ %@ NSString2NSDictionary jsonStr: %@", LOG_TAG, TAG, jsonStr); if ([self isEmptyString:jsonStr]) { NSLog(@"%@ %@ NSString2NSDictionary error: jsonStr nil or empty string", LOG_TAG, TAG); return [NSDictionary dictionary]; } NSData *jsonData = [jsonStr dataUsingEncoding:NSUTF8StringEncoding]; NSError *err = nil; NSDictionary *properties = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err]; if (!!err) { NSLog(@"%@ %@ NSString2NSDictionary error: parse jsonStr failed, error: %@", LOG_TAG, TAG, err.localizedDescription); return [NSDictionary dictionary]; } return properties; }
3.2 函数 3.2.1 函数声明 OC的函数声明格式如下: + - (返回值类型)函数名:(参数1类型)参数1名称 [参数2别名]:(参数2类型)参数2名称 …;
其中,返回值类型之前的+号表示类函数,-号表示类的成员函数。
而方括号[ ]里面的内容是可以省略的。也就是参数名的别称可以省略。
示例:
1 2 3 4 5 6 7 8 9 // ModooPlay类的类函数 + (ModooPlay *)getInstance; // ModooPlay类的成员函数 - (void)getStrategyConfig:(NSNumber *)strategyId; - (void)initSDK:(BOOL)isFirst showView:(UIView *)view viewCtrl:(UIViewController *)viewCtrl - (NSNumber *)getCount:(NSString *)id :(NSNumber *)value;
3.2.2 函数调用 调用类函数的格式:
[类名 空格 函数名:参数1 [参数2别称]:参数2 …];
调用类的成员函数的格式: [类的实例对象 空格 函数名:参数1 [参数2别称]:参数2 …];
函数调用时,使用方括号[]包围整个语法,这一点是跟其他语言很不一样的地方。而且也不用. 或 -> 来访问函数,而是直接用空格分隔开对象和函数名。
示例:
1 2 3 4 5 6 7 8 // 调用类函数 ModooPlay *instance = [ModooPlay getInstance]; // 调用类的成员函数 NSNumber *id = @500; [instance getStrategyConfig:id]; [[ModooPlay getInstance] getStrategyConfig:id];
3.2.3 补充说明 OC的函数,完整的函数名是包含冒号:和参数别称的。
1 2 3 - (NSNumber *)getCount:(NSString *)id :(NSNumber *)value; - (NSNumber *)getCount2:(NSString *)id subNum:(NSNumber *)value;
上面的代码中,getCount的完整函数名是getCount::,注意是2个:号;
而getCount2因为有参数别称,因此完整的函数名是getCount2:subNum:。
这一点在JS调用OC时特别重要,因为不是完整的函数名,OC这边会找不到函数而报错。注意不要遗漏:号和参数别称。
3.3 类 3.3.1 类的声明 类的声明放在*.h头文件中
格式 :
1 2 3 4 5 6 7 8 9 @interface 类名: 父类名 { 属性1访问权限关键字 属性1类型 属性1; 属性2访问权限关键字 属性2类型 属性2; ... } +/- 函数返回值类型 函数名:(参数1类型) 参数2别称:(参数2类型)参数2 ... @end
访问权限关键字可以是以下内容,但是,OC中其实并没有私有属性、共有属性这些区别的
关键字
说明
@public
对外公开,其子类可以继承和使用,在所有的类中都可以使用
@protected
受保护的,只对其自身和他的子类公开,可以类继承,其他类中不可以使用
@private
私有的,只在本类中可以使用,不能被继承
@package
在本应用程序包中可以使用
需要注意的是,类的成员函数以及类函数的声明,都放在花括号外面,只有类的属性声明放在花括号里面。
示例 :
1 2 3 4 5 6 7 #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface Person : NSObject{ @public NSString *name; @private NSNumber *age; } @end
3.3.2 类的成员属性 类的属性声明也可以放在*.m文件中,这个时候用关键字@property进行声明。
示例,在*.m实现文件中声明类的属性:
1 2 3 4 5 6 7 8 @interface Person() <TGCWeChatLoginDelegate, AppsFlyerLibDelegate> @property (nonatomic) int cardId; @property (nonatomic, strong) RichOXNormalStrategyInstance *strategyInstance; // 通用策略句柄 @end @implementation Person ... @end
3.3.3 成员属性的self.访问和下划线访问 OC中访问类的成员变量,既可以用self.来访问,也可以直接变量名前面加下划线来访问。
但是,禁止用下划线访问类的成员属性
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @interface Person() @property (nonatomic) NSString *name; @property (nonatomic) int cardId; @property (nonatomic, strong) RichOXNormalStrategyInstance *strategyInstance; // 通用策略句柄 @end @implementation Person - (void)testFunc { // self. 访问 self.name = @"Peter"; // 下划线访问成员属性 // 会导致一个很奇葩的bug,具体忘了,所以禁止使用下划线访问成员属性 _name = @"Peter"; } @end
在使用self.访问时,实际上是用默认的getter、setter函数来修改成员属性,同时,成员属性的引用计数也会加1。而使用下划线访问时,不会有这些变化。下划线访问实际是对局部变量的访问,会导致奇奇怪怪的bug。因此,禁止使用下划线访问
3.4 UIView和UIViewController UIView就是OC中负责渲染的一个视图,可以简单理解为一个弹窗。而UIViewController则是它的控制类。
需要注意的是,UIViewController中有属性字段view,可以直接na
3.5 OC其他说明 3.5.1 nil Nil NULL NSNull区别 在底层定义中,nil、 Nil、 NULL都是相同的,都是NULL(空字符),是一个空对象,没有内存地址。因此将这个三个赋值给某一对象后,均会将该对象的内存释放。
一般约定俗成的用法是:
nil:赋值给对象,指向 OC 中对象的空指针
Nil:赋值给类,指向 OC 中类的空指针
Null:赋值给他数据类型,指向其他类型的空指针
那么NULL和NSNull有什么区别呢?
NULL是一个空对象,没有内存地址,对它进行访问会报错;而NSNull是一个内容为空的对象,是一个对象,有内存地址,只是这个对象没有任何内容。
4. JS调用OC 4.1 格式 格式如下:
jsb.reflection.callStaticMethod(类名, 完整函数名, 参数1, 参数2, …);
类名不用带任何路径,就是单纯的类名
完整函数名包含冒号: 及 参数别称
调用的OC函数只能是类函数(即函数名前用加号+)
OC函数,在接受JS的 float、int 等数据类型时,请使用 NSNumber 类型
OC函数,在接受JS的 bool 数据类型时,请使用 BOOL 类型
OC函数,在接受JS的 string 数据类型时,请使用 NSString 类型
4.2 完整示例代码 JS代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 testOC() { if (!cc.sys.isNative) { console.log("---- YinoriZhu testOC debug web call"); return; } if (cc.sys.os == cc.sys.OS_IOS) { console.log("---- YinoriZhu testOC debug ios call"); let result = jsb.reflection.callStaticMethod("JsbService", "showAd:title:", "test oc success", "testOC"); console.log(`----YinoriZhu testOC result: ${result}`); } },
上面的代码中,调用的OC函数,完整声明如下:
1 + (NSString *)showAd:(NSString *)str title:(NSString *)tit;
因为完整的函数名包括冒号: 及 参数别称,因此完整的函数名是showAd:title:。
若是没有参数的函数,自然也就不需要冒号:了。
OC代码:
1 +(NSString *)callNativeWithReturnString;
JS代码:
1 let str = jsb.reflection.callStaticMethod("JsbService", "callNativeUIWithTitle");
5. OC回调JS 5.1 格式 se::ScriptEngine::getInstance()->evalString(可执行的JS代码的C字符串);
这里可执行的JS代码,是指在我们自己的项目中,任何地方插入都不会报错的代码。
例如:
1 2 3 cc.log('YinoriZhu aaaaaaaa'); game.jsb.onTestOC();
这两句,cc.log自然不必说,任何地方都能正常运行;而game.jsb在我们项目中是全局的,那么这个语句自然也不会有任何问题。
完成整个OC回调JS的过程,相对较复杂,需要先将拼接出C++字符串,再转为C字符串,最后调用cocos引擎的函数来完成调用。
为了方便多参数时传递,建议将所有参数合并成一个json对象,再转成json字符串,然后传到JS那边。cocos引擎会自动将json字符串转成json对象,因此在JS的回调函数那边使用很方便。
5.2 封装工具函数 OC调用JS时,需要使用这几个头文件
1 2 3 4 #include "cocos2d.h" #include "platform/CCApplication.h" #include "base/CCScheduler.h" #include "cocos/scripting/js-bindings/jswrapper/SeApi.h"
下面是封装好的,OC回调JS的工具函数,建议加到工具类中直接使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include "cocos2d.h" #include "platform/CCApplication.h" #include "base/CCScheduler.h" #include "cocos/scripting/js-bindings/jswrapper/SeApi.h" /** * oc回调js * @param funcName 完整的函数调用表达式,例:@"game.jsb.onShowAdCB",@"cc.log" * @param funcArgs NSDictionary对象,该对象为函数的参数 */ + (void)ocCallJs:(NSString *)funcName funcArgs:(NSDictionary *)funcArgs { NSLog(@"%@ %@ ocCallJs funcName: %@, funcArgs: %@", LOG_TAG, TAG, funcName, funcArgs.description); // 获取函数调用表达式的C++ string std::string *funcNameCStr = new std::string([funcName UTF8String]); // 获取函数参数的C++ string NSString *funcArgsStr = [self NSDictionary2NSString:funcArgs]; std::string *funcArgsCStr = new std::string([funcArgsStr UTF8String]); // 拼接成完整的函数调用表达式 std::string jsCallStr = cocos2d::StringUtils::format("%s(%s);", funcNameCStr->c_str(), funcArgsCStr->c_str()); printf("---- YinoriZhu JsbService ocCallJs final function express: %s", jsCallStr.c_str()); NSLog(@""); // 在主线程中回调JS cocos2d::Application::getInstance()->getScheduler()->performFunctionInCocosThread([=]() { se::ScriptEngine::getInstance()->evalString(jsCallStr.c_str()); }); }
需要注意的是,在拼接的可执行的JS代码中,作为参数的C++字符串std::string所转成的C字符串c_str()需要用反引号扩住,但是如果参数是NSDictionary所转成的C字符串c_str(),则不需要反引号。
另外因为部分代码是C++代码,可能会导致XCode在编译时报错,找不到头文件,解决方法参考2.4.2小节
5.3 示例代码 下面是OC回调JS的示例代码,其中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 onTestOcCB(data) { console.log(`----YinoriZhu onTestOcCB data: `, JSON.stringify(data)); for (const key in data) { if (Object.hasOwnProperty.call(data, key)) { const value = data[key]; console.log(`----YinoriZhu onTestOcCB data key: ${key}, value: ${value}, type: ${typeof value}`); if ("userInfo" == key) { for (const userKey in value) { if (Object.hasOwnProperty.call(value, userKey)) { const userValue = value[userKey]; console.log(`----YinoriZhu onTestOcCB userInfo key: ${userKey}, value: ${userValue}, type: ${typeof userValue}`); } } } } } }, onTestOcCB2(success, flag, otherFlag, name, userInfo) { console.log(`----YinoriZhu onTestOcCB2 success: ${success}, type: ${typeof success}`); console.log(`----YinoriZhu onTestOcCB2 flag: ${flag}, type: ${typeof flag}`); console.log(`----YinoriZhu onTestOcCB2 otherFlag: ${otherFlag}, type: ${typeof otherFlag}`); console.log(`----YinoriZhu onTestOcCB2 name: ${name}, type: ${typeof name}`); console.log(`----YinoriZhu onTestOcCB2 success: ${JSON.stringify(userInfo)}, type: ${typeof userInfo}`); for (const userKey in userInfo) { if (Object.hasOwnProperty.call(userInfo, userKey)) { const userValue = userInfo[userKey]; console.log(`----YinoriZhu onTestOcCB2 userInfo key: ${userKey}, value: ${userValue}, type: ${typeof userValue}`); } } },
OC代码中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 /** * 测试oc与js互相调用 */ + (NSString *)showAd:(NSString *)str title:(NSString *)tit { NSLog(@"%@ %@ showAd, str: %@, title: %@", LOG_TAG, TAG, str, tit); UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:tit message:str delegate:nil cancelButtonTitle:@"否" otherButtonTitles:@"是", nil]; [alertView show]; // 回调1,OK NSString *name1 = @"ZhangSan"; NSString *funcName1 = @"game.jsb.onTestOcCB"; NSDictionary *funcArgs1 = @{@"success":@1, @"flag":@true, @"otherFlag":@"cb_1", @"name":name1}; [self ocCallJs:funcName1 funcArgs:funcArgs1]; // 回调2,OK NSString *name2 = @"LiSi"; NSString *funcName2 = @"game.jsb.onTestOcCB"; NSDictionary *funcArgs2 = @{@"success":@2, @"flag":@false, @"otherFlag":@"cb_2", @"name":name2}; [self ocCallJs:funcName2 funcArgs:funcArgs2]; // 回调3,OK NSString *name3 = @"WangWu"; NSDictionary *userInfo3 = @{@"name":@"ZhuYi", @"age":@26, @"testBool":@true, @"otherFlag":@"cb_3"}; NSString *funcName3 = @"game.jsb.onTestOcCB"; NSDictionary *funcArgs3 = @{@"success":@3, @"flag":@true, @"otherFlag":@"cb_3", @"name":name3, @"userInfo":userInfo3}; [self ocCallJs:funcName3 funcArgs:funcArgs3]; // 回调4,OK // 获取函数调用表达式的C++ string std::string funcNameCStr4 = "game.jsb.onTestOcCB2"; // 获取函数参数的C++ string int success4 = 4; bool flag4 = true; std::string otherFlag4 = "cb_4"; std::string name4 = "ZhaoLiu"; NSDictionary *userInfo4 = @{@"name":@"ZhuYi", @"age":@26, @"testBool":@true, @"otherFlag":@"cb_4"}; NSString *userInfoStr4 = [self NSDictionary2NSString:userInfo4]; std::string *userInfoCStr4 = new std::string([userInfoStr4 UTF8String]); // 拼接成完整的函数调用表达式 std::string jsCallStr4 = cocos2d::StringUtils::format("%s(%d,%d,\"%s\",\"%s\",%s);", funcNameCStr4.c_str(), success4, flag4, otherFlag4.c_str(), name4.c_str(), userInfoCStr4->c_str()); NSLog(@" "); printf("---- YinoriZhu JsbService onTestOcCB2 final function express: %s", jsCallStr4.c_str()); NSLog(@" "); // 在主线程中回调JS cocos2d::Application::getInstance()->getScheduler()->performFunctionInCocosThread([=]() { se::ScriptEngine::getInstance()->evalString(jsCallStr4.c_str()); }); return @"hehe"; }
注意拼接的可执行的JS代码中,各个参数的引号区别 。
1 2 3 4 5 6 7 // 拼接成完整的函数调用表达式 std::string jsCallStr4 = cocos2d::StringUtils::format("%s(%d,%d,\"%s\",\"%s\",%s);", funcNameCStr4.c_str(), success4, flag4, otherFlag4.c_str(), name4.c_str(), userInfoCStr4->c_str() );
C++字符串 otherFlag4 和 name4,转成C字符串后,仍需要用引号括住,所以使用的\"%s\"来完成拼接,
而userInfoCStr4是NSDictionary 转 NSString,再转成C++字符串,则不需要用引号括住,因此直接使用%s来完成拼接。
5.4 OC函数的返回值 OC函数的返回值, JS是可以直接收到的,并且在OC回调JS之前就能收到。因为OC回调是在主线程中,而return直接在当前线程中就完成了。因此若是简单数据的获取,直接用return更加方便快捷。
目前返回值仅支持 int、float、bool、string,其余的类型暂时不支持。
6. 终端设置代理 终端默认是不会使用网络代理的,即使代理工具设置全局代理,终端依旧不使用网络代理。在终端中输入下列指令,即可使当前的终端 使用网络代理,同时开着的其他终端依然不会使用网络代理。其中1080为mac默认的代理端口号,具体端口号见代理工具的配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // 设置代理 export all_proxy=socks5://127.0.0.1:1080; // 取消代理 unset all_proxy // 设置git代理 git config --global http.proxy 'socks5://127.0.0.1:1080' git config --global https.proxy 'socks5://127.0.0.1:1080' git config --global https.proxy http://127.0.0.1:1080' git config --global https.proxy https://127.0.0.1:1080' // 取消git代理 git config —-global -—unset http.proxy git config —-global —-unset https.proxy // curl url链接:ping指定的url链接 curl https://www.baidu.com/
7. GameCenter相关 首先需要在创建GameCenter
https://blog.csdn.net/keyoubeike/article/details/77718180
https://www.jianshu.com/p/8eb14edca8fb
https://www.jianshu.com/p/629c01646bac
https://blog.csdn.net/shenjie12345678/article/details/45025403/
https://blog.csdn.net/hekejun19861107/article/details/24037121/
https://www.jianshu.com/p/448ff66c8914
https://www.jianshu.com/p/8eb14edca8fb
https://blog.csdn.net/keyoubeike/article/details/77718180
8. apple developer 全流程 info.plist里Location Always Usage 和Location When In Use Usage是两个权限,不可混淆使用
图片加密替换的CCImage.cpp和CCImage.h直接放在 build-templates/jsb-link/frameworks/cocos2d-x/cocos/platform 路径下即可
构建完成后,cd到encode.py路径下,然后运行python3 encode.py即可。build/jsb-link/assets/resources/native文件夹下的png就是我们项目的图片资源,加密前图片正常显示,加密后无法直接打开png,而游戏中显示正常,则加密成功。
问题1: No accounts with App Store Connect access have been found for the team ‘XXX’. App Store Connect access is required for App Store Connect distribution.
解决:
rm -fr ~/Library/Developer/Xcode/DerivedData/