新年之际,盘点一些APP开发技巧
(原文:Reader Submissions - New Year's 2015 作者:Mattt Thompson 译者:培子 校对:蓝魂)
回顾过去一年发生在我们身边的事情时,有一点不得不提:对苹果开发者来讲,2014年是令人难以置信的一年。在这短短的一年中(有关APP的开发)发生了如此多的变化:在充满吸引力的Swift面前,我们几乎忘了之前是如何痴迷于Objective-C;以及充满想象力的iOS 8和WatchKit,难以想象还有什么API能与之相比。
NSHipster的惯例:请可爱的童鞋们,在新年的第一天,为大家展示你们(在开发中)常使用的技巧和方法。如今,随着来自库比蒂诺(Cupertino,苹果总部,位于旧金山)和众多开源社区的一系列API的涌现,妈妈再也不用担心我们找不到有趣的东西来分享啦!
在此,感谢以下童鞋们所做的贡献:
Colin Rofls, Cédric Luthi, Florent Pillet, Heath Borders, Joe Zobkiw, Jon Friskics, Justin Miller, Marcin Matczuk, Mikael Konradsson, Nolan O'Brien, Robert Widmann, Sachin Palewar,Samuel Defago, Sebastian Wittenkamp, Vadim Shpakovski, and Zak
成员函数的使用技巧
(来自 Robert Widmann)
在用静态方式调用Swift类和结构中的成员函数时,通常使用以下格式:
Object->(参数)->Things
比如,你可以用以下两种方式调用reverse():
1
2
|
[1,2,3,4].reverse( ) Array.reverse([1,2,3,4]) |
用@()来封装C字符串
(来自 Samuel Defago)
事实上文字大部分时候是数字和字母的集合,使用C字符串,尤其当我在使用运行时编码的时候,我常常会忘记用UTF8编码、以NULL结束:Objective-C字符串封装:
1
2
3
|
NSString *propertyAttributesString = @(property_getAttributes(class_getProperty([NSObject class], "description" ))); // T@"NSString",R,C |
AmIBeingDebugged
Nolan O'Brien在这篇Q&A技术文档中让我们注意到了AmIBeingDebugged函数方法:
使用延迟存储属性
(来自 Colin Rofls)
在开发过程中,应该避免使用Optionals类型,更不应该使用隐式解包optionals类型。你想声明一个var变量却不想给一个初始值?使用“lazy”吧,唯一要注意的就是:在你的属性被赋值之前不要调用getter方法即可(童叟无欺!)
1
|
lazy var someModelStructure = ExpensiveClass() |
假如你仅仅对这var变量调用set方法,而没有调用getter方法的话,这个被lazy修饰的var变量不会被赋值。例如,用lazy修饰那些直到viewDidLoad时才需要初始化的views变量就会非常合适。
获取Storyboard视图容器里的子视图控制器
(来自 Vadim Shpakovski)
有一个比较方便的方法来获取故事板视图容器里的子视图控制器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// 1. A property has the same name as a segue identifier in XIB @property (nonatomic) ChildViewController1 *childController1; @property (nonatomic) ChildViewController2 *childController2; // #pragma mark - UIViewController - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { [ super prepareForSegue:segue sender:sender]; // 2. All known destination controllers assigned to properties if ([self respondsToSelector:NSSelectorFromString(segue.identifier)]) { [self setValue:segue.destinationViewController forKey:segue.identifier]; } } - (void)viewDidLoad { [ super viewDidLoad]; // 3. Controllers already available bc viewDidLoad is called after prepareForSegue self.childController1.view.backgroundColor = [UIColor redColor]; self.childController2.view.backgroundColor = [UIColor blueColor]; } |
重复运行项目,不重复构建项目
(来自 Heath Borders)
假如你一直在不停地调试同一个问题,你可以在不重复构建的情况下运行你的APP,这样:“Product>Perform Action>Run without Building” (快捷键??R: Command + R)
快速获取Playground资源
(来自 Jon Friskics)
Swift里的所有Playground共享相同的数据目录:/Users/HOME/Documents/Shared Playground Data
如果你喜欢使用很多Playgrounds,你将需要在上述共享目录下为每个Playground新建对应的子目录,来存储每个Playground用到的数据;但是那之后你需要告诉每个Playground在哪儿可以获取其对应的数据。下面是我常用的一个辅助解决方法:
1
2
3
|
func pathToFileInSharedSubfolder(file: String) -> String { return XCPSharedDataDirectoryPath + "/" + NSProcessInfo.processInfo().processName + "/" + file } |
processName属性是Playground文件的名字,因此只要你已经在Playground数据共享文件目录下以相同的名字新建了一个子目录,那么你可以很容易访问这些数据,和读取本地JSON数据一样:
1
2
3
|
var jsonReadError:NSError? let jsonData = NSFileManager.defaultManager().contentsAtPath(pathToFileInSharedSubfolder( "data.json" ))! let jsonArray = NSJSONSerialization.JSONObjectWithData(jsonData, options: nil, error: &jsonReadError) as [AnyObject] |
....或者 访问本地图片
1
2
|
let imageView = UIImageView() imageView.image = UIImage(contentsOfFile: pathToFileInSharedSubfolder( "image.png" )) |
-----------------------------------------------------------------------
Please attention!本篇文章剩余的部分来自Cédric Luthi大神的贡献,他分享了一些比较有用的开发技巧和技术,这些内容足够自成一篇,值得细细品读。这里再次感谢Cédric!
CocoaPods大揭秘
这儿有一个快速的方法来检查APP里用到的所有pods:
1
|
$ class-dump -C Pods_ /Applications/Squire.app | grep -o "Pods_\w+" |
CREATE_INFOPLIST_SECTION_IN_BINARY
注意Xcode中为命令模式APP(command-line apps)设置的CREATE_INFOLIST_SECTION_IN_BINARY属性。这比使用-sectcreate__TEXT__info_plist链接标志位更加容易,前者还把已经编译好的Info.plist文件嵌入在二进制编码中。
关于如何向苹果提需求,它也给我们上了一课,这个特性需求早在2006年的 rdar://4722772 被提出,但直到7年后才被满足。
(译者注:言外之意是它是反面教材,应该更有技巧的提需求)
禁用 dylib钩子
(来自 Sam Marshall)
Sam Marshall这个技巧可谓是走自己的路,让黑客无路可走。
在你的“Other Linker Flags”里加上下面这行:
1
|
-Wl,-sectcreate,__RESTRICT,__restrict,/dev/ null |
NSBundle -preferredLocalizations
某些时候,你需要知道APP当前使用的是什么语言。通常,大家会使用NSLocal+preferredLanguages. 可惜的是这个方法不会告诉你APP实际呈现的文字语种。你仅仅会得到iOS系统里“Settings->General->Language&Region->Preferred Language”列表中的选项,或者OSX系统里“System Preferences->Language & Region->Preferred Languages”列表中的选项。想象一下:优先语言列表中只有{英语,法语},但你的APP仅使用德语;调用[[NSLocal preferredLanguages] firstObject]返回给你的是英语,而不是德语。
正确的方法是用[[NSBundle mainBundle] preferredLocalizations]方法。
苹果的开发文档是这样说的:
一个包含了在bundle中本地化的语言ID的NSString对象的数组,里面的字符串排序是根据用户的语言偏好设置和可使用的地理位置而来的。
NSBundle.h里的备注:
一个bundle中本地化的子集,重新排序到当前执行坏境的优先序列里,main bundle的语言顺序中最前面的是用户希望在UI界面上看到的语种。
当然你也许需要调用这个方法:
1
|
NSLocal+canonicalLanguageIdentifierFromString: |
来确保你使用的文字语种是规范的语种。
保护SDK头文件
如果你用dmg安装Xcode,那么看看这篇Joar Wingfors的文章,它讲述了如何通过保留所有权来避免SDK头文件被意外修改:
1
|
$ sudo ditto /Volumes/Xcode/Xcode.app /Applications/Xcode.app |
任意类型的实例变量检测
为了达到逆向处理的目的,查询对象的实例变量是一个常见可靠的途径。通常调用对象valueForKey:方法就能达到这一目的,除了少数重写了类方法+accessInstanceVariablesDirectly的类屏蔽了该操作。
下面是一个例子:当实例变量有一个为任意类型的属性时,上述提到的操作无效
这是iOS6.1 SDK中MediaPlayer 框架的一段引用:
1
2
3
4
|
@interface MPMoviePlayerController : NSObject { void *_internal; // 4 = 0x4 BOOL _readyForDisplay; // 8 = 0x8 } |
因为 id internal=[moviePlayerController valueForKey:@”internal”] 无效,下面有一个笨办法来取得这个变量:
1
|
id internal = *((const id*)(void*)((uintptr_t)moviePlayerController + sizeof(Class))); |
注意!不要随意调用这段代码,因为ivar的布局可能改变(指针偏移量计算可能出错)。仅在逆向工程中使用!
NSDateFormatter +dateFormatFromTemplate:options:locale:
友情提示:假如你调用[NSDateFormatter setDateFormat],而没有调用[NSDateFormatter dateFormatFromTemplate:options:local:],n那么很可能出错。
苹果文档:
1
2
3
|
+ (NSString *)dateFormatFromTemplate:(NSString *)template options:(NSUInteger)opts locale:(NSLocale *)locale |
不同地区有不同的日期格式。使用这个方法的目的:得到指定地区指定日期字段的一个合适的格式(通常你可以通过currentLocal查看当前所属地区)
下面这个例子给我们表现了英式英语和美式英语不同的日期格式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
NSLocale *usLocale = [[NSLocale alloc] initWithLocaleIdentifier:@ "en_US" ]; NSLocale *gbLocale = [[NSLocale alloc] initWithLocaleIdentifier:@ "en_GB" ]; NSString *dateFormat; NSString *dateComponents = @ "yMMMMd" ; dateFormat = [NSDateFormatter dateFormatFromTemplate:dateComponents options:0 locale:usLocale]; NSLog(@ "Date format for %@: %@" , [usLocale displayNameForKey:NSLocaleIdentifier value:[usLocale localeIdentifier]], dateFormat); dateFormat = [NSDateFormatter dateFormatFromTemplate:dateComponents options:0 locale:gbLocale]; NSLog(@ "Date format for %@: %@" , [gbLocale displayNameForKey:NSLocaleIdentifier value:[gbLocale localeIdentifier]], dateFormat); // Output: // Date format for English (United States): MMMM d, y // Date format for English (United Kingdom): d MMMM y |
通过调试获取内部常量
近期, Matthias Tretter在Twitter上问到:
有人知道在iOS8里modal viewController presentation的默认动画时间和跳转方式吗?
我们在UIKit的类库中发现了这样一个函数:[UITransitionView defaultDurationForTransition:],并在这个方法的位置加一个断点:
1
|
(lldb) br set -n "+[UITransitionView defaultDurationForTransition:]" |
模态显示一个viewController,就会停在这个断点,输入finish执行该方法:
1
|
(lldb)finish |
在defaultDurationForTransition:被执行时,你就能读到结果(在xmm0寄存器里)
1
2
|
(lldb) register read xmm0 --format float64 xmm0 = {0.4 0} |
回复:默认动画时间0.4s
DIY 弱关联对象
不幸的是,关联对象OBJC_ASSOCIATION_ASSIGN策略不支持引用计数为0的弱引用。幸运的是,你可以很容易实现它,你仅仅需要一个简单的类,并在这个类里弱引用一个对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@interface WeakObjectContainter : NSObject @property (nonatomic, readonly, weak) id object; @end @implementation WeakObjectContainter - (instancetype)initWithObject:(id)object { self = [ super init]; if (!self) { return nil; } self.object = object; return self; } @end |
然后,通过OBJC_ASSOCIATION_RETAIN(_NONATOMIC)关联WeakObjectContainter:
1
|
objc_setAssociatedObject(self, &MyKey, [[WeakObjectContainter alloc] initWithObject:object], OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
用object属性指向这个所需的引用计数为0的弱引用对象。
1
|
id object = [objc_getAssociatedObject(self, &MyKey) object]; |
在这么多日新月异的新技术推动下,我们将迎来一个充满无限可能和机会的一年。在此,祝大家2015年新年快乐!
愿编程路上,与君共勉。
(本文为CocoaChina组织翻译,本译文权利归译者所有,未经允许禁止转载。)
新年之际,盘点一些APP开发技巧的更多相关文章
- 这些APP开发技巧可少花60万!
用户需求——我偏不用干嘛要装? 随着手机的普及,大众流量的端口从电脑转移到手机,传统的商业平台从线下到电脑再到手机进行了转换.手机APP作为移动互联网的入口,众多创业者凭借一个手机APP成就了亿万财富 ...
- web app开发技巧总结 (share)
(转自http://hi.baidu.com/kuntakinte/item/ca92d6e5edae9fc0bbf37d08) 自Iphone和Android这两个牛逼的手机操作系统发布以来,在互联 ...
- Web APP开发技巧总结(转)
一.META/LINK相关: 1.百度禁止转码 通过百度手机打开网页时,百度可能会对你的网页进行转码,往你页面贴上它的广告,非常之恶心.不过我们可以通过这个meta标签来禁止它: <meta h ...
- Web APP开发技巧总结
http://www.admin10000.com/document/6304.html 1. 当网站添加到主屏幕后再点击进行启动时,可隐藏地址栏(从浏览器跳转或输入链接进入并没有此效果) <m ...
- 前端读者 | Web App开发入门
本文来自互联网 自Iphone和Android这两个牛逼的手机操作系统发布以来,在互联网界从此就多了一个新的名词 - Web App(意为基于WEB形式的应用程序).业界关于Web App与Nativ ...
- 十大技巧快速提升原生APP开发性能
移动应用市场用户争夺战日益激烈,原来做APP拼想法拼创意拼是否抓住用户痛点.现在,精细化用户体验成为了一个APP能否留存用户的关键问题,一旦用户觉得体验不畅,马上就有竞品APP后补,如何开发高性能的移 ...
- AppCan移动开发技巧:3步走,获取移动APP签名信息
大家知道,在移动APP开发里,与应用包名一样,应用的签名信息需是唯一的,否则将会出现应用冒领.重复安装等问题.之前分享过安卓应用的签名如何获取(点击查看),这里将继续以AppCan平台为例,分享如何获 ...
- 移动 Web 开发技巧之(后续)
昨天的<移动 Web 开发技巧>的这篇文章,大家反响不错,因为这些问题在大家日常写移动端的页面时经常遇到的.所以那个文章还是超级实用的,那么我们今天继续来分享一下移动端的web开发技巧吧, ...
- 微信公众平台开发:Web App开发入门
WebApp与Native App有何区别呢?Native App:1.开发成本非常大.一般使用的开发语言为JAVA.C++.Objective-C.2.更新体验较差.同时也比较麻烦.每一次发布新的版 ...
随机推荐
- 圣诞节来了,雪花纷飞的CSS3动画,还不首页用起来
圣诞节来了,冬天来了,怎么可以没有雪花纷飞效果,昨天下班前折腾了一会儿,弄了个雪花纷飞的实例,有兴趣的可以交流分享下. 原文链接:http://www.html5think.com/article/i ...
- chrome 插件 vimium 快捷键大全
DESCRIPTION vimium是一款让你在chrome浏览器能方便地使用键盘操作浏览器的插件.虽然和firefox相比还有些许不足(比如不能按到一些按钮之类的),但是vimium还是做到了能在9 ...
- C#实现FTP文件夹下载功能【转载】
网上有很多FTP单个文件下载的方法,前段时间需要用到一个FTP文件夹下载的功能,于是找了下网上的相关资料结合MSDN实现了一段FTP文件夹下载的代码. 实现的思路主要是通过遍历获得文件夹下的所有文件, ...
- [Redux] Wrapping dispatch() to Log Actions
We will learn how centralized updates in Redux let us log every state change to the console along wi ...
- HDU 5046 Airport ( Dancing Links 反复覆盖 )
今年上海网络赛的一道题目 , 跟 HDU 2295 如出一辙 . 就是距离的计算一个是欧几里得距离 , 一个是曼哈顿距离 学完DLX感觉这题好水 ,就是一个裸的反复覆盖 注意下别溢出即可了 #incl ...
- C# StringExt 字符串扩展
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threa ...
- C#实现Ruby的负数索引器
public class InvertibleList<T> : List<T> { public new T this[int index] { get { ) return ...
- Eclipse 浏览文件插件 EasyExplorer 和 OpenExplorer
EasyExplorer 是一个类似于 Windows Explorer的Eclipse插件,它可以帮助你在不退出Eclipse的环境下浏览本地文件系统 下载地址: 从 http://sourcef ...
- springMVC工作原理图
- winows 进程通信的实例详解
发送端: 新建一个基本对话框工程,添加6个文本框控件,并且关联控件变量(CString类型): m_strCopyData, m_strFileMap, m_strMem, m_strRegMsg, ...