OS X/iOS 多线程编程小结

前言

我来填坑了。欠下的多线程,趁着假期无聊,赶紧码了。

好吧,如我的这篇博文《OS X/iOS 并发编程小结》最后一段所说,虽然queue那样的并发编程非常方便,但在音频、视频这些需求最小延时的情况下,queue并发编程即使有优先级也并不能保证任务在特定的时间得到执行。故而,这种情况下还是需要我们直接操作线程。

注:除开这种情况外,我建议(苹果也建议),各位还是乖乖用queue并发编程就好了。

什么是多线程

简而言之,主要有三个术语:线程、进程、任务。 这里不想给大家上操作系统课啦,大家不明白的google一下或者回去翻翻书就好。

APPLE的多线程技术

APPLE关于多线程的整个架构

image (注:NSOperationQueue是在GCD上面的)

ok,我一个一个介绍:

  1. 多线程底层实现的机制是Mach的线程。坦白的说,mach我压根不懂。苹果也说了,你几乎不会用到。不过如果你真的有兴趣研究,可以看这里:Kernel Programming Guide 里面的关于Mach的那部分。
  2. pthread(POSIX threads)。这是传统的多线程,C-based interface,非常灵活。如果你写的不是Cocoa应用,这是实现多线程的最好选择。
  3. NSThread,Cocoa的线程实现。当你在写Cocoa应用而又需要进行直接的线程操作的时候,我认为NSThread是比pthread更好的选择。虽然你依然可以使用pthread在Cocoa程序上,但是会有一些在Cocoa程序需要遵循的规则。我会在后面说明。
  4. GCD和NSOperationQueue略

同步工具

锁(locks),条件(condition),原子操作(atomic)

线程间通信

  1. Direct messaging: Cocoa应用可以直接perform某个selector在指定线程
  2. 全局变量,共享内存和对象。
  3. Conditions:一种特殊类型的锁
  4. Run loop sources:简单的来说,run loop是用来在线程上管理时间异步到达的工具。run loop能为线程监听一个或多个事件源(event sources)。run loop能把线程置于休眠状态,而当事件到达时,系统能唤醒线程并把事件分发给run loop,而后run loop能将事件分发给特定的handler。
  5. Ports and sockets:也使用run loop,不同之处在于可以进行多进程通信
  6. Message queues:历史遗物,一种多进程通信的玩意,才用FIFO的信息队列,但是有效率问题,
  7. Cocoa distributed objects:好高级的技术,可以在call不同cocoa应用的object,甚至跨越网络的不同计算机上的cocoa应用。

(老实说,后面几种我原先压根就没见过,为了弄懂他们是啥,看了好几篇文档。总结到这里,我的压力也感觉越来越大。觉得自己图样图拿衣服。。写了点多线程代码,就想总结OSX\iOS多线程编程。这才发现里面内容之多,细节之细令人发指。远不是整理一个并发编程能比的。我水平恐怕也就能堪堪掌握个大概框架,就操作系统课上的那点东西,跟生产环节下的多线程相比真是弱爆了。。)

所以从这里开始,后面我打算走实用主义,直接小结一下如何写多线程程序好了。

线程管理

使用NSThread创建线程

  • 类方法detachNewThreadSelector:toTarget:withObject:
  • 创建一个NSThread对象initWithTarget:selector:object:,并调用start方法

需要注意的是,这两种方法创建的线程,是分离(detach)的线程。detached的意思即,当线程退出的时候,系统会自动回收线程资源。当线程运行的时候,可以使用performSelector:onThread:withObject:waitUntilDone:modes来进行线程通信。其中modes用来指定run loop

注意:在大部分情况下,脱离线程更适用,因为它允许系统在线程完成的时候自动回收。如果你想创建可连接的线程(Joinable thread),唯一的办法是使用pthread。

使用pthread创建线程

参看pthread手册

需要注意的几点:

  1. 在cocoa程序上仅使用pthread,你需要先使用NSThread生成一个线程之后立即退出。就能保证Cocoa框架切换到多线程模式,来启用一些锁或者其他的同步技术来保证Cocoa框架代码的正确执行。如果你不这么做,当涉及到Cocoa框架的操作时有可能会照成不可预料的后果。
  2. pthread和Cocoa的锁是完全可以混用的,(我就这么做过,因为pthread的锁更灵活),但是对于指定的一个锁,必须使用同类型的接口来操作,即你不能同时用pthread和NSLock同时操作一个锁。

使用NSObject创建线程

performSelectorInBackground:withObject:也会生成一个脱离线程。你还可以在该线程里面使用performSelectorOnMainThread:withObject:waitUntilDone:modes:

线程的优先级

创建的线程的优先级和所处在的线程是相同的,优先级高的线程比优先级低的线程能获得更多运行机会,但并不能保证线程的具体执行时间和顺序。内核的调度算法会决定该运行哪个线程。

NSThread 的 setThreadPriority:以及pthread_setschedparam 可以改变线程的优先级。

注意:一般来说,保持默认优先级是一个不错的选择。更改某些线程的优先级,会增加某些较低优先级的线程的饥饿。若高优先级和低优先级的线程又有交互的需求,那么低优先级就有可能因为得到运行机会的难度而阻塞其他线程,造成线程瓶颈。

关于自动释放池 (autorelease pool)

autorelease pool用于自动释放pool里面捕获的autorelease对象。

理论上来说,每一个线程都应该有一个autorelease pool,主 线程在main.m里面XCode会为你创建一个。但当你创建一个线程的时候,你第一件应该做的事,就是创建一个autorelease pool。

非ARC之前,你使用NSAutoreleasePool,之后你可以使用@autoreleasepool。

注意:在ARC环境下你仍然不能忽略autorelease pool,因为ARC仍然使用autorelease来进行release操作,且他并不会为你自动的创建autorelease pool。BTW,除了在线程里面你会需要autorelease pool之外,在次数巨大或者多重的for-loop里面你也会使用autorelease pool,来避免一次alloc和release数量巨大的对象。

中断线程

(未完待续。。。写不动了。。)

Run Loop 详解

线程同步 详解

Cocoa的线程安全

iOS

Modern Objective-C

前言

Objective-C已经出道了30多年,即使是iOS,也已经面世了5年之久,期间Objective-C和LLVM Clang都已经有了巨大的变化,这篇文章主要是描述一下Modern Objective-C Style。

在写这篇文章的时候,我本打算完整的描述一下我理解的现代Objective-C是如何,然后发现唐巧的blog上已经有一了一篇很好的博文:Objective-C新特性,大家可以先行阅读,我主要对他的博文进行补充。

使用ARC

我不想解释更多了,ARC虽说不是万能的,但在工程效率高于一切,速度就是王道的今天,ARC可以省下大量的时间。更何况,为什么总有人认为这些机械的内存管理,自己要做的比机器好呢?

想了解更多的ARC的信息,推荐Ray的arc教程

  • http://www.raywenderlich.com/5677/beginning-arc-in-ios-5-part-1
  • http://www.raywenderlich.com/5773/beginning-arc-in-ios-5-tutorial-part-2

    New Object Literals 和 Subscripting

    参见唐巧的博文

    关于instance variables和property和methods

    我之前写了一篇博文,引起了一些朋友的讨论。这里主要描述,在modern objective-c下,我们怎么写类里面的各种成员。

  • 你不再需要声明声明property的时候又声明instance variables,当你声明一个property的时候,编译器就会自动的帮你声明一个instance variable。

  • 你不再需要@synthesize,当你声明了@property的时候,编译器会自动帮你@synthesize,同时帮你指定好你的instance variable的名称为_var。
  • 即使你确实喜欢自己声明instance variables和自己@synthesize,那么你也不应该违反_var的命名形式。
  • 你不再需要Forward declaration,(只适用OBJC)
  • 把所有私有的东西从.h文件移动到.m文件

强调一下,上面说的第五条,把所有私有的东西从.h文件移动到.m文件,可以说就是“old-fashioned” 和 modern OBJC的最大区别。具体有如下几点:

  1. 你可以把需要私有的property从h文件移到m文件,这样外界将无法直接访问。
    • 记住IBOutlet也是可以移到m文件的。
  2. 你可以把私有的instance variables从h文件移到m文件。这里可以移到两个地方:
    • .m文件的@interace
    • .m文件的@implementation
  3. 你可以把类需要实现的protocol从h文件移到m文件,当你认为其不需要暴露给外部知道时。
  4. 如之前所说,你不再需要前置声明,所以私有方法,可以直接从.h文件中删除。当然你也可以放在.m文件的@interface里。
    • 记住IBOutlet也可以直接连到.m文件中,不需要连到@interface里面做前置声明。

注意:有时候你会奇怪,为什么有的程序有instance variables,有些却没有。这里暂时还没有一个明确的定论,只是不同风格有不同的写法。有些人喜欢为所有的东西创建property,而有些人却不愿意为私有成员创建property。

就我个人而言,是习惯于为所有的东西创建property的,然后将私有的proerty移到m文件中。为什么使用property呢?这里有一个来自苹果工程师Paul(在stanford上课的那位)在课上的解释:

Why property?

Most importantly, it provides safety and subclassablility for instance variables. Also provides “value” for lazy instantiation, UI updating, consistency checking, etc.

使用Block

  • 可以使用block来代替delegate
  • 可以使用block来遍历容器。

Block通常意味着Do more with less code

关于property的权限,对外readonly对内readwrite的property

你可以在.h文件里设置一个权限readonly的property,并在.m文件设置一个权限为readwrite的同样的property,这时,你的property对外是只读的,对内却是可读写。

EDIT:补充关于最后这个的样例说明 在类的.h文件的interface下声明一个

1
@property (nonatomic, strong, readonly) NSString *testString;

再在.m文件的interface下声明一个

1
@property (nonatomic, strong, readwrite) NSString *testString;

此时在类内部可以对该变量进行修改,但在类外部修改会被编译器报错为readonly。

iOS

OS X/iOS 并发编程小结

简洁

这里不讨论传统的多线程编程,而讨论OS X/iOS特有的异步技术,GCD和Operation Queue。

  • Grand Central Dispatch (GCD): C语言的系统管理线程,不需要编写线程代码,只需要定义需要执行的任务,然后以block的形式添加到适当的dispatch queue中,系统就会负责线程的创建和任务调度。
  • Operation Queue: Cocoa的系统管理线程,与GCD类似。

Operation Queues

基于cocoa的应用通常会使用Operation Queues

Operation Objects

NSOperation是抽象基类,需要实现相应子类。不过Cocoa提供了两个具体的子类NSInvocationOperation和NSBlockOperation。其中NSInvocationOperation以@selector来创建operation object;NSBlockOperation以block来创建operation object.(这里暂且不讨论自己实现NSOperation).

所有的operation objects都支持这些特性:

  • 依赖关系,可以阻塞某个operation,直到他所依赖的所有operation都已经完成
  • 可以设置completion block
  • 可以通过KVO来监控operation的状态
  • 可以设置operation的优先级
  • 可以取消

创建operation object之后,加入到适当的operation queue即会立刻开始执行。

Operaton queue

  • 可以设置并发执行的operation 数量,设为1,即为串行队列
  • 可以暂时挂起,继续,等待直到完成

Dispatch Queues

GCD使用block来创建任务,切任务总是以添加的顺序开始顺序执行。有串行队列,也有并发队列,还有主线程队列。

注:GCD相关技术还有,Dispatch group:监控一组block对象完成;Dispatch semaphore:类似于传统的信号量;Dispatch source:在特定类型的系统事件发生时产生通知。

管理和创建Dispatch Queue

  • dispatch_get_global_queue 获得全局共享并发队列
  • dispatch_queue_create 创建串行队列
  • 可以通过dispatch_set_context和dispatch_get_context来管理自定义的上下文信息,finalizer可以销毁上下文。
  • dispatch_async 异步添加任务到queue,dispatch_sync同步添加(尽量少用)
  • 据说可以添加completed block,但是实际上就是再添加一个block,(感觉略无力,不敢用)
  • dispatch_apply可以执行循环迭代

Dispatch Semaphore

  1. dispatch_semaphore_create创建信号量,指定可用资源数
  2. dispatch_semaphore_wait等待可用资源数
  3. dispatch_semaphore_signal释放信号量

Dispatch group

  1. dispatch_group_create 创建group
  2. dispatch_group_async 添加到group
  3. dispatch_group_wait 阻塞线程直到group完成

Dispatch source


备注:queue 不是替代线程的万能药!queue 提供的异步编程模型适合 于延迟无关紧要的场合。虽然 queue 提供配置任务执行优先级的方法, 但更高的优先级也不能确保任务一定能在特定时间得到执行。因此线程 仍然是实现最小延迟的适当选择,例如音频和视频 playback 等场合。

(上面这一条让我吐死。。看了几十页的文档,写了n多代码。。发现real time是不适用的。。然后开始用多线程。。过两天在补一个多线程编程小结。。)

iOS

Cocoa代码规范官方指南(要点与简译)

背景

我一年前的时候看这篇的时候,对cocoa还处在刚入门阶段,很多细节并没有留下太深刻的影响。如今经常阅读别人的代码了之后,越来越体会到代码规范的重要性,故而重新找回这一篇,又仔细读了一遍,收获颇丰。本着善待别人就是善待自己的原则,就顺手翻译了一遍,希望每个人都有收获,能写规范的代码,让别人看得舒服。

英文好的同学,建议直接看英文,因为很多东西我都不知道怎么翻译,这一篇原名叫 Coding Guidelines for Cocoa

命名基础

一般原则

清楚

  • 在保证清楚的前提下才能简化
Code Commentary
insertObject: atIndex: Good.|
insert:at: Not clear; what is being inserted? what does “at” signify?|
removeObjectAtIndex: Good.|
removeObject: Good, because it removes object referred to in argument.|
remove: Not clear; what is being removed?|
  • 一般来说,不要使用缩写,即使他的全称很长
Code Commentary
destinationSelection Good.|
destSel Not clear.|
setBackgroundColor: Good.|
setBkgdColor: Not clear.|
  • 只有一些少数的缩写的确是为绝大多数人所知的,可以继续使用(见附录)
  • 避免含糊不清API命名,比如能够以一种以上的方式进行解释的名称
Code Commentary
sendPort Does it send the port or return it?|
displayName Does it display a name or return the receiver’s title in the user interface?|

一致性

  • 使用和cocoa一致的命名风格,如果你不确定,可以参看头文件和文档
  • 一致性在你的类方法使用了多态性的情况下尤为重要,不同类却做相同事的方法应该有相同的名字
Code Commentary
– (NSInteger)tag Defined in NSView, NSCell, NSControl. |
- (void)setStringValue:(NSString *) Defined in a number of Cocoa classes. |

不要自参考(No self-reference)

  • 命名不要自参考
Code Commentary
NSString Okay. |
NSStringObject Self-referential.|
  • 常量是这个原则的例外,比如mask,比如notification,(译者注:比如encoding)
Code Commentary
NSUnderlineByWordMask Okay. |
NSTableViewColumnDidMoveNotification Okay. |

前缀

因为没有命名空间,所以前缀是很必要的

  • 前缀也有指定的格式,两到三个字母的大写缩写,不要使用下划线
Prefix Cocoa Framework
NS Foundation |
NS Application Kit |
AB Address Book |
IB Interface Builder |
  • 命名类,协议,函数(译者注:这里是指c-style函数),常量,typedef结构使用前缀。命名类方法时不要使用前缀,因为class已经定义了他们。也不要在命名the fields of structure使用前缀。(译者注:不知道如何翻译,求各路大神指点)

命名约定

  • 使用驼峰法命名,即不要使用任何分隔符,而使用单词首字符大写的方式来分割。要额外注意下面两点:

    • 方法首字母小写,不要使用前缀

      objective-c fileExistsAtPath:isDirectory: 一个例外,当使用广为人之的缩写的作为方法开头时,比如TIFFRepresentation (NSImage).

    • 定义方法和常量的时候,使用相关前缀并第一个单词首字母大写

      objective-c NSRunAlertPanel NSCellDisabled

  • 不要使用下划线来标记私有方法,这是苹果的保留字段。冒然使用会导致不可知的冲突。(译者注:或者上不了架)

类和协议的命名

类的名字应该包含一个名词,用来指明这个类(或者这个类的对象)表示什么,或者做了什么。类名需包含一个适当的前缀

协议的命名取决于协议如何组织行为

  • 大多数协议将一些相关的方法组合在一起,但是又不关联特定的类,这样的协议命名需要注意不被误认为一个类,通常的做法,是使用动名词形式(-ing)
Code Commentary
NSLocking Good. |
NSLock Poor (seems like a name for a class). |
  • 有一些协议将一些不相关的方法组合在一起,却是为了指向特定的类,这时可以将协议命名为于类名相同。一个例子就是NSObject protocol。(译者注:这个情况有点像java里的interface,应用这种协议的方式就能达到相同接口不同实现的效果。虽然我从没见过有人这么做。如果这里我有理解错的地方,各路大神请斧正)

头文件

  • 声明一个单独的类或协议,比如NSLocale.h 声明 The NSLocale class.
  • 声明关联的类和协议,比如NSString.h 声明 NSString and NSMutableString classes;再比如NSLock.h 生命了NSLocking protocol and NSLock, NSConditionLock, and NSRecursiveLock classes.
  • 包含框架头文件,比如Foundation.h包含Foundation.framework.
  • 添加API到另一个框架的类,If you declare methods in one framework that are in a category on a class in another framework, append “Additions” to the name of the original class; an example is the NSBundleAdditions.h header file of the Application Kit.
  • 相关的函数或者类型,If you have a group of related functions, constants, structures, and other data types, put them in an appropriately named header file such as NSGraphics.h (Application Kit).

方法命名

一般原则

  • 首字母小写,驼峰命名法,不要使用前缀
  • 对于对某个对象进行操作的方法,以一个动词开头,比如
1
2
- (void)invokeWithTarget:(id)target;
- (void)selectTabViewItem:(NSTabViewItem *)tabViewItem

不要使用do和does,因为他们没有添加任何说明意义,也不要在动词前加副词和形容词

  • 如果方法返回一个属性,则可以以属性为开头命名方法名,“get”是不必要的,除非是间接的返回。
1
2
3
- (NSSize)cellSize;  //Right.
- (NSSize)calcCellSize; //Wrong.
- (NSSize)getCellSize; //Wrong.
  • 在所有参数前使用关键词
1
2
- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag; // Right
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag; // Wrong
  • 使用描述性的关键字描述参数
1
2
- (id)viewWithTag:(NSInteger)aTag; //Right.
- (id)taggedView:(int)aTag; // Wrong.
  • 在子类里面扩展父类已有的方法时,再原来的方法后面添加关键字,比如:
1
2
3
4
5
6
- (id)initWithFrame:(CGRect)frameRect; //NSView, UIView.
- (id)initWithFrame:(NSRect)frameRect
mode:(int)aMode cellClass:(Class)factoryId
numberOfRows:(int)rowsHigh
numberOfColumns:(int)colsWide;
// NSMatrix, a subclass of NSView 
  • 参数之间不要使用“and”连接
1
2
3
4
- (int)runModalForDirectory:(NSString *)path file:(NSString *)
name types:(NSArray *)fileTypes;        //Right.
- (int)runModalForDirectory:(NSString *)path andFile:(NSString
*)name andTypes:(NSArray *)fileTypes;   //Wrong.
  • 当一个方法执行两个不同操作时,使用“and”
1
- (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag;

属性访问方法

  • 如果property表现为一个名词,则
1
2
- (type)noun;
- (void)setNoun:(type)aNoun;
  • 如果property是形容词,则
1
2
- (BOOL)isEditable;
- (void)setEditable:(BOOL)flag;
  • 如果property是动词,则
1
2
- (BOOL)verbObject;
- (void)setVerbObject:(BOOL)flag;
  • 不要把动词分词形式当初形容词用,例如:
1
2
3
4
- (void)setAcceptsGlyphInfo:(BOOL)flag; //Right.
- (BOOL)acceptsGlyphInfo; //Right.
- (void)setGlyphInfoAccepted:(BOOL)flag; //Wrong.
- (BOOL)glyphInfoAccepted; //Wrong.
  • 可以使用情态动词(can,show,will等),但是不要使用do,does
1
2
3
4
5
6
- (void)setCanHide:(BOOL)flag; //Right.
- (BOOL)canHide; //Right.
- (void)setShouldCloseDocument:(BOOL)flag; //Right.
- (BOOL)shouldCloseDocument; //Right.
- (void)setDoesAcceptGlyphInfo:(BOOL)flag; //Wrong.
- (BOOL)doesAcceptGlyphInfo; //Wrong.
  • “get”只用于间接返回对象或数值,你应该只在需要返回多个值的适合使用这样的形式:
1
- (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase;

在这样的方法里,你应该允许接受NULL作为参数,当呼叫者不关心多个返回值时。

代理方法

  • 用能指明发送信息的类的名字作为delegate方法名的开头
1
2
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
  • 其他的参数在附在前一条的后面,除非只有一个参数the sender
1
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;
  • post notification的方法为例外,这时,单个参数为notification对象
1
- (void)windowDidChangeScreen:(NSNotification *)notification;
  • 用“did”和“will”来表示某些事已经发生或即将发生
1
2
- (void)browserDidScroll:(NSBrowser *)sender;
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window;
  • 询问delegate,某事要发生时是否允许,使用should
1
- (BOOL)windowShouldClose:(id)sender;

容器方法(collection methods)

  • 一般形式
1
2
3
- (void)addElement:(elementType)anObj;
- (void)removeElement:(elementType)anObj;
- (NSArray *)elements;
  • 如果容器是明显无序的,返回NSSet来替代NSArray。
  • 插入和删除
1
2
- (void)insertLayoutManager:(NSLayoutManager *)obj atIndex:(int)index;
- (void)removeLayoutManagerAtIndex:(int)index;
  • 实现细节上的注意事项:
    • 一般来说,插入的对象包涵所有关系,所以在你插入对象的时候,应该retain他们,删掉他们的时候,应该release。
    • If the inserted objects need to have a pointer back to the main object, you do this (typically) with a set… method that sets the back pointer but does not retain. In the case of the insertLayoutManager:atIndex: method, the NSLayoutManager class does this in these methods:
1
2
- (void)setTextStorage:(NSTextStorage *)textStorage;
- (NSTextStorage *)textStorage;

You would normally not call setTextStorage: directly, but might want to override it. (译者注:这个情况我也不是很熟悉,为避免让人误解,故保留原文,有大神准确的明白这里指什么的,请指教)

方法参数

对于方法的参数的命名,有一般原则:

  • 小写开头的驼峰命名(for example, removeObject:(id)anObject).
  • 不要使用“pointer”或“ptr”,让参数的类型来说明这些
  • 避免一个火两个字母的参数
  • 避免使用不必要的缩写

cocoa的惯例命名如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...action:(SEL)aSelector
...alignment:(int)mode
...atIndex:(int)index
...content:(NSRect)aRect
...doubleValue:(double)aDouble
...floatValue:(float)aFloat
...font:(NSFont *)fontObj
...frame:(NSRect)frameRect
...intValue:(int)anInt
...keyEquivalent:(NSString *)charCode
...length:(int)numBytes
...point:(NSPoint)aPoint
...stringValue:(NSString *)aString
...tag:(int)anInt
...target:(id)anObject
...title:(NSString *)aString

私有方法

大部分情况下,私有方法的命名和共有方法一样,然而,一个常见的惯例是给私有方法前加一个前缀来标示这是一个私有方法。Coca frameworks里使用下划线前缀来标示私有。因此,

  • 不要使用下划线作为前缀来标示你自己实现的私有方法,苹果已经保留了这种形式
  • 如果你继承了一个Cocoa framework class,为了确保你的私有方法与其不一样,你应该加上你自己的前缀。前缀应该尽量唯一化,一般来说为“XX_”,XX可以是你的公司或项目缩写。

虽说这里使用前缀违反了前面说的,类方法不要使用前缀,而是由类本身作为命名空间的原则,但,在这里的意图不一样,这里的意图是为了避免非故意的重写父类的私有方法。 (译者注:OBJC没有真正意义上的私有,即使是使用了如上这样的标示方法,也只是提醒他人这个函数是私有函数,并没有强制的保证别人无法访问。另一种常用的标示私有的做法,是把私有方法的声明放在m文件,而不是h文件)

函数命名

一般原则

  • 函数的命名和方法相似,不过又一些例外
    • 使用和类名或常量相同的前缀
    • 前缀后第一个字母大写
  • 大多数函数以动词开头来描述函数的功能:
1
2
NSHighlightRect
NSDeallocateObject

请求属性的函数,有另一些规则 * 如果函数返回一个来自其第一个参数的属性,则省略动词

1
2
unsigned int NSEventMaskFromType(NSEventType type)
float NSHeight(NSRect aRect)
  • 如果返回的值是引用,则使用“get”
1
const char *NSGetSizeAndAlignment(const char *typePtr, unsigned int *sizep, unsigned int *alignp)
  • 如果返回的值是bool,
1
BOOL NSDecimalIsNotANumber(const NSDecimal *decimal)

属性和数据类型命名

属性和实例变量

一般来说和属性访问方法的原则相同,例如:

1
2
@property (strong) NSString *title;
@property (assign) BOOL showsAlpha;

如果是形容词,可以这样

1
@property (assign, getter=isEditable) BOOL editable;

变量命名需简明的描述储存的属性。一般来说,你不应该直接访问变量,而是使用访问方法,(你只在init和dealloc里面直接访问变量)。

There are a few considerations to keep in mind when adding instance variables to a class: * Avoid explicitly declaring public instance variables. Developers should concern themselves with an object’s interface, not with the details of how it stores its data. You can avoid declaring instance variables explicitly by using declared properties and synthesizing the corresponding instance variable. * If you need to declare an instance variable, explicitly declare it with either @private or @protected. If you expect that your class will be subclassed, and that these subclasses will require direct access to the data, use the @protected directive. * If an instance variable is to be an accessible attribute of instances of the class, make sure you write accessor methods for it (when possible, use declared properties).

(译者注:在这部分上我持保留意见,这应该不是现代的做法,事实上,我还没有见过有人使用@private,@protected,我自己尝试在代码里面使用这个关键词却被编译器报错。为避免引起人误解,这里保留原文,欢迎大家讨论)

常量

枚举常量

  • 用枚举来给组合一组相关的常量,其类型通常为int
  • 使用和函数命名相同的规则,如
1
2
3
4
5
6
typedef enum _NSMatrixMode {
    NSRadioModeMatrix = 0
    NSHighlightModeMatrix = 1
    NSListModeMatrix = 2
    NSTrackModeMatrix = 3
} NSMatrixMode;

注意这里的 typedef 标签并不是必须的。 * 可以创建没有命名的枚举

1
2
3
4
5
6
7
enum {
    NSBorderlessWindowMask      = 0,
    NSTitledWindowMask          = 1 << 0,
    NSClosableWindowMask        = 1 << 1,
    NSMiniaturizableWindowMask  = 1 << 2,
    NSResizableWindowMask       = 1 << 3
};

const常量

  • Use const to create constants for floating point values. You can use const to create an integer constant if the constant is unrelated to other constants; otherwise, use enumeration.
  • 使用和函数命名相同的规则

    其他类型的常量

  • 一般来说不要使用 #define 来创建常量
  • 使用大写字母来进行预处理标示,判断一个代码块是否需要处理,比如
1
#ifdef DEBUG
  • macro使用两个下划线前缀和后缀,比如:
1
__MACH__
  • Define constants for strings used for such purposes as notification names and dictionary keys. By using string constants, you are ensuring that the compiler verifies the proper value is specified (that is, it performs spellchecking).TheCocoa frameworks provide many examples of string constants, such as: objective-c APPKIT_EXTERN NSString *NSPrintCopies; The actual NSString value is assigned to the constant in an implementation file. (Note that the APPKIT_EXTERN macro evaluates to extern for Objective-C.) (译者注:我没法使用APPKIT_EXTERN,查看了一下cocoa头文件,发现其使用的是FOUNDATION_EXPORT,为避免引起误解,这里也保留原文)

通知和异常

通知

Global NSString objects

1
[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification

例如:

1
2
3
4
NSApplicationDidBecomeActiveNotification
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification

异常

Global NSString objecsts

1
[Prefix] + [UniquePartOfName] + Exception

例如:

1
2
3
4
5
NSColorListIOException
NSColorListNotEditableException
NSDraggingException
NSFontUnavailableException
NSIllegalSelectorException

接受的缩写

Abbreviation Meaning and comments
alloc Allocate. |
alt Alternate. |
app Application. For example, NSApp the global application object. However, “application” is spelled out in delegate methods, notifications, and so on. |
calc Calculate. |
dealloc Deallocate. |
func Function. |
horiz Horizontal. |
info Information. |
init Initialize (for methods that initialize new objects). |
int Integer (in the context of a C int—for an NSInteger value, use integer). |
max Maximum. |
min Minimum. |
msg Message. |
nib Interface Builder archive.
pboard Pasteboard (but only in constants). |
rect Rectangle. |
Rep Representation (used in class name such as NSBitmapImageRep). |
temp Temporary. |
vert Vertical. |

接受的大写缩写词: ASCII PDF XML HTML URL RTF HTTP TIFF JPG PNG GIF LZW ROM RGB CMYK MIDI FTP

Tips and Techniques for Framework Developers

frameworks的开发者需要比其他的开发者更小心。许多用户应用都可能会连接到这些framework,因正因为如此framework的任何都不足,都将被放大。接下来描述的技术可以用于保证你的框架的效率和可靠性。 注意:其中的一些技术不单单适于用框架,你也可以用于应用开发。 (译者注:这一节比较复杂,因为我没开发过frameworks,所以就不冒然翻译了,待我研究清楚后,另开一篇详说。有兴趣的,也可以自行查看原文)

Contact Me

iOS

Bayesian Filtering

About

贝叶斯过滤算法是一种基于统计学的过滤算法,它使用贝叶斯分类来进行特定类别文本的判别和过滤。

Naive Bayes classifier

朴素贝叶斯分类器是一种应用基于独立假设的贝叶斯定理的简单概率分类器,这种潜在的概率模型称为独立特性原型。 简单的说,朴素贝叶斯分类器假设样本的每个特征都是独立的与其他特征不相关的,尽管这些特征可能存在相互依赖,或者一些特征由其他特征而决定。

概率模型(源自贝叶斯定理)

公式

公式一 Pr(H | T) = Pr(T | H)·Pr(H) / [Pr(T | H)·Pr(H) + Pr(T | M)·Pr(M)]

其中:

  • Pr(H | T)代表当一条文本有token T存在的时候,命中指定类别文本的概率
  • Pr(H)代表对于任意一条文本,命中指定类别文本的概率
  • Pr(T | H)代表token T出现在命中指定类别文本中的概率
  • Pr(M)代表对于任意一条文本,非命中指定类别文本的概率
  • Pr(W | H)代表token T出现在非命中指定类别文本重的概率

通常情况下,我们会假定Pr(H) = Pr(M) = 0.5,即普遍命中概率和普遍非命中概率相等,这种假定是因为我们不想对出现的文本产生偏见关注。在这个假定下,我们可以将公式化简为:

公式一(简) Pr(H | T) = Pr(T | H) / [Pr(T | H) + Pr( T | M)]

合并独立概率

朴素贝叶斯分类器假定每个特征(该应用中为token)都是独立的,则我们可以使用合并概率公式:

公示二 P = P2·P2···Pn / [P1·P2···Pn + (1 - P1)(1 - P2)···(1 - Pn)]

其中:

  • P 即为该文本命中指定类别文本的概率
  • Pi (i = 1..n)当文本中出现某一token i,的时候,该文本命中指定类别文本的概率。(上面的Pr(H | T))

实现案例——贝叶斯过滤算法在抽奖微博识别的应用

功能

鉴别给定的微博,判断其是否为抽奖微博,从而为后续操作,比如过滤或者自动参与抽奖,提供基础。

程序设计

  1. 首先收集一定数量的抽奖微博和非抽奖微博,存在不同的两个文件(hitFileName.txt; misFileName.txt

  2. 将两个文件分别读入两个List(hitStringList, misStringList
  3. 对List里的每个string,进行tokenization,并加到对应的两个countTable(dict),(hitCountTable, misCountTable),countTable用于统计每个token出现的次数。

    • 例:hitCountTable[token]表示token在命中文本中出现的次数)
  4. countTable转换为对应的probabilityTable,(hitProbabilityTable, misProbabilityTable):单个token出现的次数 / 整个表所有token出现的次数)
    • 例:hitProbabilityTable[token]表示token在命中文本重出现的概率
  5. 用公式一,由hitProbabilityTablemisProbabilityTable求得tokensProbabilityTable

    • tokensProbabilityTable[token]表示当一条文本有token存在的时候,命中指定类别文本的概率
  6. 由给定string,分词后,找出它们其在tokensProbabilityTable的概率,用公式二,既可以求出该文本命中指定类型文本的概率

代码

代码在Github开源托管传送门

Contact Me

从一段奇葩的objc代码看代码规范的重要性

背景介绍

昨天,在和我一个朋友讨论,到底是用self.propertyName还是_propertyName来访问property,我认为应该使用self.propertyName,因为我在听Stanford Open Course的时候,苹果的工程师告诫要使用self.propertyName,不要使用_propertyName。而朋友认为应该使用_propertyName,因为google objc code style认为最好不要用self.propertyName

我没看过google objc code style,我只看过objective c programming guide。在我的理解里property的作用在于根据参数生成相应的getter和setter。self.propertyName本质上既是调用getter函数的,而_propertyName直接访问成员函数,因为相应参数生成的getter和setter是不会被调用的。

再说,我还是决定相信apple,而不是google,毕竟Objc还是apple在支持和维护。

上代码

重点来了,朋友为了说服我self.property是有问题的,发了一段代码过来,这段代码非常奇葩,可以点这里下载,或者直接看代码,代码不算很长,简单的说,是要实现一个功能,一个tableview右上角有一个刷新按钮,每次刷新会改变dataArray(setupData),然后刷新tableview。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#import "ViewController.h"

@interface ViewController () <UITableViewDataSource, UITableViewDelegate>

@property (strong, nonatomic) UITableView *tableView;
@property (strong, nonatomic) NSArray *dataArray;
@property (assign, nonatomic) BOOL flag;

@end

@implementation ViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self)
    {
        //按这个按钮本来是tableview会变化的,但是现在调用了reloadData之后,不会调用cellForRowAtIndexPath这个方法。
        UIBarButtonItem *rightItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self action:@selector(add:)];
        self.navigationItem.rightBarButtonItem = rightItem;

        //设置数据源
        [self setupData];
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.view addSubview:self.tableView];
}

#pragma mark - getter & setter

- (UITableView *)tableView
{
    if (_tableView == nil)
    {
        _tableView = [[UITableView alloc] initWithFrame:self.view.bounds
                                                  style:UITableViewStyleGrouped];
        _tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
        _tableView.delegate = self;
        _tableView.dataSource = self;
    }
    return _tableView;
}

#pragma mark - UITableView Delegate & Datasource

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.dataArray.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *identifier = @"settingcell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
    if (cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
    }

    cell.textLabel.text = [self.dataArray objectAtIndex:[indexPath row]];

    return cell;
}

- (void)setupData
{
    if (self.flag)
        self.dataArray = [NSArray arrayWithObjects:@"1", @"2", @"3", @"fuck", nil];
    else
        self.dataArray = [NSArray arrayWithObjects:@"1", @"2", @"3", nil];
    [self.tableView reloadData];

}

- (void)add:(id)sender
{
    self.flag = !self.flag;
    [self setupData];
}

@end

问题

这段代码是无效的,按下按钮之后,setupData被调用了,已经log确定dataArray已经改变,tableviewdelegatedatasource都设置正确,确定numberOfRowsInSection被调用,奇葩的是cellForRowAtIndexPath没有调用,故而tableview没有改变。

奇葩的来了

朋友跟我说,你只要把[self.tableview reloadData]改成[_tableview reloadData],他就生效了。是的,他就生效了。你设一个断点在这个地方,然后把self.tableview_tableviewpo出来,发现他们的指针是一样的。朋友说写这个代码的那货折腾了一天,百思不得其解,最后得出结论self.propertyName就是坑爹。

生效的修改方法

朋友提供的:

  1. 前面说的讲把[self.tableview reloadData]改成[_tableview reloadData]
  2. tableview的getter函数的init里面的self.view.bounds改成CGReckMake(0,0, 320, 480)

朋友试图用这个两个方法来说明,self.property是坑爹的。

我在初步debug的时候,由于我是property的拥护者,property自动生成setter和getter函数,我是不支持重写getter函数的,所以我将getter函数删掉,把初始化代码移到viewdidload里面。然后代码就生效了。

但是即使代码生效了,还是没有找到问题的关键,仍然没办法解释为什么[self.tableview reloadData]改成[_tableview reloadData]就能运行了,因为po出来的指针是完全一样的,这不科学。

真正的问题所在

在各种Stackoverflow,google无果之后,我还是着手准备深入debug。

通过各种断点和gdb,最后打印函数调用栈才让我发现了真正的问题所在。

整个程序的执行顺序是这样的:

  1. initWithNibName(执行到[self setupData],没执行完) –>
  2. 第一次setupData(执行到[self.tableView reloadData],没执行完) –>
  3. 第一次执行tableview getter(到init,调用self.view,没执行完)->
  4. viewDidLoad(到addSubview:self.tableView, 没执行完) –>
  5. 第二次执行tableview getter(问题在这里!第一次执行的时候没有init玩,所以又会执行一次!)->
  6. 回到4.viewDidLoad,这是add的subview是第二次的init而先init完的tableview –>
  7. 回到3.第一次执行getter,(又alloc了一次tableView,这是self.property指向的是第一次init而后init完成的tableview))

所以,显示在界面上的tableview根本不是self.tableview指向的tableview,故而根本没法刷新(cellForRowAtIndexPath,是当需要显示的时候才会调用的)。

那为什么把[self.tableview reloadData]改成[_tableview reloadData]就能生效了呢?因为这样在initWithNibName的第一次调用setupData,就不会在reload的时候调用tableview getter,也就不会有后面一连串的连锁反应。之后顺利在viewdidload的时候只调用一次,完成init。

知道了问题的关键,还能有各种各样让他生效的方法,就不吐槽了。

正确的写法

这段奇葩代码带给我最大的感触就是,不好好写规范的代码,各种问题都会坑死你。我认为规范的写法应该是

  1. 不要重写getter和setter函数,使用property生成的getter和setter
  2. 不要在vc的init的函数里面初始化,尤其是初始化视图。而应该在viewdidload里面初始化,保证self.view已经生成。(非ARC环境下还需要注意memory warning导致的viewdidload多次加载而多次初始化所带来的内存泄露问题。最安全的做法是lazy instantiation)
  3. 应该使用自顶向下的程序设计方法,保证程序的顺序执行和层次关系。不应该出现如上程序的跳来跳去的调用。

后记

帮人debug还是有好处的,让我结识了这位bug兄。也让我更加深入的了解了cocoa的变量访问机制,debug的时候顺带还测试了KVO。

Edit

我又重新去看了property和getter,setter的资料,也看了苹果对property的解释。最后我修正关于不要重写getter和setter函数的观点,更正为可以重写getter和setter,目的可以为lazy instantiation, UI updating, consistency checking,等。但需要注意如上程序的连锁反应。代码的灵活性和安全性

关于@property,经过和大家的讨论也有了一个结论:

Why property?

Most importantly, it provides safety and subclassablility for instance variables. Also provides “value” for lazy instantiation, UI updating, consistency checking, etc.

Lancy

11.27.2012

iOS

Objective-C Associative References(关联引用)

About

在研究Objc的运行时特性的时候,发现了一个有意思的东东,Associative Reference关联引用。使用关联引用,能够模拟添加一个对象实例到一个已有的类中,能够添加存储到一个对象中而不需要改变类的定义。这个技术在你不能访问源码的时候有用,或者你只是觉得动态的增加关联很好玩。

创建关联

可以使用 objc_setAssociatedObject 来创建关联引用。

1
2
3
4
5
6
7
8
9
static char overviewKey;
NSArray *array = @[@"One", @"Two", @"Three"];
NSString *overview = @"First three numbers";
objc_setAssociatedObject (
                          array,
                          &overviewKey,
                          overview,
                          OBJC_ASSOCIATION_RETAIN
                          );
  • key是一个 void 指针,对于每个关联,key必须唯一,通常可以使用一个 static variable.
  • police 用来指定,关联的对象是 assigned, retained, copied, 还有是否是atomically.
    • OBJC_ASSOCIATION_ASSIGN Specifies a weak reference to the associated object.
    • OBJC_ASSOCIATION_RETAIN_NONATOMIC Specifies a strong reference to the associated object, and that the association is not made atomically.
    • OBJC_ASSOCIATION_COPY_NONATOMIC Specifies that the associated object is copied, and that the association is not made atomically.
    • OBJC_ASSOCIATION_RETAIN Specifies a strong reference to the associated object, and that the association is made atomically.
    • OBJC_ASSOCIATION_COPY Specifies that the associated object is copied, and that the association is made atomically.

取回关联对象

1
NSString *associatedObject = (NSString *)objc_getAssociatedObject(array, &overviewKey);

取消关联

1
objc_setAssociatedObject(array, &overviewKey, nil, OBJC_ASSOCIATION_ASSIGN);

policy可以任意设置

另外还可以使用 objc_removeAssociatedObjects,不过这是不被赞成的,因为他打破了所有用户的所有关联。

完整的样例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#import <objc/runtime.h>
……

static char overviewKey;
NSArray *array = @[@"One", @"Two", @"Three"];
NSString *overview = @"First three numbers";
objc_setAssociatedObject (
        array,
        &overviewKey,
        overview,
        OBJC_ASSOCIATION_RETAIN
        );

NSString *associatedObject =
(NSString *) objc_getAssociatedObject (array, &overviewKey);
NSLog(@"associatedObject: %@", associatedObject);

objc_setAssociatedObject (
        array,
        &overviewKey,
        nil,
        OBJC_ASSOCIATION_ASSIGN
        );

注意:本样例代码使用了Objc2.0语法和ARC,更详细的信息,请参考官方文档。

iOS

Mac OS X 高端果粉进阶指南

前言

这一篇的tip主要是介绍Mac OS X自带功能,我没有说的,一定是因为我觉得没什么用,或者是太简单了,大家都会。然后再挖一个坑,以后再写一篇介绍mac下的高效APP。

基础类

1. 截图

  • shift+command+3全屏截图到桌面文件
  • shift+command+4选择截图到桌面文件
  • shift+control+command+3
  • shift+control+command+4到剪切板
  • 选择截图的时候按住option有不一样的效果

2. 窗口控制

command+tab可以切换程序 command+· 可以在程序内切换窗口 mission control和app expose就不多说了,还有各种手势操作要开足,默认是没有四指的。

3. 登陆项

在用户控制里面,另外在dock上右键也可以找到“登录时打开”

4. Finder path

defaults write com.apple.finder PathBarRootAtHome -bool TRUE;killall Finder

5. Lock files

cmd+i,可以lock files 打开文件,点击窗口最上面的文件名,也可以lock

6. 拷贝文字样式

  • opt+cmd+c
  • opt+cmd+v

效率类

1. 全屏模式下使用dock

把鼠标移动到dock原先在的边缘,停顿,再往边缘推一下

2. 把窗口移到另一个桌面

拖动窗口,到屏幕边缘,停顿一秒,就会切换桌面。delay的时间还可以改变用如下命令改变

defaults write com.apple.dock workspaces-edge-delay -float 0.15;killall Dock

也可以用如下命令取消

defaults delete com.apple.dock workspaces-edge-delay;killall Dock

3.多手势操作,惯性拖动

其他的不多说,设置里面的trackpad里面有视频演示。重点说一个我觉得很好用的,大家知道三个手指能拖动窗口(物体),如果你三个手指抓住之后,往一个方向甩出去,同时变成一个手指,就会发现移动带上了惯性。这个tip在使用双屏幕的时候,尤其好用,手指一甩,窗口就飞到另一个屏幕,还有惯性动画,碉堡了

4. 通知中心,Do not disturb me

打开通知中心,往下拖一下,就能看见那个开关了。也可以option然后点击通知中心的按钮。

5. Mission Control 也可以快速查看

mission control 状态下,也是可以按space来进行快速预览的,还可以双指(鼠标滚轮)向上滑动查看同一个应用的多个窗口

6. 一次显示多个文件的信息

cmd+i显示文件信息; opt+cmd+i显示多个文件的信息

7. 给文件加tag

这是一个牛逼的功能啦,打开一个文件的信息页面(cmd+i),有一个spotlight注释。

8. 给文件加label

右键可以给文件加颜色label,然后在spotlight中输入:label:blue(或 label:蓝色)就可以看见相应的文件,支持智能文件夹。

9. 拖动文件时,立即打开文件夹

拖动文件到另一个文件夹的时候,如果停在上面一会儿,就会打开该文件夹,如果你等不及,可以按一下space,就会立刻打开。

10. 高端mac pdf用户

preview app是可以annotating pdf的,而且还可以通过摄像头提取签名附近去。他还可以将不同的pdf合并在一起,添加图片,和密码保护。

11. focus single windows

这是一个有意思的tip,能让你点击dock上的icon,就会自动隐藏其他的app,这样,同时就只会有一个app在最前面。对于注意力难以集中,发现其他的东西分散了你的注意力的用户可以尝试,(其实也可以使用全屏模式)。

defaults write com.apple.dock single-app -bool TRUE;killall Dock 
//关闭
defaults delete com.apple.dock single-app;killall Dock 

12. 让quick look能看更多的文件格式

http://qlplugins.com/

13. 一个秘密的手势,回到前一个桌面

四指轻拍两下切换最近桌面,(老实说不是很好用。。)

defaults write com.apple.dock double-tap-jump-back -bool TRUE;killall Dock

美化类

1. 给任何文件换图标

非常简单,用预览打开一个图片,command+a, command+c。然后选中你想换图标的文件command+i显示信息,选中最上面的图标(会有高亮框),然后command+v! 或者干脆直接把文件直接拖到信息界面的icon哪里!

2. 给dock的文件夹加高亮框

//开启
defaults write com.apple.dock mouse-over-hilite-stack -bool TRUE;killall Dock
//关闭
defaults delete com.apple.dock mouse-over-hilite-stack;killall Dock

3. 替换Dashboard和Mission control的壁纸

/System/Library/CoreService/Dock.app/Contents/Resource

替换这个defaultdesktop.png和pirelli.png PS:注意安全,最好上备份,Time Machine

4. 在桌面播放动态屏保

nohup /System/Library/Frameworks/ScreenSaver.framework/Resources/ScreenSaverEngine.app/Contents/MacOS/ScreenSaverEngine -module Arabesque -background

其中Arabesque可以替换为/System/Library/Screen Savers里面的任意

这样关掉

killall ScreenSaverEngine 

PS.不会开机启动,想启动载入的可以写个脚本

5. 更改登陆界面壁纸

/System/Library/Frameworks/AppKit.framework/Versions/C/Resources/NSTexturedFullScreenBackgroundColor.png

6. 第三种最小化动画

defaults write com.apple.dock mineffect -string suck;killall Dock

defaults delte com.apple.dock mineffect;killall Dock

安全类

1. 可以用硬盘工具disk utility制作带密码保护的u盘

打开disk utility你就知道了

2. 安全倾倒垃圾箱

按住command右键垃圾箱,这样就不能用软件恢复了

3. 管理员密码非常重要

呵呵呵呵呵,我发现很多人对电脑的管理员密码是无动于衷的,甚至我认识不少大牛都这样,把密码设成一位,或者随意告诉别人。你们难道不知道keychain里面是存有你们所有的密码的么?可以通过鉴定显示所有密码哦~safari也可以哦~你的gmail,支付宝,网银全部都在里面哦

4. 一个牛逼的功能FileVault

国防级别的实时档案加密,保证你的硬盘不会被破解,没有你的密码,即使把硬盘拆了放到另一个电脑上也读取不出任何内容。 PS. 用这个功能会降低运行速度(因为是实时加密),所以如果你没有什么不可告人的秘密,还是别蛋疼了。

5. Time machine备份时间设定

sudo defaults write /System/Library/LaunchDaemons/com.apple.backupd-auto StartInterval -int 7200
// 调回默认
sudo defaults delete /System/Library/LaunchDaemons/com.apple.backupd-auto StartInterval
Mac

iPhone交互设计心得点滴

  • 移动应用之所以被称为移动应用正是因为用户正在移动,地铁,咖啡店,排队,候车等任何时候都有可能让用户打开你的APP,也同时有可能打断。你的用户不可能像设计师你一样全神贯注的把玩你的应用,周围的环境(还有其他的应用推送,来电,短信)随时都在争夺用户的注意力,所以移动应用的设计,不论是视觉设计,还是应用的交互设计,都必须简洁简洁再简洁,less is more。
  • 移动应用的上下文环境,即移动场景是一个考量用户需求的重要因素,用户启动一个APP的动机和心态无非三种1.我有个小任务要做;2.我想看看附近的情况;3。我无聊(不要再欺骗自己了,你真的很无聊)
  • 你可能会说,我的应用比他的好,为什么用户不来用我的?那是因为用户有他自身的习惯,改变习惯是要付出代价的,除非你的应用足够好到抵消这个代价。我们常常想要做出与众不同的设计,但是交互设计和艺术设计最大的区别就在于我们更关心的是用户,用户习惯是必须要考量的因素,不要为了设计而设计。
  • 单手操作是iPhone之所以是3.5寸屏幕的重要原因,因为iPhone就是被设计出来单手操作的,你的另一只手,或者另一半大脑还要处理一些周围环境的事情。所以将重要的经常需要使用的按钮放在单手拇指最容易操作的地方是一个设计的技巧。对于右手来说,这个地方是屏幕中间到左下角。然而一个需要注意的是事实是,你完全不能确定用户会用哪一只手来操作,对此有一种设计,就是横跨整个屏幕宽的按钮。
  • 一个保持应用良好形象的方法就是不使用滚动条,这样会让你的应用更象实体设备。而你恐怕不会知道用户对滚动的信息注意力有多低,实际上每一次滚动都需要多费一次脑,一次力。当然你知道我的意思不是完全抵制,只是作为设计师,你应该尽可能消灭多余的体力和脑力劳动。
  • 在我们做应用设计之前,首先应该了解目标客户是那些,他们有哪些需求,模拟用户使用app的流程和逻辑。简单的说,研究用户是在app设计之前就应该做的事,而不是上手就开始设计应用,这会让你偏离群众。
  • 如果不是程序很大,需要载入很长时间,就不要滥用开场动画了,你做的不是游戏。
  • Metro的设计很酷,是的,我也这么认为。你可以使用一些Metro的设计风格和设计元素,但千万不要以为自己真的是在给Windows Phone做应用。(或者android
  • 易发现的手势来源于经验,如果有些手势在现实世界中可以做,或是在桌面软件控制可以做,那么人就会同样在触屏上试试能不能做。
  • 设计手势的一个好方法就是观察用户的期望,比如在内置日历中,用户会期望通过左右滑动来切换月份。(可惜你只能按箭头)
  • 手势操作应该做操作的一种补充,而不是主要的操作模式,因为他往往并不是那么直观,也不容易被用户发现,如果单单只有手势操作,会降低易用性。所谓的补充,就是当一些操作过于耗时或是要经常重复的时候,设计一个简洁的手势作为操作的一种快捷方式,就能提高可用性。
  • 关于摇动这个手势,标准的用法应该是撤销,因为当用户不小心做了一个误操作之后,他会NO NO NO的大喊,然后晃动手机,这时候弹出一个是否撤销,不是很贴心么。但是千万不要把摇动手势来做破坏性的操作,比如删除或者清空什么的,千万不要忘了你拿着的是一个移动电话,移动的,亲。 (用户完全没有这个摇动撤销的习惯,不要违背用户的习惯,在能看见的地方显示撤销按钮,摇动一般作为一种有趣的附加操作)
  • 在3.5寸屏上面使用2根手指或以上的手势,就需要你使用两只手。而且,除了缩放操作之外,2根手指还没有任何一种手势是被人所熟悉的。(你知道地图应用里面两根手指单击可以缩小么?)
  • 自卫设计是为了防止误操作,通过需要更复杂的一些操作来完成诸如删除之类的破坏性功能,iPhone的标准删除操作,就需要三个流程。不过,防止误操作的真正救命稻草是提供撤销。Google在这一点上做得很好,他的决定性和破坏性操作,都会产生一个临时的撤销选项,比如当你把情书发给错误的女孩,你还有那么几秒钟时间可以紧急抢救一把。
  • 慎用模态警告框,因为他会打断操作,非真正紧急必要的情况,尽可能少的使用。可以用动作选单(iPad可以使用popover)来取代比如确认删除一类的操作。但是用弹出警告框来让用户打分评论,真的,这样做不够意思。(虽然貌似挺有效)

Mac & iOS的中文分词

简介

封装了CFStringTokenizer的NSString Category,可以方便的应用于Mac或者iOS APP, 其不但支持西方语言,更支持中文和日文这样没有空格分词的语言。

使用方法

导入NSString + Tokenize.hNSString + Tokenize.m后, 即可使用这两个接口

1
2
- (NSArray *)arrayWithWordTokenize;
- (NSString *)separatedStringWithSeparator:(NSString *)separator;

示例

1
2
3
4
5
6
#import "NSString+Tokenize.h"
- (IBAction)tapTokenizeButton:(id)sender {
    NSString *inputString = self.inputTextView.string;
    NSLog(@"TokensArray = %@", inputString.arrayWithWordTokenize);
    [self.outputTextView setString:[inputString separatedStringWithSeparator:@"/"]];
}
iOS