NSHipster


#pragma

pragma 的原始目的是使源代码适配多种编译器,但是现阶段更多时候用于分割代码,增加可读性。类似用法

#pragma mark - UIViewController
- (void)viewDidLoad {
}

#pragma mark - IBAction
- (IBAction)cancel:(id)sender {
}

将在代码块跳转的下拉pop中生成横线和名字,便于查找代码

另外 #pragma 多用于临时禁用或者启用一些警告,比如

#pragma clang diagnostic push
#pragma clang diagnostic ignored “-Warc-retain-cycles”
    self.completionBlock = ^ {

    };
#pragma clang diagnostic pop

关于警告,更多可参看这里 以及这里

另外还有 Which Clang Warning Is Generating This Message?


BOOL / bool / Boolean / NSCFBoolean

objc 中一般使用 BOOL 做真假判断。必须注意 BOOL 在 objc 中是被 typedef 为 signed char 的,因此在比较时不建议使用 BOOLValue == YES 或者 NO 之类的写法。另外在返回一个 BOOL 值时,也一定进行条件化后再进行返回。

错误用例

static BOOL different (int a, int b) {
    return a - b;
}

if (different(11, 10) == YES) {
  printf (“11 != 10\n”);
} else {
  printf (“11 == 10\n”);
}!
if (different(10, 11) == YES) {
  printf (“10 != 11\n”);
} else {
  printf (“10 == 11\n”);
}!
if (different(512, 256) == YES) {
  printf (“512 != 256\n”);
} else {
  printf (“512 == 256\n”);
}

输出为:

11 != 10
10 == 11
512 == 256

make no sense!

@(YES) 得到的是一个 __NSCFBoolean,这是 NSNumber 的 class cluster 的一个私有类。关于 class cluster 的更多,可以参考 Apple 文档


nil / Nil / NULL / NSNull

C 语言

Objective-C 在 C 的基础上增加了

任何对 nil 发送的消息都会返回 0 值,objc 很独特的一个点

NSArray 和 NSDictionary 不能存储 nil,因此使用 NSNull 对象来进行表示

Type What Detail
NULL (void *)0 literal null value for C pointers
nil (id)0 literal null value for Objective-C objects
Nil (Class)0 literal null value for Objective-C classes
NSNull [NSNull null] singleton object used to represent null

iOS 7 SDK中关于这些的定义

MacTypes.h
#ifndef NULL
#define NULL    __DARWIN_NULL
#endif /* ! NULL */
#ifndef nil
    #define nil NULL
#endif /* ! nil */

objc.h
define Nil __DARWIN_NULL

_types.h
#ifdef __cplusplus
    #ifdef __GNUG__
        #define __DARWIN_NULL __null
    #else /* ! __GNUG__ */
        #ifdef __LP64__
            #define __DARWIN_NULL (0L)
        #else /* !__LP64__ */
            #define __DARWIN_NULL 0
        #endif /* __LP64__ */
    #endif /* __GNUG__ */
#else /* ! __cplusplus */
    #define __DARWIN_NULL ((void *)0)
#endif /* __cplusplus */

Equality

直接对对象进行 == 操作符运算表示比较指针是否相等。默认情况下NSObject的-isEqual:方法也是比较指针相等。

对于特定的NSObject类,一般会存在特定的判等方法,如

Class Method
NSAttributedString -isEqualToAttributedString:
NSData -isEqualToData:
NSDate -isEqualToDate:
NSDictionary -isEqualToDictionary:
NSHashTable -isEqualToHashTable:
NSIndexSet -isEqualToIndexSet:
NSNumber -isEqualToNumber:
NSOrderedSet -isEqualToOrderedSet:
NSSet -isEqualToSet:
NSString -isEqualToString:
NSTimeZone -isEqualToTimeZone:
NSValue -isEqualToValue:

对于自定义的判等,一般我们可以通过实现自己的 -isEqualXXX: 方法来比较某些感兴趣的属性。当然也可以重载 -isEqual:,但是在这种情况下对于 -hash 也进行重载可以有效提高散列效率。

在实现 isEqual:hash 的重载时,注意:

Object equality is commutative 
 ([a isEqual:b] ⇒ [b isEqual:a])

If objects are equal, their hash values must also be equal 
 ([a isEqual:b] ⇒ [a hash] == [b hash])

However, the converse does not hold: two objects need not be equal in order for their hash values to be equal ([a hash] == [b hash] ¬⇒ [a isEqual:b])


C Storage Classes

auto

C 中默认的存储关键字。自动在程序进入块时为变量申请内存,并且在离开块时进行释放。auto 的变量只在块作用域中有效。

register

在 NS 的世界不常用。和 auto 类似,但是变量不会在栈上被申请,而会存储在寄存器中。寄存器比 RAM 快,但是前提是经过良好设计。事实上,如果滥用寄存器进行存储结果往往可能因为不必要地占用寄存器而导致变慢。使用 register 实际上只作为编译器的参考,实际上编译器可能不使用寄存器。

static

这个关键字用于存储时,有两种含义

  1. 在方法或函数中的static表示值在不同的call中保持一致。
  2. 全局声明的static变量可以被任何方法或函数取到。

在 OC 中,一个常用 staic 的地方是单例。objc 最佳的单例实践

+ (instancetype)sharedInstance {
    static id _sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedInstance = [[self alloc] init]; 
    });
    return _sharedInstance;
}

线程安全,必定只会被 call 一次

extern

static 不同,extern 声明的变量或者方法可以在所有文件中可见。全局变量是应该绝对避免的,在 objc 中,extern 的典型用法有下面两种:

.h  extern NSString * const kAppErrorDomain;
.m NSString * const kAppErrorDomain = @“com.example.yourapp.error”

attribute

attribute 是编译器命令,用来在声明是指定一些的特性,主要用于错误检查,优化和提示等

语法为 __attribute__ 后紧接两个括号,在里面是逗号分隔的属性。

对于 GCC

format 格式化输出

extern int my_printf (void *my_object, const char *my_format, …) __attribute__((format(printf, 2, 3)));

nonnull 指定某些输入参数不应该是 NULL

extern void *my_memcpy (void *dest, const void *src, size_t len)  __attribute__((nonnull (1, 2)));

noreturn

无返回,用在抛出等

pure / const

pure 用在除了返回以外,对程序没有影响的函数中。返回值只与输入参数以及全局变量有关。编译器将对其进行特殊优化。const 更进一步,将只与参数有关(而且参数中不能含有指针),const 的函数不应该返回 void

一个例子:

int square(int n) __attribute__((const));

pureconst 都将会在编译时有大幅优化,比如由于函数的结果只依赖于输入,因此函数可以缓存某一输入的结果,从而再次接受到这个输入是迅速返回。

unused

附加在方法上,表示方法很可能是没有被使用的。在这种情况下 GCC 将不会对此方法产生警告。

对于LLVM

availability

void f(void) __attribute__((availability(macosx,introduced=10.4,deprecated=10.6,obsoleted=10.7)));

比较常见的可用性 attribute,说明版本兼容性。一般可以用 Apple 的宏来代替:

可以参看 `/usr/include/Availability.hAvailabilityInternal.h

overloadable

用C来重载C++的方法。比如

#include <math.h>
float __attribute__((overloadable)) tgsin(float x)
{ return sinf(x); }
double __attribute__((overloadable)) tgsin(double x)
{ return sin(x); }
long double __attribute__((overloadable)) tgsin(long
double x) { return sinl(x); }

@

objc 语法三大特点:方括号,超级长的函数名,@ 符号

Interface & Implementation

@interface…@end
@implementation…@end

Properties

@property
@synthesize
@dynamic

Forward Class Declarations

@class

Instance Variable Visibility

@public
@package
@protected
@private

Protocols

@protocol…@end

Requirement Options

@required and @optional

Exception Handling

@try…@catch…@finally 

Literals

Object

Type Syntax
NSString @“”
NSNumber @42, @3.14, @YES, @‘Z’
NSArray @[]
NSDictionary @{}
Dynamically evaluates @()

关于自定义Literals,可以参看这里

struct {
    @defs(NSObject)
}

现在的 objc runtime 中 @defs 已经不可用了

Optimizations

Compatibility

@compatibility_alias 允许一个现有的类被 alias 为一个另外的名字,一般用在兼容性中。


instancetype

allocinit 将返回所发送类的实例,这是命名规范。但是对于类的 construtor 方法,同样无法从类型检查中受益。例如

[[[NSArray alloc] init] mediaPlaybackAllowsAirPlay]; 
// ! “No visible @interface for `NSArray` declares the selector `mediaPlaybackAllowsAirPlay`”!
[[NSArray array] mediaPlaybackAllowsAirPlay];
// (No error)

使用 instancetype 关键字,可以针对上下文给出合适的返回类型。


NS_ENUM & NS_OPTIONS

在 iOS6 之后,推荐使用 NS_ENUMNS_OPTIONS 来进行定义。

NS_ENUM

几种 enum 的对比

最基本的 enum:

enum {
    UITableViewCellStyleDefault,
    UITableViewCellStyleValue1,
    UITableViewCellStyleValue2,
    UITableViewCellStyleSubtitle
};

指定整数,但是无类型:

typedef enum {
    UITableViewCellStyleDefault,
    UITableViewCellStyleValue1,
    UITableViewCellStyleValue2,
    UITableViewCellStyleSubtitle
} UITableViewCellStyle;

指定整数以及类型:

typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
    UITableViewCellStyleDefault,
    UITableViewCellStyleValue1,
    UITableViewCellStyleValue2,
    UITableViewCellStyleSubtitle
};

NS_OPTIONS

在作为 bitmask 的时候使用,语法和 NS_ENUM 一样,但是编译器会为添加组合的警告。


NSOperation

objc 中一般的多线程方式 GCD 和 NSOperation

NSOperation 表示一个单一的计算单元。它本身是 abstract 的,为其子类提供了一个有用的线程安全的环境。我们可以直接使用其子类 NSBlockOperation 来完成一系列工作。

NSOperationQueue 用来组织多线程执行操作,扮演优先队列的角色。要开始某个 NSOperation,可以对其发送 start 方法,或者将其加入一个 NSOperationQueue 中。

NSOperation 的状态机转换 isReadyisExecutingisFinished。一般可以通过 KVO 来对状态转换进行检测。


NSDataDetector

NSDataDetectorNSRegularExpression 的子类。用来匹配日期,地址,链接,电话号码和换乘信息等。

NSError *error = nil;
NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeAddress | NSTextCheckingTypePhoneNumber error:&error];

NSString *string = @“123 Main St. / (555) 555-5555”;
[detector enumerateMatchesInString:string
                           options:kNilOptions
                             range:NSMakeRange(0, [string length])
                        usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop)
 {
     NSLog(@“Match: %@“, result);
 }];

iOS 版本有一个 UIDataDetectorTypes

另外在将自然语言翻译为结构化数据时,可以使用 linguistic 相关API (可以参看这里)


NSCache

NSCache 经常被直接用一个 Dictionary 替代,对于 NSCache 其实是应该在合适的场景经常使用的

key 不被 copy,因此不需要满足 NSCopying

使用方法类似字典,参看这里

// Your cache should have a lifetime beyond the method or handful of methods
// that use it. For example, you could make it a field of your application
// delegate, or of your view controller, or something like that. Up to you.
NSCache *myCache = …;
NSAssert(myCache != nil, @“cache object is missing”);

// Try to get the existing object out of the cache, if it’s there.
Widget *myWidget = [myCache objectForKey: @“Important Widget”];
if (!myWidget) {
    // It’s not in the cache yet, or has been removed. We have to
    // create it. Presumably, creation is an expensive operation,
    // which is why we cache the results. If creation is cheap, we
    // probably don’t need to bother caching it. That’s a design
    // decision you’ll have to make yourself.
    myWidget = [[[Widget alloc] initExpensively] autorelease];

    // Put it in the cache. It will stay there as long as the OS
    // has room for it. It may be removed at any time, however,
    // at which point we’ll have to create it again on next use.
    [myCache setObject: myWidget forKey: @“Important Widget”];
}

// myWidget should exist now either way. Use it here.
if (myWidget) {
    [myWidget runOrWhatever];
}

CFBag

CFBagNSCountedSet 有类似的功能,但是并不是 toll-free 的,需要特别注意

一般来说 NSCountedSet(带有统计次数的set)在实际应用中使用的可能不多,但是其实对于某些场景还是比较有用的。

对于高度定制,可以使用 CFMutableBag 和它的各种 callback

struct CFBagCallBacks {
   CFIndex version;
   CFBagRetainCallBack retain;
   CFBagReleaseCallBack release;
   CFBagCopyDescriptionCallBack copyDescription;
   CFBagEqualCallBack equal;
   CFBagHashCallBack 
typedef struct CFBagCallBacks CFBagCallBacks;

NSValueTransformer

将一个(类)value 转换为另一类的类,本身是 abstract 的

iOS 中不太使用 NSValueTransformer 的原因是没有 binding,并且现有的 block 等已经使胶水代码的数量大大降低

一个简单例子

@interface ClassNameTransformer: NSValueTransformer {}
@end

#pragma mark -

@implementation ClassNameTransformer
+ (Class)transformedValueClass {
  return [NSString class];
}

+ (BOOL)allowsReverseTransformation {
    return NO;
}

- (id)transformedValue:(id)value {
    return (value == nil) ? nil : NSStringFromClass([value class]);
}
@end

NSExpression

NSPredicate 其实是由两个 NSExpression 的左值和右值两部分构成,中间使用操作符链接(比如 < IN LIKE 之类)

但是大多数开发者使用 NSPredicate+predicateWithFormat:`` 方法来构建 predicate,因此NSExpression` 很少用。但是其实它很强力

数学计算

NSExpression *expression = [NSExpression expressionWithFormat:@“4 + 5 - 2**3”];
id value = [expression expressionValueWithObject:nil  context:nil]; 
// 1

一行搞定计算器app

函数

NSArray *numbers = @[@1, @2, @3, @4, @4, @5, @9, @11];
NSExpression *expression = 
[NSExpression expressionForFunction:@“stddev:” 
                          arguments:@[[NSExpression 
         expressionForConstantValue:numbers]]];
 
id value =  [expression expressionValueWithObject:nil context:nil]; 
// 3.21859…

统计学

基本算数

高级算数

边界方法

随机

二进制运算

日期

字符串


NSFileManager

对于一般需求,可以使用这个 category

很多代码片段示例

等等。。


NSValue

作为标量值的 boxing,使用 NSValue 将标量值进行封装。一般用于将标量值插入到 collection 中时使用。

使用例子

// assume ImaginaryNumber defined:
typedef struct {
    float real;
    float imaginary;
} ImaginaryNumber;


ImaginaryNumber miNumber;
miNumber.real = 1.1;
miNumber.imaginary = 1.41;

NSValue *miValue = [NSValue valueWithBytes: &miNumber
                            withObjCType:@encode(ImaginaryNumber)];

ImaginaryNumber miNumber2;
[miValue getValue:&miNumber2];