iOS GCD常用函数总结

GCD提供两种Dispatch Queue, 分别为Concurrent Dispatch Queue 和 Serial Dispatch Queue.

Serial Dispatch Queue 使用一个thread。

Concurrent Dispatch Queue 使用多个thread。

获取Dispatch Queue:

0)dispatch_queue_create

dispatch_queue_t queue = dispatch_queue_create("name.of.your.queue",NULL);

第一个参数为queue的名字,第二个参数传NULL表明要创建一个Serial Dispatch Queue, 传入DISPATCH_QUEUE_CONCURRENT 创建一个 Concurrent Dispatch Queue。
如果是对数据库表更新或更新文件,最好为每一个表或文件建立一个Serial Dispatch Queue,这样能保证只有一个thread会对数据进行更新。

如果对那些不会引起数据不一致问题的任务,需要放到Concurrent Dispatch Queue执行。

1)从Main Dispatch Queue/Global Dispatch Queue获取

//main queue
dispatch_queue_t mainQueue = dispatch_get_main_queue();

//global queue of high priority
dispatch_queue_t globalHigh =         dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);

//global queue of default priority
dispatch_queue_t globalDefault =     dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

//global queue of low priority
dispatch_queue_t globalLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0);

//global queue of background priority
dispatch_queue_t globalBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);

Main Dispatch Queue -> Serial dispatch queue
Global Dispatch Queue -> Concurrent dispatch queue

dispatch_set_target_queue: 主要用来给新建的queue设置优先级

dispatch_queue_t serialQueue = dispatch_queue_create("name.of.queue",NULL);

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);

dispatch_set_target_queue(serialQueue, globalQueue);

需要注意的是,第一个参数是自定义的queue,而不是系统的queue。因为你不能给系统的queue设置权限。通过上面设置,serialQueue 就有了与globalQueue一样的优先级。其实这个函数不仅可以设置queue的优先级,还可以设置queue之间的层级结构。

dispatch_after: 过一段时间执行queue中的task

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);

dispatch_after(time, dispatch_get_main_queue(),^{
           NSLog(@"after 3 seconds...");});

Dispatch Group

0)dispatch_group_notify

dispatch group 可以在group中的dispatch queue都执行完之后,通过一个dispatch_group_notify通知回调。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{NSLog(@"task1");});
dispatch_group_async(group, queue, ^{NSLog(@"task2");});
dispatch_group_async(group, queue, ^{NSLog(@"task3");});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"finished...");});

输出总会保证”finished…”会在最后一句输出。

1)dispatch_group_wait

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{NSLog(@"task1");});
dispatch_group_async(group, queue, ^{NSLog(@"task2");});
dispatch_group_async(group, queue, ^{NSLog(@"task3");});

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

dispatch_group_wait提供了一种类似超时的机制,当然如果等待时间设置为DISPATCH_TIME_FOREVER,功能和dispatch_group_notify是一样的。

dispatch_barrier_async

dispatch_barrier_async是一个可以让当前任务等待queue其他任务完成再执行的函数。例如,从数据库读取数据是可以多线程并发读取的,因为这样提高效率。但是,更新数据库数据,就要保证只有一个线程进行更新操作。所以,当更新操作发生的时候,一定要保证读操作和其他更新操作等待。

dispatch_queue_t queue = dispatch_queue_create("barrier.queue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, read_block_0);
dispatch_async(queue, read_block_1);
dispatch_async(queue, read_block_2);
dispatch_async(queue, read_block_3);
dispatch_async(queue, read_block_4);

dispatch_barrier_async(queue, update_block);

dispatch_async(queue, read_block_5);
dispatch_async(queue, read_block_6);
dispatch_async(queue, read_block_7);
dispatch_async(queue, read_block_8);
dispatch_async(queue, read_block_9);

这样就可以保证只有当更新操作结束后,才会进行其他数据库读操作,保证了数据的一致性。

dispatch_apply

dispatch_apply用于给一个block添加到dispatch queue若干次。 “for”循环

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

dispatch_apply(10, queue, ^(size_t index) { NSLog(@"%zu",index);});

NSLog(@"done...");

dispatch_suspend/dispatch_resume

用于暂停和继续执行queue

dispatch semaphore

dispatch semaphore 适合处理比dispatch queue或者dispatch_barrier_async更小颗粒度的操作。

例如,

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

NSMutableArray *array = [[NSMutableArray alloc] init];

for (int i = 0; i < 100000; i++)
{
dispatch_async(queue, ^{
  [array addObject:[NSNumber numberWithInt:i]];
});
}

如果执行上段代码,因为NSMutableArray不是thread safe的,所以在global queue中对其进行添加对象,会导致程序crash掉。

使用dispatch semaphore我们可以实现多线程对NSMutableArray进行操作

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

NSMutableArray *array = [[NSMutableArray alloc] init];

for (int i = 0; i < 100000; i++)
{
dispatch_async(queue, ^{

  dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

  [array addObject:[NSNumber numberWithInt:i]];

  dispatch_semaphore_signal(semaphore);
});
}

dispatch semaphore 是一个信号量的counter,当counter为0,当前线程停止运行,当大于0,自减1,继续执行。

dispatch_semaphore_create

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

初始化一个counter为1的semaphore.
dispatch_semaphore_wait

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

等待counter大于0才运行,第二个参数为等待时间。执行后counter减1.

dispatch_semaphore_signal

dispatch_semaphore_signal(semaphore);

给counter加1
所以在上面的代码中,就可以保证在wait和signal中的[array addObject:[NSNumber numberWithInt:i]]; 就只有一个thread进行操作,保证了线程安全。

dispatch_onece

保证了dispatch_once中的代码只会被执行一次,常用于单例。

+ (id)sharedManager {
static MyManager *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}

iOS Block实现探究

使用clang的rewrite-objc filename 可以将有block的c代码转换成cpp代码。从中可以看到block的实现。

#include <stdio.h>
int main()
{
  void (^blk)(void) = ^{
    printf("Block\n");
  };
  blk();
  return 0;
}

使用clang rewrite-objc以后会看到block的实现

struct __main_block_impl_0 {
 struct __block_impl impl;
 struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0)
{
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
 }
};

可以看到其实block是一个正常的OC类

来看看block是怎样访问外部变量的

int main()
{
  int dmy = 256;
  int val = 10;
  const char *fmt = "val = %d\n";
  void (^blk)(void) = ^{
    printf(fmt, val);
  };
  return 0;
}

转换之后,可以看到

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
  }
};


int main()
{
  int dmy = 256;
  int val = 10;
  const char *fmt = "val = %d\n";
  void (*blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val);
  return 0;
}

block的变量会被复制进block中

如果当block要改变传入的变量值怎么办?首先看一下全局变量和本地静态变量

int global_val = 1;
static int static_global_val = 2;
int main()
{
  static int static_val = 3;
  void(^blk)(void) = ^{
  global_val *= 1;
   static_global_val *= 2;
  static_val *= 3;
  };
  blk();
  return 0;
}



struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_val = __cself->static_val; // bound by copy

global_val *= 1;
static_global_val *= 2;
(*static_val) *= 3;
}

由于全局变量是在Data Section中,所以直接可以访问。局部静态变量是通过将其指针传入到block中,block就可以对其值进行修改。

然后看一下__block修饰符变量

int main()
{
  __block int val = 10;
  void (^blk)(void) = ^{val = 1;};
  blk();
  return 0;
}

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,             __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)

block修饰符的变量,会生成一个Block_byref_val_0的struct,然后通过访问其forwarding来访问val值。因为Block有可能是在stack或者heap中,所以用forwarding来访问。之所以会将__block单独生成一个struct是因为可能该变量会被多个block使用。

Block分三种类型

0) NSConcreteStackBlock –stack

1) NSConcreteGlobalBlock –data area

2) NSConcreteMallocBlock –heap

自动copy block

当开启ARC时,在某些情况编译器会自动copy block,从stack到heap。

typedef int (^blk_t)(int);
blk_t func(int rate)
{
  return ^(int count){return rate * count;};
}


blk_t func(int rate)
{
  blk_t tmp = &__func_block_impl_0(
__func_block_func_0, &__func_block_desc_0_DATA, rate);

  tmp = objc_retainBlock(tmp);
  return objc_autoreleaseReturnValue(tmp);
}

有些情况,编译器是无法检测是否应该copy block:

当block作为参数传递到方法或函数中。

但是,如果该方法或函数在内部copy,就不用手动再copy:

0)cocoa framework方法, 有usingBlock

1) GCD API

- (id) getBlockArray
{
  int val = 10;
  return [[NSArray alloc] initWithObjects:
 [^{NSLog(@"blk0:%d", val);} copy],
 [^{NSLog(@"blk1:%d", val);} copy], nil];
}

__forwarding

当block从stack copy到 heap中时,block中用到的block也会copy到heap中,并且copy到heap中的block拥有该block。

__block int val = 0;
void (^blk)(void) = [^{++val;} copy];
++val;
blk();
NSLog(@"%d", val);

在block和外的++val都会变成 ++(val.forwarding->val);
当block copy到heap中后, stack中的
forwarding会指向heap中的block, heap中的forwarding会指向自己的block值,这样保证了forwarding指向的是同一个变量值。

Block什么时候会copy到heap中

0)对block调用copy方法。

1)block作为一个函数的返回值。 编译器自动copy

2)赋值给id或block type class 有__strong 修饰符的成员变量。 编译器自动copy

3)usingBlock, GCD API。 在函数内copy

什么时候用该copy block?

0) block是函数返回值

1) block赋值给id或block type class 有__strong 修饰符的成员变量。

2)3)usingBlock, GCD API。 在函数内copy

UIApplicationDelegate 启动选项介绍

1
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  // Override point for customization after application launch.
  return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application
{
  // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
  // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
  // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
  // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
  // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
  // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

- (void)applicationWillTerminate:(UIApplication *)application
{
  // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}

每个iOS app的入口都是

1
- application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

当app启动完成,准备运行时,application会调用自己的这个代理方法。

一个app除了点击图标进行启动,还有一些其他方式调用一个app。区别不同启动方式,就需要用到launchOptions. 与userInfo 字典相似,-application:didFinishLaunchingWithOptions: 可以通过launchOptions获取启动信息的key。

通过URL启动

1
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"app://..."]];

http://URL 会调用Safari, mailto://URL 会打开邮箱, tel://URL 会拨打电话。

这种情况下launchOptionsUIApplicationLaunchOptionsURLKey

app通过URL调用时,还可以有一些系统信息。 当通过UIDocumentInteractionController 或者 AirDrop 调用时,launchedOptions 会被设置为:

UIApplicationLaunchOptionsSourceApplicationKey: key值是一个NSString,表示要调用你app的app bundle ID

UIApplicationLaunchOptionsAnnotationKey: key可以存储property-list 对象。

1
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"Document" withExtension:@"pdf"];
if (fileURL) {
  UIDocumentInteractionController *documentInteractionController =
  [UIDocumentInteractionController interactionControllerWithURL:fileURL];

  documentInteractionController.annotation = @{@"foo": @"bar"};
  [documentInteractionController setDelegate:self];
  [documentInteractionController presentPreviewAnimated:YES];
}

推送消息调用

远程推送

远程推送消息调用时,launch option 会是 UIApplicationLaunchOptionsRemoteNotificationKey

1
- (BOOL)application:(UIApplication *)applicationdidFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  // ...

    if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) {
       [self application:application didReceiveRemoteNotification:launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]];
    }
}

本地推送

本地推送launch options 会是 UIApplicationLaunchOptionsLocalNotificationKey

1
@import AVFoundation;
@interface AppDelegate ()
@property (readwrite, nonatomic, assign) SystemSoundID localNotificationSound;
@end

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{     
    if (application.applicationState == UIApplicationStateActive) {         
   UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:notification.alertAction                                                               message:notification.alertBody                                                                 delegate:nil
                                             cancelButtonTitle:NSLocalizedString(@"OK", nil)                                                otherButtonTitles:nil];         
   if (!self.localNotificationSound) {             
        NSURL *soundURL = [[NSBundle mainBundle] URLForResource:@"Sosumi"                                                                        withExtension:@"wav"];             
     AudioServicesCreateSystemSoundID((__bridge CFURLRef)soundURL, &_localNotificationSound);      }         
    AudioServicesPlaySystemSound(self.localNotificationSound);         
    [alertView show];     
  }
}
- (void)applicationWillTerminate:(UIApplication *)application {     
  if (self.localNotificationSound) {         
      AudioServicesDisposeSystemSoundID(self.localNotificationSound);     
  }
}

位置事件调用

当由手机位置变动,调用app时,launch option 会是 UIApplicationLaunchOptionsLocationKey, key 是一个NSNumber 包含一个Boolean。

1
@import CoreLocation;
@interface AppDelegate () <CLLocationManagerDelegate>
@property (readwrite, nonatomic, strong) CLLocationManager *locationManager;
@end

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {     
  // ...     
  if (![CLLocationManager locationServicesEnabled]) {         
  [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Location Services Disabled", nil)                                  message:NSLocalizedString(@"You currently have all location services for this device disabled. If you proceed, you will be asked to confirm whether location services should be reenabled.", nil)                                    
                             delegate:nil                           
                    cancelButtonTitle:NSLocalizedString(@"OK", nil)                                                otherButtonTitles:nil] show];     
   }
   else {         
       self.locationManager = [[CLLocationManager alloc] init];         
       self.locationManager.delegate = self;         
       [self.locationManager startMonitoringSignificantLocationChanges];     
   }     
   if (launchOptions[UIApplicationLaunchOptionsLocationKey]) {         
   [self.locationManager startUpdatingLocation];     
   }
}

Newsstand

launch option 会是 UIApplicationLaunchOptionsNewsstandDownloadsKey, 提示用户有新的Newsstand可以下载

蓝牙调用

iOS 7 中蓝牙分主从关系,所以对应的launch option key有两种

UIApplicationLaunchOptionsBluetoothCentralsKey

UIApplicationLaunchOptionsBluetoothPeripheralsKey

1

@import CoreBluetooth;
@interface AppDelegate () <CBCentralManagerDelegate>
@property (readwrite, nonatomic, strong) CBCentralManager *centralManager;
@end

self.centralManager = [[CBCentralManager alloc] initWithDelegate:self
                                                         queue:nil
                                                         options:@{CBCentralManagerOptionRestoreIdentifierKey:(launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey] ?: [[NSUUID UUID] UUIDString])}];
if (self.centralManager.state == CBCentralManagerStatePoweredOn) {     
  static NSString * const UID = @"7C13BAA0-A5D4-4624-9397-15BF67161B1C"; // generated with `$ uuidgen`     
  NSArray *services = @[[CBUUID UUIDWithString:UID]];     
  NSDictionary *scanOptions = @{CBCentralManagerScanOptionAllowDuplicatesKey:@YES};     
  [self.centralManager scanForPeripheralsWithServices:services options:scanOptions];
}

参考链接

iOS7 nested push animation can result in corrupted

在iOS7中,开启push/pop view controller 动画遇到一个问题,就是在快速切换多次后会在console出现一个问题

nested push animation can result in corrupted navigation bar multiple warning
Unbalanced calls to begin/end appearance transitions for

查了一些资料后,找到了一个方法:

0)找到push/pop view controller 动画结束的回调。

UINavigationControllerDelegate代理中的

-(void)navigationController:(UINavigationController *)navigationController
  didShowViewController:(UIViewController *)viewController
               animated:(BOOL)animated

1)找到是否接受点击方法的方法。

UIGestureRecognizerDelegate中的

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch

应用以上两个代理方法,加上一个BOOL的flag值,可以控制是否处罚pop/push

当动画完成时,将flag设置为允许pop/push, shouldReceiveTouch直接返回flag值。

在点击事件时,要注意更改flag值不再接受其他事件,防止在flag为YES时的多次点击。

参考链接:

http://stackoverflow.com/questions/11813091/nested-push-animation-can-result-in-corrupted-navigation-bar-multiple-warning

http://www.taofengping.com/2013/12/26/ios7_barbuttonitem_navigation_gesture/

http://blog.csdn.net/lengshengren/article/details/12616217

http://stackoverflow.com/questions/10150231/how-to-detect-when-uinavigationcontroller-animation-has-finished

iOS 消息传递过程解析

在iOS中调用一个方法,其实是调用一个方法实现的函数指针并传给他对象实例指针,一个Selector,还有函数用到的参数。在Objective-C 中,每一个方法调用其实都是转换成了调用objc_msgSend方法。

objc_msgSend的调用过程如下:

0)首先检查消息接受对象是否为nil. 如果消息接受对象为nil,则触发nil-handler. 默认nil-handler是什么都不做的。

1)在垃圾回收环境中(Mac OS X)中,检查是否调用了(retain, release, autorelease, retainCount), 如果是,就返回self. 所以,这就意味着在垃圾回收机制中,retainCount 返回self。这时调用retainCount就会产生无限循环。

2)检查class的缓存中是否有该方法的实现,如果有,调用。

3)在类方法中是否有该方法,如果有就调用类方法的实现。

4)查看父类中是否有该方法实现,如果没有再查找父类的父类中有没有实现。如果找到了,调用。

5)调用resolveInstanceMethod:(或者resolveClassMethod:). 如果返回YES, 则调用。 该对象会有该方法的实现,因为它会调用class_addMethod.

6) 调用forwardingTargetForSelector:. 如果返回non-nil, 则对返回对象发消息。此处不会返回self,否则会进入无限循环。

7)调用methodSignatureForSelector:, 如果返回non-nil, 创建一个NSInvocation 并且将这个NSInvocation传递给forwardInvocation:.

8) 调用doesNotRecognizeSelector:. 默认实现就是抛一个异常。

iOS KVO实现方式

KVO 也许是iOS中“最神奇”的部分了,因为你不需要在被观察对象中添加任何代码,就可以实现对被观察对象属性改变的通知。KVO究竟是怎么实现的?

KVO是通过Objective-C的runtime来实现的。当你第一次要对一个对象进行观察时,runtime会为你创建一个被观察对象class的subclass。在这个新创建的subclass中,KVO会复写所要观察属性的setter方法,然后转换被观察对象的isa指针,指向新创建的subclass,所以,你想要观察的对象,变成了KVO在runtime时创建的subclass。因为Apple不想让这种机制暴露,所以还会复写要观察对象的class方法,所以,当你调用class来判断该对象的class时,还会显示原对象的class类型,而不是subclass的类型。

继续探究

// gcc -o kvoexplorer -framework Foundation kvoexplorer.m

#import <Foundation/Foundation.h>
#import <objc/runtime.h>


@interface TestClass : NSObject
{
    int x;
    int y;
    int z;
}
@property int x;
@property int y;
@property int z;
@end
@implementation TestClass
@synthesize x, y, z;
@end

static NSArray *ClassMethodNames(Class c)
{
    NSMutableArray *array = [NSMutableArray array];

    unsigned int methodCount = 0;
    Method *methodList = class_copyMethodList(c, &methodCount);
    unsigned int i;
    for(i = 0; i < methodCount; i++)
        [array addObject: NSStringFromSelector(method_getName(methodList[i]))];
    free(methodList);

    return array;
}

static void PrintDescription(NSString *name, id obj)
{
    NSString *str = [NSString stringWithFormat:
        @"%@: %@\n\tNSObject class %s\n\tlibobjc class %s\n\timplements methods <%@>",
        name,
        obj,
        class_getName([obj class]),
        class_getName(obj->isa),
        [ClassMethodNames(obj->isa) componentsJoinedByString:@", "]];
    printf("%s\n", [str UTF8String]);
}

int main(int argc, char **argv)
{
    [NSAutoreleasePool new];

    TestClass *x = [[TestClass alloc] init];
    TestClass *y = [[TestClass alloc] init];
    TestClass *xy = [[TestClass alloc] init];
    TestClass *control = [[TestClass alloc] init];

    [x addObserver:x forKeyPath:@"x" options:0 context:NULL];
    [xy addObserver:xy forKeyPath:@"x" options:0 context:NULL];
    [y addObserver:y forKeyPath:@"y" options:0 context:NULL];
    [xy addObserver:xy forKeyPath:@"y" options:0 context:NULL];

    PrintDescription(@"control", control);
    PrintDescription(@"x", x);
    PrintDescription(@"y", y);
    PrintDescription(@"xy", xy);

    printf("Using NSObject methods, normal setX: is %p, overridden setX: is %p\n",
          [control methodForSelector:@selector(setX:)],
          [x methodForSelector:@selector(setX:)]);
    printf("Using libobjc functions, normal setX: is %p, overridden setX: is %p\n",
          method_getImplementation(class_getInstanceMethod(object_getClass(control),
                                   @selector(setX:))),
          method_getImplementation(class_getInstanceMethod(object_getClass(x),
                                   @selector(setX:))));

    return 0;
}

首先,定义一个TestClass,有3个属性。
然后定义一些工具函数。ClassMethodNames 通过Objective-C 的runtime函数,来返回当前class实现的方法名。

代码执行结果

control: <TestClass: 0x104b20> NSObject class TestClass libobjc class TestClass implements methods <setX:, x, setY:, y, setZ:, z>


x: <TestClass: 0x103280> NSObject class TestClass libobjc class NSKVONotifying_TestClass implements methods <setY:, setX:, class, dealloc, _isKVOA>

y: <TestClass: 0x104b00> NSObject class TestClass libobjc class NSKVONotifying_TestClass implements methods <setY:, setX:, class, dealloc, _isKVOA>

xy: <TestClass: 0x104b10> NSObject class TestClass libobjc class NSKVONotifying_TestClass implements methods <setY:, setX:, class, dealloc, _isKVOA>

Using NSObject methods, normal setX: is 0x195e, overridden setX: is 0x195e

Using libobjc functions, normal setX: is 0x195e, overridden setX: is 0x96a1a550

可以看出,
0)TestClass 在runtime时变成了NSKVONotifying_TestClass

1)虽然x,y只观察了一个属性,但是NSKVONotifying_TestClass却实现了setY, setX方法。也就是说,一个类,KVO只会subclass一个KVO类,也就是NSKVONotifying_TestClass类。

2)NSKVONotifying_TestClass 覆写了class方法,来掩盖subclass的存在,还覆写了dealloc方法。除此之外,还有一个新的方法_isKVOA, 是Apple提供的一个私有方法,用于判断一个object是否生成动态subclass。

原文链接:http://www.mikeash.com/pyblog/friday-qa-2009-01-23.html

iOS GCD 与 NSOperationQueue对比

  • NSOperationQueue 是在GCD基础上实现的,只不过是GCD更高一层的抽象。
  • GCD 只支持FIFO 的队列, 而NSOperationQueue可以调整队列的执行顺序。(通过调整权重)
  • NSOperationQueue可以在Operation间设置依赖关系,而GCD不可以。 如果一个Operation依赖另一个Operation所产生的数据的化,你可以设置一个Operation依赖于另一个Operation来实现, NSOperationQueue可以根据依赖关系,可以以正确的顺序执行Queue中的Operation。
  • NSOperationQueue支持KVO。 这就意味着你可以观察任务的状态属性。
    以上几点并不是说在任何处理多任务时一定要选择NSOperationQueue, 因为NSOperationQueue在执行速度上会比GCD慢。

iOS 获取本地设备IP地址

1
#import <ifaddrs.h>
#import <arpa/inet.h>
// Get IP Address
- (NSString *)getIPAddress {    
  NSString *address = @"error";
  struct ifaddrs *interfaces = NULL;
  struct ifaddrs *temp_addr = NULL;
  int success = 0;
  // retrieve the current interfaces - returns 0 on success
  success = getifaddrs(&interfaces);
  if (success == 0) {
      // Loop through linked list of interfaces
      temp_addr = interfaces;
      while(temp_addr != NULL) {
          if(temp_addr->ifa_addr->sa_family == AF_INET) {
              // Check if interface is en0 which is the wifi connection on the iPhone
              if([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) {
                  // Get NSString from C String
                  address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];               
              }
          }
          temp_addr = temp_addr->ifa_next;
      }
  }
  // Free memory
  freeifaddrs(interfaces);
  return address;
  }

iOS Crash类别总结

EXC_BAD_ACCESS

An EXC__BAD_ACCESS occurs whenever you try to access or send a message to a deallocated object. The most common cause of EXC_BAD_ACCESS is when you initialize a variable in one of your initializer methods but use the wrong ownership qualifier, which results in deallocation of your object. For example, you create an NSMutableArray of elements for your UITableViewController in the viewDidLoad method but set the ownership qualifier of the list to unsafe_unretained or assign instead of strong. Now in cellForRowAtIndexPath:, when you try to access the deallocated object, you’ll crash with a EXC_BAD_ACCESS. Debugging EXC_BAD_ACCESS is made easy with the NSZombiesEnabled environment variable.

SIGSEGV

A signal segment fault(SIGSEGV) is a more serious issue that the operating system raises. It occurs when there is a hardware failure or when you try to access a memory address that cannot be read or when you try to write to a protected address.

The first case, a hardware failure, is uncommon. When you try to read data stored in RAM and the RAM hardware at that location is faulty, you get a SIGSEGV. But more often than not, a SIGSEGV occurs for the latter two reasons. By default, code pages are protected from being written into and data pages are protected from being executed. When one of your pointers in your application points to a code page and tries to alter the value pointed to, you get a SIGSEGV. You also get a SIGSEGV when you try to read the value of a pointer that was initialized to a garbage value pointing to an address that is not a valid memory location.

SIGSEGV faults are more tedious to debug, and the most common case reason a SIGSEGV happens is an incorrect typecast. Avoid hacking around with pointers or trying to a read a private data structure by advancing the pointer manually. When you do that and don’t advance the pointer to take care of the memory alignment or padding, you get a SIGSEGV.

SIGBUS

A signal bus(SIGBUS) error is a kind of bad memory access where the memory you tried to access is an invalid memory address. That is, the address pointed to is not a physical memory address at all. Both SIGSEGV and SIGBUS are subtypes of EXC_BAD_ACCESS.

SIGTRAP

SIGTRAP stands for signal trap. This is not really a crash signal. It’s sent when the processor executes a trap instruction. The LLDB debugger usually handles this signal and stops at a specified breakpoint. If you get a SIGTRAP for no apparent reason, a clean build will usually fix it.

EXC_ARITHMETIC

divide zero.

SIGILL

SIGILL stands for SIGNAL ILLEGAL INSTRUCTION. This happens when you try to execute an illegal instruction on the processor. You execute an illegal instruction when you’re trying to pass a function pointer to another function, but for one reason or other, the function pointer is corrupted and is pointing to a deallocated memory or a data segment. Sometimes you get an EXC_BADINSTRUCTION instead of SIGILL, and through both are synonymous, EXC* are machine-independent equivalents of this signal.

SIGABRT

SIGABRT stands for SIGNAL ABORT. This is a more controlled crash where the operating system has detected a condition that is not safe and asks the process to perform cleanup if any is needed. There is no one silver bullet to help you debug the underlying error for this signal. Frameworks like cocos2d or UIKit often call the C function abort when certain preconditions are not met or when something really bad happens. When a SIGABRT occurs, the console usually has a wealth of information about what went wrong. Since it’s controlled crash, you can print the backtrace by typing bt into the LLDB console.

iOS LLDB console debug总结

Xcode’s debugging console window is a full-featured LLDB debugging console. When your app is paused(at a breakpoint), the debugging console shows the LLDB command prompt. You can type any LLDB debugger common into the console to help you with debugging, including loading external python script.

The most frequently used command is po, which stands for print object. When your application is paused in debugger, you can print any variable that is in the current scope. This includes any stack variables, class variables, properties, ivars, and global variables. In short, any variable that is accessible by your application at the breakpoint can be accessed via the debugging console.

Printing scalar variables

when you’re dealing with scalars like integers or structs(CGRect, CGPoint, etc..), instead of using po, you use p, followed by the type of struct.

p (int) self.myAge

p (CGPoint) self.view.center

Printing Registers

Registers in your CPU are used for storing variables that have to be accessed frequently. Compilers optimize frequently used variables like the loop variable, method arguments, and return variables in the registers. When your app crashes for no apparent reason, probing the register for the method name or the selector name that crashed your app will be very useful.

(lldb) register read

General Purpose Registers:

  r0 = 0x37c9cb21  libobjc.A.dylib`objc_msgSend + 1

  r1 = 0x37c9cb21  libobjc.A.dylib`objc_msgSend + 1

  r2 = 0x01b5c214  "idKey"

  r3 = 0x01b5eb28  "checkCurrentContactBean"

  r4 = 0x00000000

  r5 = 0x37c9cb21  libobjc.A.dylib`objc_msgSend + 1

  r6 = 0x27d09bd0

  r7 = 0x27d09bc8

  r8 = 0x01b5bbc4  "view"

  r9 = 0x00000000

 r10 = 0x01b5e3f8  "masterViewController"

 r11 = 0x00000000

 r12 = 0x3a11c1d0  (void *)0x382c3959: _os_lock_handoff_unlock$VARIANT$mp + 1

  sp = 0x27d09358

  lr = 0x37cacabb  libobjc.A.dylib`objc_object::sidetable_release(bool) + 95

  pc = 0x002cce40  iPoS_IOS`-[PersonMainForm viewWillAppear:] + 232 at PersonMainForm.m:59

cpsr = 0x60000030

Your output may vary, butt pay close attention to the wax, dcx, and esi on the simulator or r0-r4 registers when running on a device. These registers store some of the values that you’re interested in. In the Simulator, the dcx register holds the name of the selector that is called when your app crashed. You print an individual register to console by specifying the register name as shown below
register read ecx.

You can also specify multiple registers like

register  read eax ecx.

The dcx register on Intel architecture and the r15 register on ARM architecture hold the program counter. Printing the address of the program counter will show the last executed instruction. Similarly, wax(r0 on ARM) holds the receiver address, ecx (r4 on ARM) and holds the selector that was called last. The arguments to the methods are stored in registers r1-r3. If your selector has more than three arguments, they are stored on stack, accessible via the stack pointer(r13). sp, lr, and pc are actually aliases to the r13,r14 and r15 register, respectively. Hence, register read r13 is equivalent to register read sp.