Reader Submissions - New Year's 2015
当我们花些时间来回溯我们在过去一年的经历的时候,有一点是清楚的:对专业的苹果开发者来说,2014 年是一个令人难以置信的一年。在这么短的时间跨度内发生了这么多的事情,都记不得在 Swift 之前我们跟 Objective-C 的关系,或者还有什么 API 比 iOS 8 或 WatchKit 更让我们着迷。
这有一个 NSHipster 传统问题要问你们,亲爱的读者,请把你在过去一年里最喜欢的技巧发送给我们,我们会在新年假期后公布结果。这一年随着大量新发展的出现,无论从苹果还是整个社区,都为读者分享了很多的有趣花絮。
谢谢 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, 和 Zak Remer 的贡献。
成员函数的秘密生活
来自 Robert Widmann:
在 Swift 的类和结构里,使用静态时成员函数类总是有下列类型:
Object -> (Args) -> Thing
比如,你可以用两种方式来对一个数组调用 reverse()
:
[1, 2, 3, 4].reverse()
Array.reverse([1, 2, 3, 4])()
@( )
来封装 C-Strings
来自 Samuel Defago:
鉴于文字大部分是用数字和集合关联的,我常常忘记它们可以在 UTF8 下工作良好,并且编码了
NULL
, 终结了 C-string,特别是当我使用运行时代码:
NSString *property Attributes String =
@(property_get Attributes(class_get Property([NSObject class], "description")));
// T@"NSString",R,C
AmIBeingDebugged
Nolan O’Brien 的 this Technical Q&A document 让我们对 Am
方法引起了关注:
#include <assert.h>
#include <stdbool.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/sysctl.h>
static Bool Am IBeing Debugged(void) {
int mib[4];
struct kinfo_proc info;
size_t size = sizeof(info);
info.kp_proc.p_flag = 0;
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PID;
mib[3] = getpid();
sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
return (info.kp_proc.p_flag & P_TRACED) != 0;
}
使用惰性变量(Lazy Variables)
来自 Colin Rofls:
避免使用 optional。应尽量避免对 optional 进行隐式拆包(implicitly unwraped)。要声明一个变量,但不一定在初始化时赋初始值?就用惰性关键字,在你真的有值之前不调用 getter 方法。
lazy var some Model Structure = Expensive Class()
如果你对这个变量调用
set
之前没有调用过 getter,惰性表达式永远不会被执行。比如在视图里直到 viewDidLoad 之前你都不一定要初始化就很棒。
访问添加到 Storyboard 的容器视图(container views)里的子控制器(child controllers)
来自 Vadim Shpakovski:
有一种方便的方式来访问插入到 storyboard 容器视图的子控制器:
// 1. A property has the same name as a segue identifier in XIB
@property (nonatomic) Child View Controller1 *child Controller1;
@property (nonatomic) Child View Controller2 *child Controller2;
// #pragma mark - UIView Controller
- (void)prepare For Segue:(UIStoryboard Segue *)segue
sender:(id)sender
{
[super prepare For Segue:segue sender:sender];
// 2. All known destination controllers assigned to properties
if ([self responds To Selector:NSSelector From String(segue.identifier)]) {
[self set Value:segue.destination View Controller for Key:segue.identifier];
}
}
- (void)view Did Load {
[super view Did Load];
// 3. Controllers already available bc view Did Load is called after prepare For Segue
self.child Controller1.view.background Color = [UIColor red Color];
self.child Controller2.view.background Color = [UIColor blue Color];
}
不需要重新编译的重新运行
来自 Heath Borders:
如果你一遍又一遍的调试同样的问题,你可以不重新编译就运行你的应用程序: “Product > Perform Action > Run without Building” (
⌘⌃R
)。
快速访问 Playground 资源
来自 Jon Friskics:
Swift Playgrounds 跟所有的共享 Playground 数据都在
/Users/HOME/Documents/Shared Playground Data
下可以找到。
如果你喜欢使用很多的 Playgrounds,你会想要把各 Playground 使用到的数据放到该共享文件夹的子文件夹里面,但你得让 Playground 知道去哪里找。下面是我使用的辅助方法来让这事变得简单:
func path To File In Shared Subfolder(file: String) -> String {
return XCPShared Data Directory Path + "/" + NSProcess Info.process Info().process Name + "/" + file
}
在 NSProcessInfo 的 processName 属性包含了 Playground 文件的名称,所以只要你已经在 Playground 的共享数据文件夹里创建了用相同名字命名的子文件夹,就可以很容易的访问这些文件,就像读本地的 JSON 一样:
var json Read Error:NSError?
let json Data = NSFile Manager.default Manager().contents At Path(path To File In Shared Subfolder("data.json"))!
let json Array = NSJSONSerialization.JSONObject With Data(json Data, options: nil, error: &json Read Error) as [Any Object]
…或者得到一个本地图片:
let image View = UIImage View()
image View.image = UIImage(contents Of File: path To File In Shared Subfolder("image.png"))
今年其余的读者意见的来自 Cédric Luthi,他(像去年或之前一样)贡献了很多的技巧和窍门值得占据一整篇文章。非常感谢,Cédric!
揭露 CocoaPods!
有一个快速方法来检查(闭源)应用程序使用的所有源:
$ class-dump -C Pods_ /Applications/Squire.app | grep -o "Pods_\w+"
CREATE_INFOPLIST_SECTION_IN_BINARY
查看 Xcode 中对命令行应用程序的设置
CREATE_INFOPLIST_SECTION_IN_BINARY
。它比-sectcreate__TEXT__info_plist
链接标志(linker flag)更容易使用,而且它把处理了的 Info.plist 文件嵌入了到二进制包中。
这也是在归档雷达的教训。在2006年,此功能被要求以
rdar://4722772
归档,在 7 年后才被认真对待。
阻止 dylib 钩子
来自 Sam Marshall 的这一招使黑客的生活更艰难:> >
把这一行加到你的 “Other Linker Flags” 里:
-Wl,-sectcreate,__RESTRICT,__restrict,/dev/null
NSBundle -preferredLocalizations
有时候,你需要知道你的应用程序在什么语言环境下运行。通常,人们会用
NSLocale +preferred
。不幸的是这除了告诉你应用程序实际上显示的语言之外一无所知。它只是给你 iOS 中 “Settings → General → Language & Region → Preferred Language” 或是 OS X 里 “System Preferences → Language & Region → Preferred Languages” 同样的有序列表。Languages
想象一下如果首选语言顺序是
{英语, 法语}
但是你的应用程序只支持德语。调用[NSLocale preferred
会返回英语而不是你想要的德语。Languages] first Object]
得到应用程序使用的准确语言环境的正确方式是使用
[[NSBundle main
。Bundle] preferred Localizations]
文档是这么说的:
一个
NSString
对象的数组包含了 bundle 里的区域语言 ID。这些字符串是按用户系统设置和可用本地化来排序的。
NSBundle.h
里的注释说:
这个 bundle 本地化的子集,会对这个进程的当前执行环境的优先顺序上重新排序;主 bundle 的首选本地化显示了用户是最有可能在 UI 看到的语言(文本)
你大概还需要使用
NSLocale +canonical
来确保规范的语言标识。Language Identifier From String:
保护 SDK 头文件
如果你是从 dmg 里安装的 Xcode,参考一下这个来自 Joar Wingfors 的方法,通过保留所有权、权限和硬链接的方式避免不小心修改了 SDK 的头文件:
$ sudo ditto /Volumes/Xcode/Xcode.app /Applications/Xcode.app
void *
的实例变量
检查 因为逆向工程的原因,非常有用的常用方法是查看对象的实例变量。它通常很容易通过
value
来达成,因为很少有类会重写For Key: +access
来禁止变量通过 Key-Value Coding 访问。Instance Variables Directly
但是有一种情况会让这个不起作用:当变量有一个
void *
类型的时候。
这有一个来自 iOS 6.1 里 MediaPlayer 库的摘录:
@interface MPMovie Player Controller : NSObject <MPMedia Playback>
{
void *_internal; // 4 = 0x4
BOOL _ready For Display; // 8 = 0x8
}
由于
id internal = [movie
不工作,有一个硬编码的方式访问内部变量:Player Controller value For Key:@"internal"]
id internal = *((const id*)(void*)((uintptr_t)movie Player Controller + sizeof(Class)));
不要发布这样的代码,这是非常不可靠的,因为变量布局可能会改变。只在逆向工程里使用!
NSDate Formatter +date Format From Template:options:locale:
友情提示:如果你在使用
NSDate
而不同时使用Formatter -set Date Format: NSDate
那么你很可能做错了。Formatter +date Format From Template:options:locale:
文档是这样的:
+ (NSString *)date Format From Template:(NSString *)template
options:(NSUInteger)opts
locale:(NSLocale *)locale
不同的语言对时间要素有不同的规范。你用这个方法来得到某个特定语言(通常使用当前的语言 - 参看 currentLocale)给定的时间要素的正确字符串格式。
下面的例子展示了时间在英国英语和美国英语下的不同格式:
NSLocale *us Locale = [[NSLocale alloc] init With Locale Identifier:@"en_US"];
NSLocale *gb Locale = [[NSLocale alloc] init With Locale Identifier:@"en_GB"];
NSString *date Format;
NSString *date Components = @"y MMMMd";
date Format = [NSDate Formatter date Format From Template:date Components options:0 locale:us Locale];
NSLog(@"Date format for %@: %@",
[us Locale display Name For Key:NSLocale Identifier value:[us Locale locale Identifier]], date Format);
date Format = [NSDate Formatter date Format From Template:date Components options:0 locale:gb Locale];
NSLog(@"Date format for %@: %@",
[gb Locale display Name For Key:NSLocale Identifier value:[gb Locale locale Identifier]], date Format);
// Output:
// Date format for English (United States): MMMM d, y
// Date format for English (United Kingdom): d MMMM y
调试器中得到内部常量
最近,Matthias Tretter 在 Twitter 上提问:
有没有人知道在 iOS 8 里面默认的动画时长和使 viewController 用模型方式展示的触发条件?
— Matthias Tretter (@myell0w) November 21, 2014</blockquote>
在 UIKit 的类堆栈里搜索 duration,找到了 UITransition View +default Duration For Transition:
方法,然后在那个方法上加个断点:
(lldb) br set -n "+[UITransition View default Duration For Transition:]"
展示一个模式视图控制器,就会进到这个断点,键入 finish
来执行这个方法:
(lldb) finish
在 default Duration For Transition:
执行的那个断点,你可以读到结果(在 xmm0
里):
(lldb) register read xmm0 --format float64
xmm0 = {0.4 0}
答案:默认时长是 0.4 秒。
DIY 弱关联对象
遗憾的是,关联对象 OBJC_ASSOCIATION_ASSIGN
的政策不支持零弱引用(zeroing weak references)。幸运的是,自己实现也很简单。你只需一个简单的类来封装一个弱引用的对象:
@interface Weak Object Containter : NSObject
@property (nonatomic, readonly, weak) id object;
@end
@implementation Weak Object Containter
- (instancetype)init With Object:(id)object {
self = [super init];
if (!self) {
return nil;
}
self.object = object;
return self;
}
@end
然后,用 OBJC_ASSOCIATION_RETAIN(_NONATOMIC):
关联 Weak Object Containter
objc_set Associated Object(self, &My Key, [[Weak Object Containter alloc] init With Object:object], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
用 object
属性来访问它以使得把零弱引用关联到需要的对象上:
id object = [objc_get Associated Object(self, &My Key) object];
就是这样,我们迎来了全新的充满可能和机会的一年。大家 2015 年快乐!
祝愿你继续编译你的代码并且得到鼓舞。