换了台Mac后无法登录Appstore,无论是在Xcode,iTunes还是App Store软件都无法登录,出现如下提示:

Can’s sign in to Appstore - This action could not be completed

在官网论坛找到问题所在,是共享文件夹权限出了问题,因为我是使用了一个新创建的管理员账号登陆的。在终端输入以下命令能解决问题:

1
2
3
sudo mkdir -p /Users/Shared
sudo chown root:wheel /Users/Shared
sudo chmod -R 1777 /Users/Shared

在做Vuforia-iOS-Lib的Demo程序时,发现库中的Controller在MRR工程中会得不到释放。我看了Apple官网的“Advanced Memory Management Programming Guide”,收获不小,做点笔记。

思考角度

内存管理应该从对象所有权的角度思考,而不应该从引用计数的角度,去过分强调实现细节。

图片来自官方文档

基本原则

  1. 你拥有任何你创建的对象。
    • 使用 “alloc”, “new”, “copy”或者“mutableCopy” 创建的对象。
  2. 可以利用retain获取对象的所有权。
    • 存取方法init中获取对象所有权作为自身属性;
    • 为避免其他操作将对象销毁而造成问题;
  3. 当不需要对象时,必须释放对其所有权。
    • 利用“release” 或 “autorelease”
  4. 必须释放不该拥有的对象的所有权。

其他原则

  1. 你不拥有以引用形式返回的对象。
    • 比如 ClassName **id *
  2. 存取方法来管理内存。

    • 用来赋值给属性;
    • 不在initdealloc中使用存取方法
  3. 用弱引用来避免循环引用

  4. 不用dealloc管理稀缺资源。
    • 不应该在dealloc中管理文件描述符网络连接缓存等。
    • 因为程序的bug或突然中断会导致dealloc不能正常调用;
    • 因为对象图的析构顺序不确定。

之前用Vuforia做了一些Demo,主要是用Unity实现的。现在有需求要将Vuforia集成到已有的iOS客户端中。事实上Vuforia提供了iOS上的SDK,不过要让客户端同事去集成并实现需求的功能还是有些麻烦的,于是我在官方提供的iOS SDK上又封装了一次,加入了先前做的模型导入和交互的相关工作(可以参考Application3D),形成了更加简单的可配置的静态库。

主要功能

  1. 方便地实现基于Vuforia的ImageTarget识别;
  2. 支持Obj格式模型导入;
  3. 模型的简单交互(旋转,缩放,平移);
  4. 支持模型和识别datasets的配置;

使用步骤

  1. 下载或克隆Vuforia-iOS-Lib
  2. 配置依赖库
    • 去Vuforia官方下载iOS SDK,并解压缩到external-deps文件夹下;
  3. 运行ARDemo工程的createARLib target
    在项目文件夹下会出现新文件夹output,里面包含新的静态库资源
    主要包含:
    • ARResources.bundle : 包含绘制用的glsl文件;
    • include : 包含头文件;
    • libAR.a : 新封装的静态库;

生成的资源

  1. 将以上三个资源添加到工程;
  2. 配置相关参数初始化ARViewController
    配置:
    {
     AR_CONFIG_INIT_FLAG : <Vuforia License Key>,
    AR_CONFIG_DATA_SETS : [<数据集>...],
    AR_CONFIG_MODEL : [<模型>...]
    
    }
    数据集:
    {
    AR_CONFIG_DATASET_NAME : <数据集名称>,
    AR_CONFIG_DATASET_PATH : <数据集所在路径>
    
    }
    模型:
    {
    AR_CONFIG_TARGET_NAME    : <识别体名称>,
    AR_CONFIG_MODEL_PATH    : <模型路径>
    
    }

通过设置ARViewController.activeDataSetName可以激活载入的数据集。

  1. AppDelegate.mapplicationDidEnterBackground中释放GL资源
1
- (void)applicationDidEnterBackground:(UIApplication *)application {
    if (self.glResourceHandler) {
        // Delete OpenGL resources (e.g. framebuffer) of the SampleApp AR View
        [self.glResourceHandler freeOpenGLESResources];
        [self.glResourceHandler finishOpenGLESCommands];
    }
}

具体的配置可以参考TestARLib工程

效果图

效果图

问题

重新封装画板内核库后,所有的混编文件(.mm)都被打包到了库中,外面应用层代码全部都是OC文件(.m)。这时候编译工程会出现如下连接错误:

Undefined symbols for architecture arm64:
“vtable for cxxabiv1::vmi_class_type_info”, referenced from:

NOTE: a missing vtable usually means the first non-inline virtual member function has no definition.
“std::1::basic_string<char, std::1::char_traits, std::1::allocator >::assign(char const*)”, referenced from:

“_
cxa_pure_virtual”, referenced from:

“std::1::basic_string<char, std::1::char_traits, std::1::allocator >::operator=(std::1::basic_string, std::1::allocator > const&)”, referenced from:

“std::
1::vector_base_common::throw_out_of_range() const”, referenced from:

“std::1::basic_string<char, std::1::char_traits, std::1::allocator >::basic_string(std::1::basic_string, std::1::allocator > const&)”, referenced from:

“std::terminate()”, referenced from:

“_
cxa_guard_release”, referenced from:

_cxa_guard_abort”, referenced from:

“std::
1::basic_string, std::1::allocator >::init(char const*, unsigned long)”, referenced from:

“std::1::basic_string<char, std::1::char_traits, std::1::allocator >::reserve(unsigned long)”, referenced from:

“_
gxx_personality_v0”, referenced from:


ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

探索

通过网上搜索到如下解决方案

  1. 将Build Settings->Apple LLVM7.1-Language中的Compile Sources As修改为According To File Tyle
  2. 将Build Settings->Apple LLVM7.1-Language中的Compile Sources As修改为Objective-C++

以上第二个方法对我不适用,修改为全部按Objective-C++方式编译源文件会出问题。第一个解决方案的话也有问题,由于项目中没有了混编文件(.mm),那么Xcode根本不会调用Objective-C++编译器,所以连接问题还是存在。

解决

将项目中的某个OC文件(.m)强行改为后缀为mm,使其成为混编文件。
这种方法也适用于Swift工程,如果Swift也要引用C++的混编库,那么可以增加一个空的混编文件(.mm),该文件不用实现,只是为了欺骗IDE,调出相应编译器完成连接。

原先的封装

先前开发了一个C++的画板库,将其封装后提供给各移动平台使用。原来的设计如下图所示。封装的库中只含有C++代码,这样能保证该库可以被各个平台使用。可这种封装暴露的内部元素太多,需要在应用层引入HYBrushCore,CanvasView以及PaintingManager等中间层代码才能让库被方便实用。
原先的封装

中间层各类

  • HYBrushCore:管理内核库中各C++组件,为应用层提供更换笔触/调整颜色/调整大小等功能;
  • CanvasView: 继承于平台相关的视图,接收交互手势,并调用内核库函数进行绘制;
  • PaintingManager: 管理绘画作品,提供存储/加载/删除作品等功能;

新的封装

新的封装直接将中间层代码封装到库内,这样虽然针对不同的平台需要封装不同的库,但暴露给应用层的接口简单了,方便使用。
为了统一接口,PaintingManager的功能拆分到了应用层和内核中

  • 存储/加载/删除作品等文件操作相关接口并入到HYBrushCore,放进内核中;
  • 对作品的数量/顺序等管理放到了应用层的相应Controller

公司的邮件服务器空间太小,需要经常清除服务器上的旧邮件才能正常使用。由于时不时会查询以前的邮件,我一般会在本地保留所有邮件,只对邮件服务器进行清除。这个用其它一些邮件客户端可以做到,不过我习惯了Mac自带的Mail,摸索了一下才找到方法。

1. 备份邮件

  • 新建邮箱,位置选在 On My Mac。名称自己写,如果是用来备份收件箱中的邮件,可以命名为本地收件箱
  • 点击邮箱中的收件箱,选择需要备份的邮件,右击鼠标,选择拷贝到->本地收件箱

2. 释放服务器空间

  • 点击邮箱中的收件箱,选择需要删除的邮件,右击鼠标,选择删除
  • 右击邮箱中的收件箱,选择清除已删除项目,来彻底删除邮件。(也可以在废纸篓中选择相应的帐户,来执行清除已删除项目操作)

经过上面两步操作后,Mail程序会自动同步邮件服务器,删除旧邮件来释放空间;同时Mail中保存了备份。

场景

程序只有一个简单的ViewController,要求启动后只横版显示,不能旋转。我设置了一下Info.plist中的支持方向后,在ViewController.m中加入如下代码:

1
- (BOOL)shouldAutorotate {
    return NO;
}

结果shouldAutorotate不调用,程序该旋转还是旋转。

解决

这种问题一般出在粗心,或者是iOS 9的分屏应用中。我的问题在于没有针对设备设置好Info.plist。只限制了Universal的方向,在iPad设备中却允许所有方向。

可以采用以下两种方式之一解决:

  1. 正确设置设备的允许方向:TARGETS -> General -> Deployment InfoDevices 设置为iPad, 再将 Device Orientation下勾选 Landscape Left 和 Landscape Right。

    我先前在Devices设置为Universal时限制了方向,而Devices为iPad时,每个方向都是被勾选的。这个时候就允许了所有方向,shouldAutorotate函数在没有禁止分屏的情况下是不会被调用的。

  2. 设置全屏显示(禁止分屏):TARGETS -> General -> Deployment InfoRequires full screen 勾选。

    苹果论坛中说,当开发分屏应用时也就放弃了对屏幕旋转的控制。这里虽然不是分屏应用,但显性要求全屏显示,可以让就算在支持所有方向的情况下,shouldAutorotate函数也会被调用。

参考

网上用Android Studio调试NDK的资料不多且分散。我将这几天踩过的坑及经验纪录一下,方便其它朋友。我将从导入一个NDK Samples中的项目开始。

演示代码放在GitHub上,每个坑为一次Submit。

开发环境

  • 操作系统: Mac OS 10.11.1
  • IDE: Android Studio 1.5.1
  • NDK: android-ndk-r10e

Native链接其它库

加载NDK Samples中的hello-gl2项目后,直接运行会出现如下错误:

Error:(39) undefined reference to `glGetError'
Error:(41) undefined reference to `__android_log_print'
...

build.gradle文件中添加命令来链接相关库,具体可见Mac下用Android Studio运行NDK samples

无法进入Native代码

解决库链接问题后,程序能运行正确运行在设备上,可无法中断在Native代码上的断点处。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
ndk {
debuggable = true
}

}
debug {
debuggable = true
jniDebuggable = true
}

}
  • 移除项目下的jniLibs文件夹

目测这个不移除,默认就会调用里面的代码,而不走新编译的Native库。

方法总结

  1. 修改build.gradle中的*buildTypes,使app-native可调试
  2. 移除项目中的jniLibs文件夹

采用实验版插件调试

更简单的方法是,按照Experimental Plugin User Guide介绍,在Android Studio中可以采用实验版插件进行调试。最快捷的方式是直接从Google的GitHub上check最新的代码即可。

Native引用STL库

gl_code.cpp中添加如下代码:

1
2
#include <string>
using namespace std;

运行程序,报出错误string: No such file or directory。解决的方法是:修改build.gradle中的defaultConfig{..}如下:
修改配置

P.S. build.gradle配置修改可参考

注意:
如果按照以上方法配置,将忽略项目中已存在的Android.mk文件而自动生成一个位于build文件夹下的Android.mk。Android Studio默认编译jni文件夹下东西,且项目中的Application.mk文件配置将失效。上图链接STL库设置编译选项Application.mk中相应的配置选如下:

1
2
APP_STL := gnustl_static
APP_CPPFLAGS := -std=c++11

示例代码

ndk-debugging

打算将先前写的一个OBJ模型查看工具objViewer应用在Android平台上,借此了解一下Android平台的开发。现在做到了运行 NDK samples,将这中间遇到的问题记录一下。

Android环境

  • 系统: Mac OS 10.11.1
  • JDK: java version “1.8.0_65”
  • IDE: Android Studio 1.5.1
  • Android SDK: r24.4.1

JDK是系统自带的,其他的是官网下载的。按照说明一步步安装就是,被墙困住的可以百度一下,教程很多。我将下载的包放在了百度云上,下载请点击

Android开发所需安装包

NDK

配置

下载安装说明还是首推官网,不过很可惜还是被墙。NDK下载可以从上面的云盘中获得,中文指导可以看下面的两个链接:

可能遇到的问题

按上面配置说的运行例子,可能会遇到一些问题,下面按照我遇到的顺序说一下:

  • NDK插件错误

    NDK插件错误
    解决方法: 在项目根目录下创建配置文件gradle.properties,里面加入
1
android.useDeprecatedNdk=true

然后点击下图所示按钮,重新启动Gradle同步即可。
同步gradle按钮

  • NDK位置未指定

    解决方法: 重新指定NDK位置,方法有二
    1. 点击错误提示中的Select NDK,通过对话框选择
    2. 在根目录的local.properties文件中添加
1
ndk.dir=/Users/charlyzhang/AndroidStudioProjects/android-ndk-r10e (NDK位置)
  • 连接不上本地库

    这个问题发生在例子hello-gl2中,Gradle同步没有问题,可连机编译时出现如下错误:
    连接不上本地库

显然是没有连接上Native Lib。
网上有人说:

  • Android.mk中添加以下代码
1
LOCAL_LDLIBS    := -llog -lGLESv2

没有解决我的问题,官方例子中已做好了这一步,问题依然在。

后来找到两个可行的解决方法,两个都是修改app文件夹下的build.gradle文件:

  1. android{…}中的defaultConfig {…}修改,添加如下连接信息:
    连接本地库解决方法1

  2. 编译JNI本地库,再在android{…}中添加如下本地库重定位信息:
    重定位本地库

编译JNI本地库也可以采用两种方式:

  • 手动编译:命令行进入app/src/main/jni文件夹,输入ndk-build命令;(这与导入例子后在根目录用ndk-build命令构建工程不是一步
  • 自动编译:在build.gradle文件的最后,android{…}以外添加如下代码:
1
2
3
4
5
6
7
task ndkBuild(type: Exec) {
commandLine 'ndk-build', '-C',file('src/main/jni').absolutePath
}

tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn ndkBuild
}

参考

之前写的一个iOS应用内购买模块的开发有很多地方需要考虑优化,其中最主要的是App意外结束网络连接断开。应用内购买比较缓慢,再加上购买后需要请求Server更新数据库以及Server进行二次验证,整个过程时间比较长,对于可能发生的意外需要慎重考虑。

接下来以Web调用IAP模块进行虚拟币购买为例说明

处理概览

整个处理过程可分为下图几部分,其中主要关注的是:

  • Client向App Store交易虚拟币购买
  • Client请求Server更新余额
    虚拟币充值总流程图

App意外结束

  • Client向App Store交易虚拟币购买
    这个阶段主要通过Apple提供的StoreKit框架来处理,交易从addPayment开始,至finishTransaction结束。需要注意的是在结束之前,payment一直处于队列中,并且监听器的代理函数`updatedTransactions`会被反复调用。这样就方便我们处理这个阶段中App意外结束的情况了,只要:

    1. finishTransaction之前处理IAP交易成功后的后续工作(更新Server余额等);

      • 待后续工作启动后才会将交易从队列中移除,这能保证后续工作总能启动,可也会带来App重启时重复启动后续工作的问题,这个可以用后面讨论的重传机制规避。
      1
      /// send request to Server
      ...
      
      [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
2. 在App启动之时就添加交易监听器(`SKPaymentTransactionObserver`)
    - 我将添加交易监听器的处理移到了AppDelegate`didFinishLaunchingWithOptions`函数中,使得App启动后就能处理先前意外结束留在队列中的交易。
    - 这里推荐一篇好文[In-App Purchase Best Practices](https://developer.apple.com/library/ios/technotes/tn2387/_index.html),这*Best*可是Apple官方说的。[中文翻译在此](http://mobile.51cto.com/iphone-448610.htm)
    - 为了保证App在重启时能处理意外结束的交易,我重构了代码:

重构前交易处理的依赖
之前的交易处理中,监听器IAPObserver通过Block调用IAPViewController来做IAP交易完成后的处理工作,它们之间是强依赖关系。
这样的问题是:重启时IAPViewController还没有创建,此时监听到的未完成交易无法启动后续处理工作。
考虑到:

1. 后续处理工作主要是与Server的交互,可以独立成新的类;
2. 采用`NSNotification`的方式可以弱化耦合,也可以实现一对多的关联

于是我将代码改成下面的样子:
重构后交易处理的依赖
更新Server的工作封装成单例ServerManager,和IAPViewController一样,都接收来自IAPObserver的消息,完成IAP交易成功后的工作。同时ServerManager会发送通知给IAPViewController,告知工作完成情况,由其来展示UI和通知调用者;当IAPViewController没有创建时,并不会影响Client请求Server更新的操作。

  • Client请求Server更新余额
    这个阶段由Client向Server发更新余额请求。为了应对可能出现的App结束,可以设立重传机制,具体来说:
    1. 先将请求数据本地化存储,再发起请求;
    2. 只有服务器正确返回且返回的是更新成功/更新重复/二次验证失败等情况时才移除本地存储的请求数据;
    3. App启动发起虚拟币充值网络连接恢复 时检查是否存在本地请求数据,如果存在则再次请求。

设立重传机制还可以应对网络连接断开出现的问题。

网络连接断开

网络连接断开处理首要是保证交易的一致性,即请求App Store购买虚拟币成功后也要成功更新Server虚拟币信息。Client分以下几个阶段考虑:

  1. 虚拟币充值发起前
    • 不作特别处理
  2. 与App Store进行虚拟币购买
    • 不作特别处理
  3. 发送更新虚拟币余额请求到Server前
    • 本地存储相关信息,下次再请求,确定成功更新Server的余额后删除
  4. 收到Server的返回信息前
    • 本地存储相关信息,下次再请求,确定成功更新Server的余额后删除

详细处理流程如下图:
Client进行虚拟币充值的处理

至于Server与App Store进行二次验证时发生的网络断开,这里不讨论。

Demo

GitHub上的代码库,有处理不当的地方,欢迎提Issues。