i-chou's tech blog

Android iOS

iOS 7:隐藏的特性和解决之道

| Comments

当 iOS7 刚发布的时候,全世界的苹果开发人员都立马尝试着去编译他们的app,接着再花上数月的时间来修复任何出现的故障,甚至重建这个app。这样的结果,使得人们根本无暇去探究 iOS7 所带来的新思潮。一些明显而细微的更新,比如说[NSArray firstObject],这个方法可追溯到 iOS4 时代,现在被提为公有API,除此之外,还有很多隐藏的特性等着我们去挖掘。

平滑淡入淡出动画

我这里要讨论的并非新的弹性动画APIs 或者 UIDynamics,而是一些更细微的东西。CALayer增加了两个新方法:allowsGroupOpacityallowsEdgeAntialiasing。现在,组不透明度(group opacity)不再是什么新鲜的东西了。iOS会多次使用存在于 Info.plist 中的键UIViewGroupOpacity并可在应用程序范围内启用或禁用它。对于大多数apps而言,这(译注:启用)并非所期望的,因为它会降低整体性能。在 iOS7 中,用 SDK7 所链接的程序,这项属性默认是启用的。当它被启用时,一些动画将会变得不流畅,它也可以在layer层上被控制。

一个有趣的细节,如果allowsGroupOpacity启用的话,_UIBackdropView(在UIToolbar或者UIPopoverView中的背景视图)不能对其模糊进行动画处理,所以当你做一个alpha转换时,你可能会临时禁用这项属性。因为这会降低动画体验,你可以回退到旧的方式然后在动画期间临时启用shouldRasterize。别忘了设置适当的rasterizationScale,否则在retina的设备上这些视图会成锯齿状。

如果你想要复制的 Safari 显示所有选项卡时的动画,那么边缘抗锯齿属性将变得非常有用。

阻塞动画

一个小但非常有用的新方法[UIView performWithoutAnimation:]。它是一个简单的封装,先检查动画当前是否启用,然后禁止动画,执行块语句,最后重新启用动画。一个需要说明的地方是,它并不会阻塞基于 CoreAnimation 的动画。因此,不用急于将你的方法调用从:

[CATransaction begin];
[CATransaction setDisableActions:YES];
view.frame = CGRectMake(...);
[CATransaction commit];

替换为:

[UIView performWithoutAnimation:^{
    view.frame = CGRectMake(...);
}];

但是,绝大多数情况下这样也能工作的很好,只要你不直接处理CALayers。

iOS7 中,我有很多代码路径(主要是 UITableViewCells)需要额外的保护,防止意外的动画,例如,如果一个弹窗的大小调整了,那么同时显示中的表视图将因为高度的变化而加载新的cell。我通常的做法是将整个 layoutSubviews 的代码包扎到一个动画块中:

- (void)layoutSubviews 
{
    // Otherwise the popover animation could leak into our cells on iOS 7 legacy mode.
    [UIView performWithoutAnimation:^{
        [super layoutSubviews];
        _renderView.frame = self.bounds;
    }];
}

处理长表视图

UITableView 非常快速高效,除非你开始使用tableView:heightForRowAtIndexPath:,它会开始为你表中任意元素调用此方法,即便没有可视对象,就比如其内在的UIScrollView只是去获取正确的contentSize。此前有一些变通方法,但都不好用。iOS7 中,苹果公司终于承认这一问题,并添加tableView:estimatedHeightForRowAtIndexPath:,这个方法延迟了实际滚动时间成本的大部分。如果你不知道一个cell的大小,返回UITableViewAutomaticDimension即可。

对于节头/尾(section headers/footers),现在也有类似的API了。

UISearchDisplayController

苹果的 search controller 使用了新的技巧来简化移动 search bar 到 navigation bar 的过程。启用 displaysSearchBarInNavigationBar 就可以了(除非你还要用到 scope bar,我只能说你真不幸)。我倒是很喜欢这么做,但比较遗憾的是,iOS7 上的 UISearchDisplayController 貌似被摧残的比较严重,尤其是iPad。苹果公司看上去像是没时间处理这个问题的样子(原文:Apple seems to have run out of time),对于显示的搜索结果并不会隐藏实际的表视图。在 iOS7 之前,这并没有问题,但是现在 searchResultsTableView 有一个透明的背景色,使它看上去相当糟糕。作为一种变通方法,你可以设置不透明色或者取道于富于技巧的手段来获得你所期望的。关于这个控件会出现各种各样的结果,当使用displaysSearchBarInNavigationBar时甚至不会展示搜索表视图。

你的结果可能有所不同,但我是使用了一些手段来让displaysSearchBarInNavigationBar工作的:

- (void)restoreOriginalTableView 
{
    if (PSPDFIsUIKitFlatMode() && self.originalTableView) {
        self.view = self.originalTableView;
    }
}

- (UITableView *)tableView 
{
    return self.originalTableView ?: [super tableView];
}

- (void)searchDisplayController:(UISearchDisplayController *)controller 
  didShowSearchResultsTableView:(UITableView *)tableView 
{
    // HACK: iOS 7 requires a cruel workaround to show the search table view.
    if (PSPDFIsUIKitFlatMode()) {
        if (!self.originalTableView) self.originalTableView = self.tableView;
        self.view = controller.searchResultsTableView;
        controller.searchResultsTableView.contentInset = UIEdgeInsetsZero; // Remove 64 pixel gap
    }
}

- (void)searchDisplayController:(UISearchDisplayController *)controller 
  didHideSearchResultsTableView:(UITableView *)tableView 
{
    [self restoreOriginalTableView];
}

这里,别忘了在viewWillDisappear中调用restoreOriginalTableView,否则会发送crash。

记住这是唯一的解决办法;可能有不少激进的方法不替换视图本身,但这个问题确实应该由苹果公司来修复。(TODO: RADAR!)

分页

UIWebView 使用了新的技巧来自动分页带paginationMode的网站。有一大堆与此功能相关的新属性:

@property (nonatomic) UIWebPaginationMode paginationMode NS_AVAILABLE_IOS(7_0);
@property (nonatomic) UIWebPaginationBreakingMode paginationBreakingMode NS_AVAILABLE_IOS(7_0);
@property (nonatomic) CGFloat pageLength NS_AVAILABLE_IOS(7_0);
@property (nonatomic) CGFloat gapBetweenPages NS_AVAILABLE_IOS(7_0);
@property (nonatomic, readonly) NSUInteger pageCount NS_AVAILABLE_IOS(7_0);

现在而言,虽然这可能并非对于大多数网站都有用,但它肯定是生成简单的电子书阅读器或显示文本的一种更好的方式。加点乐子的话,请尝试将它设置为UIWebPaginationModeBottomToTop

会飞的 Popovers

想知道为什么你的popovers疯了一样到处乱飞?在UIPopoverControllerDelegate协议中有一个新的代理方法使你能控制它:

-     (void)popoverController:(UIPopoverController *)popoverController
  willRepositionPopoverToRect:(inout CGRect *)rect 
                       inView:(inout UIView **)view

当popover锚点是指向一个UIBarButtonItem时,UIPopoverController会有一些动作,但如果你让它在一个view或者rect中显示,你可能就需要实现此方法并正常返回。一个花费了我相当长的时间来验证的问题——如果你通过改变preferredContentSize来动态调整你的popovers,那么这个方法就特别要求得以实现。苹果公司现在对改变popovers大小的请求更严格,如果没有预留足够的空间,popover将会到处移动。

键盘支持

苹果公司不只为我们提供了全新的framework用于游戏控制器,它也给了我们这些键盘爱好者一些提示!你会发现新定义的公用键像 UIKeyInputEscape 或 UIKeyInputUpArrow,可以使用所有新的 UIKeyCommand 类截查。在 iOS7 之前,只能通过一些难以言表的手段来处理键盘命令,现在,就让我们操起蓝牙键盘试试看我们能用这个做什么!

开始之前,你需要对责任者链有个了解。你的 UIApplication 继承自 UIResponder,UIView 和 UIViewController 也是如此。如果你处理过 UIMenuItem 并且没有使用我的基于块的包装的话,那么你已经了解了这些。事件先被发送到最上层的响应者,然后一级级往下传递直到 UIApplication 。为了捕获按键命令,你需要告诉系统你关心哪些键命令(而不是全捕获)。为了完成这个,你需要重写keyCommands这个新属性:

- (NSArray *)keyCommands 
{
    return @[[UIKeyCommand keyCommandWithInput:@"f"
                                 modifierFlags:UIKeyModifierCommand  
                                        action:@selector(searchKeyPressed:)]];
}

- (void)searchKeyPressed:(UIKeyCommand *)keyCommand 
{
    // Respond to the event
}

现在可别太激动,需要注意的是,这个方法只在键盘可见时有效(比如有类似 UITextView 这样的对象作为第一响应者时)。对于全局热键,你仍然需要用上面的方法。除却那些,这个路径还是很优雅的。不要覆盖类似 cmd-V 系统的快捷键,它会被自动映射为粘贴功能。

还有一些新的预定义的响应行为如:

- (void)increaseSize:(id)sender NS_AVAILABLE_IOS(7_0);
- (void)decreaseSize:(id)sender NS_AVAILABLE_IOS(7_0);

它们分别对应着 cmd+ 和 cmd- 命令,用来放大/缩小内容。

匹配键盘背景

苹果公司终于公开了 UIInputView,其中提供了一种方式——使用UIInputViewStyleKeyboard来匹配键盘样式。这使得你可以编写自定义的键盘或者带默认样式的默认键盘扩展(工具条)。这个类以前就存在了,不过现在我们终于可以绕过私有API的方式来使用它了。

如果 UIInputView 是一个 inputView 或者 inputAccessoryView 的根视图,它将只显示一个背景,否则它将是透明的。遗憾的是,这并不能让你实现一个未填充的分离态的键盘,但它仍然比用一个简单的 UIToolbar 要好。我还没看到苹果在何处使用这个新API,貌似它只作为一个 UIToolbar 使用在 Safari 上。

了解你的网络

虽然早在 iOS4 的时候,关于网络信息的大部分已经在 CTTelephony 暴露了,但它通常只用于特定场景并非十分有用。iOS7 中,苹果公司为其添加了一个方法,其中最有用的:currentRadioAccessTechnology。这个使你能知晓手机是处于较慢的GPRS还是高速的LTE或者介于其中。目前还没有方法得到连接速度(当然手机本身也无法获取这个),但是这足以用来优化一个下载管理器,让其在EDGE下不用尝试同时去下载6张图片了。

现在还没有currentRadioAccessTechnology的相关文档,因此存在一些不正规或者错误的用法。当你想要获取当前网络信号值,你应当注册一个CTRadioAccessTechnologyDidChangeNotification通知而不应该去轮询这个属性。为了获取这些通知,你需要使用CTTelephonyNetworkInfo的一个实例,注意不要在通知中创建 CTTelephonyNetworkInfo 的实例,否则会 crash。

在这个简单的例子中,我在block中捕获并持有了 telephonyInfo,大家可以忽略这个:

CTTelephonyNetworkInfo *telephonyInfo = [CTTelephonyNetworkInfo new];
NSLog(@"Current Radio Access Technology: %@", telephonyInfo.currentRadioAccessTechnology);
[NSNotificationCenter.defaultCenter addObserverForName:CTRadioAccessTechnologyDidChangeNotification 
                                                object:nil 
                                                 queue:nil 
                                            usingBlock:^(NSNotification *note) 
{
    NSLog(@"New Radio Access Technology: %@", telephonyInfo.currentRadioAccessTechnology);
}];

当手机从edge环境到3G,log输出应该像这样:

iOS7Tests[612:60b] Current Radio Access Technology: CTRadioAccessTechnologyEdge
iOS7Tests[612:1803] New Radio Access Technology: (null)
iOS7Tests[612:1803] New Radio Access Technology: CTRadioAccessTechnologyHSDPA

苹果导出了所有字符串符号,因此可以很简单的比较和检测当前的网络信息。

Core Foundation 和 Autorelease

Core Foundation中出现了一个新的方法,它被用于私有调用已有数年时间:

CFTypeRef CFAutorelease(CFTypeRef CF_RELEASES_ARGUMENT arg)

它确实做了你所期望的事,让人费解的是苹果花了这么长时间才把它公开。ARC 下,大多数人在处理返回 Core Foundation 对象时是通过转换成对等的 NS 对象来完成的,如 NSDictionary,即便它只是一个 CFDictionaryRef 然后简单地 CFBridgingRelease() 。这样通常没问题,除非你返回的对等 NS 对象不可用时,如 CFBagRef。你要么使用 id,这样会失去类型安全性,要么你将你的方法重命名为 createMethod 并考虑所有的内存语义,最后使用 CFRelease。还有一些手段,比如这个,用 non-ARC-file 标签然后编译,但终归得使用CFAutorelease()。另外:不要编写使用苹果公司命名空间的代码,所有这些自定义的 CF-宏将来都会被打破的。

图片解压缩

当通过 UIImage 展示一张图时,在显示之前需要解压缩(除非源已经像素缓存了)。对于 JPG/PNG 文件这会占用相当可观的时间并会造成卡顿。iOS6 以前,通常是创建一个位图上下文,然后在其中画图来解决。(参见 AFNetworking 如何处理)。

iOS7 开始,你可以使用kCGImageSourceShouldCacheImmediately:来强制图片在创建时立即解压缩:

+ (UIImage *)decompressedImageWithData:(NSData *)data 
{
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
    CGImageRef cgImage = CGImageSourceCreateImageAtIndex(source, 0, (__bridge CFDictionaryRef)@{(id)kCGImageSourceShouldCacheImmediately: @YES});

    UIImage *image = [UIImage imageWithCGImage:cgImage];
    CGImageRelease(cgImage);
    CFRelease(source);
    return image;
}

当我刚发现这一点时确实很兴奋,但事实并非如此。在我的测试中,发现当开启了即时缓存后性能有明显的降低。要么这个方法是在主线程中调用的(不太可能),感觉上性能更糟,因为它在方法copyImageBlockSetJPEG中锁住了,而同时在主线程中在显示非加密的图片所致。在我的程序中,我在主线程中加载小的预览图,在后台线程中加载大型图,使用了kCGImageSourceShouldCacheImmediately后小小的解压缩阻塞了主线程,同时在后台处理大量开销昂贵的操作。

还有更多关于图片解压缩相关的却不是 iOS7 中的新东西,像kCGImageSourceShouldCache,它用来控制系统自动卸载解压缩的图片数据的能力。确保你将它设置为YES,否则所有的工作都将没有意义。有趣的是,苹果在64bit运行时的系统中将kCGImageSourceShouldCache的默认值从 NO 改为了 YES。

盗版检查

苹果添加了一个方式,通过 NSBunble 上的新方法appStoreReceiptURL来评估Lion系统上 App Store 的收据,同时也将其移植到了 iOS 上。这使得你可以检查你的应用是在被合法购买或者已经被破解了。检查收据还有一个重要的原因,它包含了初始购买日期,这点对于把你的应用从付费模型迁移到免费+应用内付费方式很有帮助意义。你可以根据这个初始购买日期来决定额外内容对于你的用户是免费的还是收费的。

收据还允许你检查应用程序是否通过批量购买计划购买以及该许可证是否仍有效,有一个名为SKReceiptPropertyIsVolumePurchase的属性显示了该值。

当你调用appStoreReceiptURL时,你需要特别注意,因为在 iOS6 上,它还是一个私有API,你应该在用户代码中先调用doesNotRecognizeSelector:,在调用前检查运行(基础)版本。在开发期间,这个方法返回的 URL 不会是指向一个文件。你可能需要使用 StoreKit 的SKReceiptRefreshRequest,这也是 iOS7 中的新东西,用它来下载证书。使用一个至少购买过一次的测试用户,否则它将没法工作:

// Refresh the Receipt
SKReceiptRefreshRequest *request = [[SKReceiptRefreshRequest alloc] init];
[request setDelegate:self];
[request start];

验证收据需要大量的代码。你需要使用OpenSSL和内嵌的苹果根证书,并且你还要了解一些基本的东西像是证书、PCKS容器以及ASN.1。这里有一些样例代码,但是你不应该让它这么简单——别只是拷贝现有的验证方法,至少做点修改或者编写你自己的,你应该不希望一个普通的补丁程序就能在数秒内瓦解你的努力吧。

你绝对应该读读苹果的指南——验证 Mac App 商店收据,这里面的大多数都适用于 iOS。苹果在 WWDC2013 的 Session308 “Using Receipts to Protect Your Digital Sales” 中详述了“Grand Unified Receipt”的变动。

Comic Sans MS

iOS7 中,终于迎回了 Comic Sans MS。现在,它以可下载的字体被添加到 iOS6 中,但当时的字体列表很少也不见得多么有趣。在 iOS7 中苹果添加了不少字体,包括“famous”,它和 PT Sans 或 Comic Sans MS 有些类似。kCTFontDownloadableAttribute并没有在 iOS6 中声明,所以 iOS7 以前它并不真正可用,但苹果确是在 iOS6 的时候就已经做了私有声明了。

字体列表是动态变化的,以后可能就会发生变动。苹果在 Tech Note HT5484 中罗列了一些可用的字体,但这个文档已经过时了,同时也不能反映 iOS7 的变化。

这里显示了你该如何获取一个用CTFontDescriptorRef标示可下载的字体数组:

CFDictionary *descriptorOptions = @{(id)kCTFontDownloadableAttribute : @YES};
CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes((CFDictionaryRef)descriptorOptions);
CFArrayRef fontDescriptors = CTFontDescriptorCreateMatchingFontDescriptors(descriptor, NULL);

系统不会检查字体是否已存在于磁盘上而将直接返回同样的列表。另外,这个方法可能会启用网络并造成阻塞,你不应该在主线程中使用它。

使用如下基于块的 API 来下载字体:

bool CTFontDescriptorMatchFontDescriptorsWithProgressHandler(
         CFArrayRef                          descriptors,
         CFSetRef                            mandatoryAttributes,
         CTFontDescriptorProgressHandler     progressBlock)

这个方法能操作网络并传递下载进度信息来调用你的progressBlock方法直到下载成功或者失败。参考苹果的 DownloadFont 样例看如何使用它。

有一些值得注意的地方,这里的字体只在当前程序周期内有效,下次运行将被重新载入内存。因为字体存放在共享空间中,你不能依赖于它们是否可用。很有可能也不能保证的说,系统会清理这个目录,或者你的程序被拷贝到新的设备环境中,而这时又没有这个字体存在,同时当前处于没有网络的环境中。在 Mac 或是模拟器上,你能根据kCTFontURLAttribute获得字体的绝对路径,加载速度也会提升,但是在 iOS 上是不可能的,因为这个目录在你程序之外,你需要再次调用CTFontDescriptorMatchFontDescriptorsWithProgressHandler

你也可以注册新的kCTFontManagerRegisteredFontsChangedNotification通知来跟踪新字体在何时载入到了字体注册表中。你可以在 WWDC2013 的 Session223 “Using Fonts with TextKit”中查找更多信息。

这还不够?

没关系,iOS7 的新东西远不止如此!了解一下 NSHipster 你将明白语音合成相关的东西,base64、NSURLComponents、NSProgress、bar codes、reading lists 以及 CIDetectorEyeBlink。还有很多我们没有涵盖到的,比如苹果 iOS7 的 API 变化iOS 指南的新东西以及 Foundation Release Notes(这些都是服务于 OS X的,但是代码都是共享的,也同样适用于 iOS)。很多方法都还没形成文档,等着你来探究和 blog。

原文链接: Peter Steinberger 翻译: 伯乐在线 - 伯乐在线读者
译文链接: http://blog.jobbole.com/51761/
[ 转载必须在正文中标注并保留原文链接、译文链接和译者等信息。]

iOS 7: 如何为iPhone 5S编译64位应用

| Comments

随着iPhone 5S的推出,大家开始关心5S上所使用的64位CPU A7。

除了关心A7的性能以外,大家还会关心一个问题,那就是使用A7的64位系统对应用有没有什么要求。特别是应用开发者,大家都比较关心我们的应用如何迁移到64位的系统上来,以充分发挥A7的能力。其实这些问题都可以在苹果的官方文档《64-Bit transition Guide for Cocoa Touch》中找到答案。

为了大家方便,我将《64-Bit transition Guide for Cocoa Touch》中的一些重点整理了一下,希望可以为大家节约一些详细阅读文档的时间,如果我理解有不对的地方请大家指正。

首先,A7使用的是ARM V8架构,除了使用64位的地址总线和64位的寄存器以外,还增加了寄存器的数量,目前A7中的整数和浮点数寄存器是A6的两倍。

这里需要强调的是,寄存器的增加大大提高了程序的运行速度。将CPU由32位提高到64位,最主要的改变增大了寻址能力,可以突破32位系统只能访问3G内存的限制(感谢 wanglang3081 指出这里的问题,32位系统在理论上可以访问4G内存,因为2的32次方约等于4 290 000 000,很多32位系统只能访问3G左右的内存是因为有一大部分地址被分配给I/O系统了,所以总体可用内存就不足4G了。),但是,32位到64位的改变并不一定意味着程序运行速度的提高,甚至有些情况下会因为64位系统中的数据占用内存变大而导致程序运行速度变慢。而寄存器数量的增加,则直接提高了程序运行速度,当然,前提是你的应用需要重新为64位系统编译一遍,让程序可以充分使用所有的寄存器。

使用Xcode 5可以很方便地将以前的应用编译成64位程序,基本过程如下:
  1. 使用Xcode 5 打开原有项目。

  2. 将支持的设备改成“iOS 7”。

  3. “Build Setting”中将“Architectures”改成“Standard Architectures (including 64-bit)”

  4. 运行测试程序,解决编译过程出现的问题。

其中第4步是关键,具体会遇到什么问题和原来程序的设计有关,包括使用数据类型的方式是否标准等,后面会继续讨论细节,其实《64-Bit transition Guide for Cocoa Touch》一书主要就是讲这些细节。

在讨论细节之前有一些较为宏观的内容大家可以了解一下。

Xcode 5编译的iOS 7程序包含了32位和64位两套二进制代码,在32位的iOS系统上会调用32位的二进制代码,在64位系统上会调用64位的二进制代码,以此来解决向后兼容的问题。

同时,考虑到很多32位的程序可能在没有重新编译的情况下部署到64位系统上,64位的iOS系统中带有两套FrameWork,一套是32位的,一套是64位的。

当64位的iOS系统运行原来的32位程序时,系统会调用32位的FrameWork作为底层支撑,当系统运行64位程序时,系统会调用64位的FrameWork作为底层支撑。

也就是说,当一个iPhone 5S上同时运行32位程序和64位程序时,系统同时将32位和64位两套FrameWork载入了内存中,所以消耗的内存也比较多。

如果一台64位的iOS设备上运行的所有程序都是为64位系统编译过的,iOS系统将只载入64位的FrameWork,这将节省好多内存。所以,如果大家都可以快速将程序传换成64位的,iOS将跑得更快。真的是“大家好才是真的好”。

后面我们来看看一些为64位系统调整程序的技术细节。

32位的iOS系统和64位的iOS系统主要的差别有两个,一个是数据类型的差别,一个是过程调用方法的差别。

在数据类型上,主要的变化是指针类型(Pointer)和长整数类型(long)的长度变化和内存对齐方式的变化,同时也导致了更高级别数据类型的变化,如NSInteger的长度也有变化。

在过程调用方法上,因为ARM V8 和ARM V7具有不同数量的寄存器,具有不同的过程调用约定,所以32位系统和64位系统在汇编层级是不同的。

根据以上两方面的变化,书中总结了以下要点,开发人员根据以下要点来检查原来的32位代码就差不多可以将应用移植到64位系统上了:
  • 不要将长整型数据(long)赋予整型(int)

    这种代码在32位系统上没有问题,因为在32位系统中long和int的长度是一样的,不过在64位系统中就有可能出问题,因为64位系统中long比int长,将long值赋予int将导致数据丢失。

  • 不要将指针类型(Pointer)赋予整型(int)

    为了方便地址计算,有时程序员会将指针类型赋予整型,这种代码在32位系统上没有问题,因为在32位系统中Pointer和int的长度是一样的,不过在64位系统中就会有问题,因为64位系统中Pointer比int长,将Pointer值赋予int将导致地址数据丢失,最终导致严重问题。

  • 留意那些和数位相关的数值计算

    比如掩码技术,如果使用一个long类型的掩码,转到64位系统后高位都是0,计算出来的结果可能不符合预期。还有无符号整数和有符号整数的混用等。

  • 留意对齐方式带来的变化

    如果在32位系统上定义一个结构包含两个long类型,第二个long数值的偏移地址是4,可以通过结构地址+4的方式获取,但是在64位系统上就不行了,因为在64位系统中第二个long数值的偏移地址是8。

  • 充分考虑在32位应用和64位应用之间的数据交换

    因为用户会通过网络交换数据,同时用户保存的数据也可能通过备份等方式在32位系统和64位系统之间切换,所以应用在保存和发送流数据的时候一定要考虑充分。比如数据在32位系统中保存,在64位系统中能否正常打开,或者反过来,在64位系统中保存,在32位系统中打开是否正常。

  • 重写所有汇编代码

    这点无需说明,如果你在代码中嵌入了汇编代码,你需要参考64位系统的指令集重写汇编代码。

  • 不要将可变参数的过程强制转换为定参过程,也不要将定参过程强制转换为可变参数的过程。

    这时因为32位系统和64位系统对于这两种过程调用方式的处理方法不同。

按以上几个重点去检查程序就差不多了,当然,具体的细节还有很多,需要在实际工作中结合代码和调试结果进行分析。

总之,建议具体负责应用迁移的开发者需要完整阅读《64-Bit transition Guide for Cocoa Touch》。

来源:keyboardOTA的博客

自定义返回按钮时,iOS7手势返回遇到的问题

| Comments

从iOS7的Beta版开始,就着手做兼容工作,到Beta4的時候,应用已经基本兼容,只是偶然发现,iOS样式的手势返回,也就是用interactivePopGestureRecognizer返回的时候,出现下面一些列问题。各方调研,无果,史无前例(废话,刚出来的7,上哪有例去—#)。

  • 我的应用是自定义的返回按钮图标(默认返回按钮样式不会出现问题3),为了保险,写了这句代码[self.navigationItem setHidesBackButton:YES]。 由于自定义返回按钮,所以iOS7自带返回手势无效。在需要的页面加上navigationController.interactivePopGestureRecognizer.delegate = self 返回手势好用了。

  • 于是出现了第二个问题。 在一级视图中,iOS样式返回的手势滑动一下,然后进入二级视图,发现,画面卡住了,按Home键转入后台,再返回应用,发现并没有Crash掉,而是直接跳到了二级视图里,运行正常了,大家知道push和pop的原理是用进栈出栈完成的,可能因为在一级视图中滑动那一下,影响了视图在栈中的位置。 好,先解决一下这个问题,一级视图中一定要加入self.navigationController.interactivePopGestureRecognizer.enabled = NO;,先把iOS7手势返回屏蔽掉,到二级视图再用self.navigationController.interactivePopGestureRecognizer.enabled = YES打开。就Ok了。

  • 好,第三个问题相继出现(其实是跟第二个一起出来的)。 手势返回拖动一半,放手,navigationBar上会出现三个小蓝点,而且位置不规律,可以肯定这个不是项目代码或者图片搞出来的东西,一定是SDK自己蹦出來的。 后台尝试发现UIBarButtonItemtitle如果是nil的话,就会有这个问题。 解决方案:把[self.navigationItem setHidesBackButton:YES];去掉,然後把假装成返回按钮的UIBarButtonItemtitle设置成@""

  • 大功告成。可见设计要是不按苹果规范来的话,就会遇到各种坑啊。

Objective-C常用數學常量宏及函數

| Comments

  • 数学常量:

    #define M_E         2.71828182845904523536028747135266250   // e
    
    #define M_LOG2E     1.44269504088896340735992468100189214   // log 2e
    
    #define M_LOG10E    0.434294481903251827651128918916605082  // log 10e
    
    #define M_LN2       0.693147180559945309417232121458176568  // log e2
    
    #define M_LN10      2.30258509299404568401799145468436421   // log e10
    
    #define M_PI        3.14159265358979323846264338327950288   // pi
    
    #define M_PI_2      1.57079632679489661923132169163975144   // pi/2
    
    #define M_PI_4      0.785398163397448309615660845819875721  // pi/4
    
    #define M_1_PI      0.318309886183790671537767526745028724  // 1/pi
    
    #define M_2_PI      0.636619772367581343075535053490057448  // 2/pi
    
    #define M_2_SQRTPI  1.12837916709551257389615890312154517   // 2/sqrt(pi)
    
    #define M_SQRT2     1.41421356237309504880168872420969808   // sqrt(2)
    
    #define M_SQRT1_2   0.707106781186547524400844362104849039  // 1/sqrt(2)
    


  • 常用函数:

    //指数运算
    
    NSLog(@"%.f", pow(3,2) ); //result 9
    
    NSLog(@"%.f", pow(3,3) ); //result 27
    
    
    //开平方运算
    
    NSLog(@"%.f", sqrt(16) ); //result 4
    
    NSLog(@"%.f", sqrt(81) ); //result 9
    
    
    //上舍入
    
    NSLog(@"res: %.f", ceil(3.000000000001)); //result 4
    
    NSLog(@"res: %.f", ceil(3.00)); //result 3
    
    
    //下舍入
    
    NSLog(@"res: %.f", floor(3.000000000001)); //result 3
    
    NSLog(@"res: %.f", floor(3.9999999)); //result 3
    
    
    //四舍五入
    
    NSLog(@"res: %.f", round(3.5)); //result 4
    
    NSLog(@"res: %.f", round(3.46)); //result 3
    
    NSLog(@"res: %.f", round(-3.5)); //NB: this one returns -4
    
    
    //最小值
    
    NSLog(@"res: %.f", fmin(5,10)); //result 5
    
    
    //最大值
    
    NSLog(@"res: %.f", fmax(5,10)); //result 10
    
    
    //绝对值
    
    NSLog(@"res: %.f", fabs(10)); //result 10
    
    NSLog(@"res: %.f", fabs(-10)); //result 10
    

iPhone应用开发中emoji带来的问题小结

| Comments

如果是iPhone自带字体,emoji和文字的高度是不同的,有些细节的地方会造成布局出现瑕疵,我之前的做法是在

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string

方法里对输入内容进行判断,采取屏蔽emoji输入的方式,具体屏蔽办法请参见: https://gist.github.com/cihancimen/4146056

但iOS7出来以后,通过测试发现7中修改和增加了一些emojide的编码。上述办法对于一些emoji的屏蔽失效。解决办法是找到合适的第三方字体包。同时,需要考虑iOS和PHP之前通信以及和其他平台通信时产生的问题。于是找到如下文章。

http://wpceo.com/supporting-emoji-on-php-mysql/

Xcode5带来的一个小问题

| Comments

带有xib文件如果用Xcode5打开过,再用Xcode4.6打开,会出现如下错误:

Please use a newer version of Xcode. Consider changing the document's Development Target to preserve compatibility.

解决办法如下:

Open the Storyboard file on Xcode 5, And then: 1. Open file inspector 2. On "Interface Builder Documents" section change "Open with" to Xcode 4.6 (if it's 5.x). 3. Save, Close the project and open that with old Xcode.

Hide statusBar (Also Work on Ios7)

| Comments

  • 代码方式隐藏

    好处是灵活,随时隐藏随时显示

      //viewDidload
    
      if ([self respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]) {
    
          // iOS 7
    
          [self prefersStatusBarHidden];
    
          [self performSelector:@selector(setNeedsStatusBarAppearanceUpdate)];
    
      } else {
    
          // iOS 6
    
          [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide];
    
      }
    
      // Add this Method
    
      - (BOOL)prefersStatusBarHidden
      {
          return YES;
      }
    
  • 在配置文件中设置

    如果整个应用都不想显示状态栏,那么这个更方便一些

    Info.plist中添加如下两行键值对:

    1. Status bar is initially hidden(值设置成YES

    2. View controller-based status bar appearance(值设置成NO

iOS获取当前系统语言

| Comments

添加如下代码:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 
NSArray *languages = [defaults objectForKey:@"AppleLanguages"]; 
NSString *currentLanguage = [languages objectAtIndex:0];

取得设置好的语言。。日语是ja,英文是en,其他。。就设置以后log出来看看吧!

dataFormat里设置格式字母含义

| Comments

y 年 Year 1996; 96

M 年中的月份 Month July; Jul; 07

w 年中的周数 Number 27

W 月份中的周数 Number 2

D 年中的天数 Number 189

d 月份中的天数 Number 10

F 月份中的星期 Number 2

E 星期中的天数 Text Tuesday; Tue

a Am/pm 标记 Text PM

H 一天中的小时数(0-23) Number 0

k 一天中的小时数(1-24) Number 24

K am/pm 中的小时数(0-11) Number 0

h am/pm 中的小时数(1-12) Number 12

m 小时中的分钟数 Number 30

s 分钟中的秒数 Number 55

S 毫秒数 Number 978

z 时区 General time zone Pacific Standard Time; PST; GMT-08:00

Z 时区 RFC 822 time zone -0800

UITableView隐藏多余的分割线

| Comments

- (void)setExtraCellLineHidden: (UITableView *)tableView
{
    UIView *view = [UIView new];
    view.backgroundColor = [UIColor clearColor];
    [tableView setTableFooterView:view];
}

当tableview的dataSource为空时,也就是没有数据可显示时,该方法无效,只能在numberOfRowsInsection函数,通过判断dataSouce的数据个数,如果为零可以将tableview的 separatorStyle设置为UITableViewCellSeparatorStyleNone去掉分割线,然后在大于零时将其设置为 UITableViewCellSeparatorStyleSingleLine

顺便,如果想仿造TableView的分割线默认样式,做个单条的分割线的话, 可以自己创建一个高度为1.25,颜色值为0xe7e7e7的UIImageview来伪装一下。