最近做的一个外包项目要提交到应用市场了,但是他的开发者账户是个人账户,无法添加其他成员;同时我又不能要求他修改自己的Apple ID密码,然后发给我,这太不安全了。因为无法在XCode里面添加开发者账户,所以就不能使用XCode内置的工具上传应用了。对于这种情况,只能使用Apple提供的ApplicationLoader来进行应用发布,但是如何创建一个正确签名、可发布的应用包就成了很大的问题。
根据开发者文档来看,ApplicationLoader只接受ipa(iOS)、pkg(OSX与IAP)还有zip文件,因为我做的是Mac应用,所以就要想办法创建一个可用的pkg包。
经过一些搜索与尝试,最终还是成功把应用包提交了,下面就把过程总结一下。
生成app文件 具体如何archive,如何校对设置我就不详细说明了,如果有需要的话请自行google “App Distribution Guide”,值得一提的是,因为我们在XCode中没有证书与签名,所以在导出app的时候,只能选择最后一项(”Export as a Mac Application”)。
所需证书 创建pkg需要两步签名,首先要对刚刚生成的”.app”签名,这里需要用到”Mac App Distribution”这个证书;接下来还要为生成的安装包签名,这里要用的是”Mac Installer Distribution”这个证书。
生成签名请求 虽然不能直接访问开发者账户,但是要将应用提交到应用市场一定要有开发者签名,这就需要有账户的人配合了。首先你要做好准备工作,或者说写申请。这里说的申请就是在本地创建一个签名请求。打开Keychain Access工具,按照下图选择:
因为要生成两个证书,而且根据我个人的测试,证书跟签名请求是一一对应的,所以在这一步需要创建两个签名请求文件,建议用”AppCertificateSigningRequest.certSigningRequest”跟”InstallerCertificateSigningRequest.certSigningRequest”来命名,其他的能区分的命名方式都可以。
指导他人生成证书 接下来就要用这个签名去请求证书了,具体的过程是:
访问开发者网站,登陆Member Center。 在Mac应用页面中,选择”Certificates, Identifiers & Profiles”中的”Certificates”那一项。 点击右上角的”+“按钮,创建新证书。 在”Production”分类中选择”Mac App Store”。 在下一步页面中,选择”Mac App Distribution”。 在下一步页面中,选择”AppCertificateSigningRequest.certSigningRequest”文件。 点击”Generate”来生成证书(证书文件名默认为”mac_app.cer”)。 重复1-7步,在第5步选择”Mac Installer Distribution”,在第六步中上传”InstallerCertificateSigningRequest.certSigningRequest”文件。这里第7步生成的证书的默认文件名是”mac_installer.cer”。 导入证书 分别双击导入”mac_app.cer”与”mac_installer.cer”,导入的时候选择“login”来导入当前用户的钥匙链中。导入之后最好通过名字过滤搜索,确认导入成功。
应用签名 有了证书之后就可以对之前生成的”.app”文件签名了。命令如下:
codesign -f -s "3rd Party Mac Developer Application: XXX" --entitlements “YYY.entitlements” "ZZZ.app"
其中”3rd Party Mac Developer Application: XXX”就是证书中的那个名字,”YYY.entitlements”就是应用对应的entitlements的路径,”ZZZ.app”就是之前生成的app文件。
安装包签名 接着使用productbuild工具来生成安装包,命令如下:
最近有机会跟郭总合作开发一些项目,真是每天都有新姿势啊,不记录下来都觉得可惜。
起因 先来描述一下遇到的问题:我们的项目要用到OpenCV,如果是为iOS开发的话,直接编译生成opencv2.framework就可以了,iOS默认将framework静态编译到最终的二进制中,但是在Mac上没有现成的framework framework不会静态编译,仍然要复制到最终的bundle中,这就可能带来问题。
我是用brew install opencv安装的opencv,没有其他特殊指令,最终是在/usr/local/Cellar/opencv/2.4.9/lib下生成了一堆dylib文件,然后把这些文件放到项目里面,同时选择复制到最终的bundle中。但是这样产生的.app在运行时会有一些路径相关的错误(一般是image not found什么的),直接就崩了。
这样我们就只能选择用静态链接库了。如果是我来解决这个问题的话,我一定会想办法安装一个生成.a版本的opencv,很有可能会自己编译源代码。
如果你比较急着用的话,可以直接下载opencv_osx.a.zip。
郭总说 让我们看看郭总是怎么搞的:
最后是去这下了个 iOS 的 framework 把里面二进制拿出来 去掉了arm http://sourceforge.net/projects/opencvlibrary/files/opencv-ios/
反正要给 iOS 模拟器就有 x64 i386
framework里面有个核心的二进制 加个.a后缀 就是 static lib 直接拿过来就行了
我嫌大 用lipo拆开 去掉了arm的重合了一个
一共只有3行操作
喂喂,略显高端了吧!
这里的要点一方面是有的iOS framework因为要支持iOS模拟器,所以会在核心二进制里面包含x64和i386可用的static lib,我们如果有需要可以利用这些生成好的二进制;
另外一方面就是lipo,如果你跟我一样从来没见过这个命令,赶快去man lipo一下吧!
实战 接下来我们来实战一下,首先从郭总提供的url里面下载好这个framework,找到这个核心的二进制:
接下来先看一下这个二进制包含了哪些arch:
➜ voidmain@MBP ~/Desktop lipo -info opencv2 Architectures in the fat file: opencv2 are: armv7 armv7s i386 x86_64 arm64 正如郭总所说,这里面除了给iOS准备的arm系列外,还包含着i386跟x86_64这两个mac上可用的framework,然后我们就需要剔除arm系列,给这个二进制瘦身一下:
➜ voidmain@MBP ~/Desktop lipo opencv2 -extract i386 -extract x86_64 -output opencv2_osx.
这可能是所有用NSImage的开发者都会遇到的一个坑:为什么我的图像用NSImage打开之后变小了?
比如下面这个图:
它的分辨率是2848x4288,但是,如果我用下面的代码打印出来的话,大小却只有854.4x1286.4。
NSImage *srcImage = [[NSImage alloc] initWithContentsOfURL:url]; NSLog(@"before: %@", NSStringFromSize(srcImage.size)); 这段代码看起来已经简洁的不能再简洁了吧,应该没有问题才对啊。但是实际问题出现在DPI这里。NSImage的size计算是按照DPI为72的值计算的,做个简单的实验,如果用Photoshop打开刚刚这幅图,然后用Image Size工具将DPI调成72(保持各种比例关系不变),就能看到这个854.4怎么来的了:
其实要解决这个问题并不是特别困难。尽管NSImage在计算尺寸的时候是按72dpi来计算的,但是NSImage的内部表示NSBitmapImageRep还是有字段保留着图像的实际尺寸,分别是pixelsWide跟pixelsHigh。因此只要利用这两个实际大小来计算,或者干脆中心绘制一下这个NSImage就可以了。核心代码如下:
- (NSBitmapImageRep *)bitmapImageRepresentation { // NSImage可能包含很多representation,需要迭代一下 NSArray * imageReps = [self representations]; float width = 0; float height = 0; for (NSImageRep * imageRep in imageReps) { // 利用pixelsWide跟pixelsHigh来获得实际图像分辨率 if ([imageRep pixelsWide] > width) width = [imageRep pixelsWide]; if ([imageRep pixelsHigh] > height) height = [imageRep pixelsHigh]; } if(width < 1 || height < 1) return nil; // 重新绘制 NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: NULL pixelsWide: width pixelsHigh: height bitsPerSample: 8 samplesPerPixel: 4 hasAlpha: YES isPlanar: NO colorSpaceName: NSDeviceRGBColorSpace bytesPerRow: 0 bitsPerPixel: 0]; NSGraphicsContext *ctx = [NSGraphicsContext graphicsContextWithBitmapImageRep: rep]; [NSGraphicsContext saveGraphicsState]; [NSGraphicsContext setCurrentContext: ctx]; // 实际绘制代码,把全部图像(fromRect: NSZeroRect)画到全尺寸的矩形中(drawInRect:NSMakeRect(0, 0, width, height)) [self drawInRect:NSMakeRect(0, 0, width, height) fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.
最近想用Swift开发一些小玩具,其中一个应用需要用到Box2d这个物理引擎,所以就遇到了如何将C++代码与Swift代码整合的问题。
在项目中整合Box2d并不困难,可以直接在Podfile里面添加pod 'box2d',比较麻烦的是怎么在代码中使用。
在WWDC的Session 406: Integrating Swift with Objective-C中,Apple只是介绍了怎么将Swift代码跟Objective-C代码做整合,但是没有提C++,后来在官方文档中看到了这样一段话:
You cannot import C++ code directly into Swift. Instead, create an Objective-C or C wrapper for C++ code.
这就很简单了,首先我们需要创建一个ObjC的类,用类创建向导很容易就能完成这个工作:
在创建过程中Xcode会提示是否需要创建bridge,选择创建就好了。
接下来就可以编辑XXXX-Bridging-Header.h这个文件了,根据我的需要,这里应该#import <Box2d/Box2d.h>,所以我就直接把这句话放到bridging header里面了,编译,BOOM!
<unknown>:0: error: /path/to/project/Pods/Headers/Box2D/Common/b2Settings.h:22: ‘cassert’ file not found
如果google这个问题的话,可以看到各种答案都是说应该把.m文件替换成.mm文件,但是我现在压根没用上我刚刚创建的VMBox2dWrapper.m,这就是问题所在。这里需要做2个修改,一个是把VMBox2dWrapper.m的后缀替换成.mm,另外一个是把#import <Box2d/Box2d.h>移动到这个.mm文件里面,而XXXX-Bridging-Header.h这个文件里面#import "VMBox2dWrapper.h"。经过这两个改动以后,就可以顺利编译了。
还有一个问题是这个Wrapper类里面写什么,基本就是看项目需要用到什么再添加什么方法了,因为我的项目才刚刚开始,如果后续有什么需要注意的地方再来添加。
序言 Apple新推出的Swift编程语言无疑会成为最近码农研究的热点,现在官方有一本官方Guide,这本书已经有国内的开发者开始翻译了(#该来的总会来的#)。
当然只看完Swift的语法还不足以进行Cocoa应用开发,用郭总的话说,语言并不会带来新的起跑线,要想开发优秀的Cocoa应用还是需要对Cocoa这个框架的深入理解。这就跟用RubyMotion仍然要学习Cocoa是一个道理,万变不离其宗。这方面就可以参考Using Swift with Cocoa and Objective-C这篇文档。
另外,Swift刚刚推出一天就已经有开发者用它开发了FlappyBird,可见大家对这门语言的热情。这个系列的博客主要是记录我在学习Swift语言过程中感觉有趣的语法、用法,并不会对所有语法都逐一进行分析,而是举几个简单的例子帮我自己来理解Swift这门语言。如果这些例子能帮助你对Swift这门语言建立一些基本的认识或者感觉,那就再好不过了!
PS:博客内容的记录顺序大部分是根据官方Guide来的,因为这是我的阅读顺序,在阅读过程中可能会根据我的理解跳跃穿插一些内容。另外因为我也是在逐渐阅读文档,所以可能写过的内容会有多次修订。
环境要求 要使用Swift必须要安装Xcode 6 Beta版,安装Xcode6不需要10.10,在10.9上就可以使用。
代码 本文中使用道德测试代码都可以在SwiftWithCocoa这个repo中找到,大部分代码都在MyPlayground中。
“变量”不变 第一次接触这个概念是在了解Scala的时候,Scala中有两个关键字,分别是val和var,用var声明的变量跟其他语言中的一样,可以改变值,但是val声明的变量,一旦第一次赋值之后就无法改变了,也就是所谓的“变量”不变。这种类型主要应用在多线程的场景中,可以有效的避免资源抢占,死锁等情况的发生,从语言级保证了代码的稳定性和执行效率。
Swift也提供了类似的声明方法,分别是let和var,用let声明的是常量,用var声明的是变量。
基本数据类型 Tuple Tuple应该是我从python、ruby转到OC之后感觉最需要的类型。Tuple最大的贡献在于能轻便的创建一些临时对象,并在不同的领域使用。比如函数返回的时候可以利用tuple便捷的返回多个值,这是现在很多流行语言都支持的。
// Returning from func func response() -> (Int, String) { return (404, "Not Found") } var code: Int, description: String (code, description) = response() // code == 404 // description == "Not Found" 我记得我最开始学python,交换变量的方法真是让我震惊了,Swift里面(因为支持了tuple,所以)也有类似的方法了:
// Swapping vars var first = 1 var second = 2 // Change 2 to "ASDF" and see the error (first, second) = (second, first) // first == 2 // second == 1 需要注意的是,因为Swift强调的是类型安全,所以上面例子中的first和second必须要是同样的类型才能交换,如果类型不同需要进行显式的类型转换,下面会进行讨论。
FML。。花了一上午调了一个bug。。
事情是这样的,我正在写的这个mac应用用到了Core Data,所以就把Core Data的文件放到了~/Library/Container/my.app.container这个目录下。但是在开发的过程中entity的结构总会发生变化,在基本稳定之前我也不想写升级那些,所以就偷懒*把container目录给删了*。
上午在用NSUserDefaults保存用户的选项的时候,当前保存成功,调用[[NSUserDefaults standardUserDefaults] synchronize]也返回YES,但是就是重启应用之后保存的内容就消失了。去~/Library/Preferences目录下找也确实没有对应的文件。
调了一上午,尝试了各种解决方案,也没搞定,最后终于在这个SO问题里面找到了答案。
关键是answer下面的第一个comment:
Also if you move the container while testing / debugging to the trash, the cfprefsd (see Activity Monitor) still keeps a link to the .plist. Empty the trash and force quit both cfprefsd (user and root).
用ps一看果然有2个cfprefsd,有一个应该就是之前删除container的时候留下的,把它kill了,然后重试就好了!
感谢 @mahal,真是救了我一命!