Objective-C学习笔记(4-7)

目录

  1. 构造和拷贝构造
  2. 动态特性和RTTI
  3. 内存管理和标准C区别
  4. 监视方式
  5. 数据持久化
  6. RTTI和动态特性2
  7. 容器类、Block和NSPredicate

04监视方式

Protocol

用protocol实现的delegate。其实我觉得这个就是观察者,只不过默认情况下是单一的观察者。具体方法不总结了。

有什么方法可以一次性存放多个delegate?这样就可以用数组方法一口气通知完:

[array makeObjectsPerformSelector:@selector(doSomething:) withObject:aObject];

如果写N个变量第一比较罗嗦第二也不知道运行时会有多少个观察者。但问题在于NSArray和NSMutableArray不能存指针之外的对象,protocol实例不认。

KVO

KVO,也就是Key-Value Observer,是除了protocol和notification之外的又一种属性监视方式

监视别人的类重载一个方法,用于观察值变化

- (void) observeValueForKeyPath:(NSString*)aPath ofObject:(id)anObject change:(NSDictionary*)aChange context:(void*)aContext {
           NSLog( @"%@ has observered key %@ change in %@, from %@ to %@",
                [self class], aPath, anObject, [aChange objectForKey:@"old"], [aChange objectForKey:@"new"] );
}

被监视的类不用特意修改。

发送消息时,需要用NSObject里面某Category已经定义好的add/removeObserver增删observer即可

Coder* me = [Coder personWithName:@"Si Wei" andAge:25];
    [me setLanguage:@"C#"];
    Computer* computer = [[Computer alloc] init];
    [computer addObserver:me forKeyPath:@"workload" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionNew context:nil];
    [computer setWorkload:10];
    [computer setWorkload:50];
    [computer removeObserver:me forKeyPath:@"workload"];
    [computer release];

Notification Center

监视者在初始化时候告诉NC自己要监视“哪个名字”,这个名字可以自定,只要保证post消息时候用一样的即可。

NSNotificationCenter* notifyCenter = [NSNotificationCenter defaultCenter];
    [notifyCenter addObserver:self selector:@selector(handleWorkloadNotification:) name:@"workload_notify" object:nil];

需要传入一个方法的selector。具体函数名参数名随便,但参数只能有一个NSNotification。

- (void) handleWorkloadNotification:(NSNotification*)aNotification {
    Computer* newCom = [aNotification object];
    NSLog( @"%@ has been notified with workload %d from %@", [self class], [newCom workload], newCom );
}

被监视的类不用特意修改。

发送通知时,使用NC的postNotificationName方法,这里的name就是上面那个。

Coder* me = [[Coder alloc] initWithArgs:@"Si Wei" andAge:25 andLanguage:@"C#"];
    Computer* computer = [[Computer alloc] init];
    NSNotificationCenter* notifyCenter = [NSNotificationCenter defaultCenter];
    [notifyCenter postNotificationName:@"workload_notify" object:computer];
    [computer setWorkload:10];
    [notifyCenter postNotificationName:@"workload_notify" object:computer];
    [me release];
    [computer release];

05数据持久化

plist

plist文件用于存储程序相关的一些内容,类似于ini文件或者.Net下面的app.config。NSData、NSArray和NSDictionary可以直接用xxxWithContentsOfFile和writeToFile读写:

Coder* siw = [Coder personWithName:@"Si Wei" andAge:25];
    [siw setLanguage:@"C#"];
    Coder* yexd = [Coder personWithName:@"Ye Xiaodong" andAge:25];
    [yexd setLanguage:@"Objective-C"];
    Person* nan_zhang = [Person personWithName:@"Zhang Nan" andAge:29];
    NSArray* objs = [NSArray arrayWithObjects:siw, yexd, nan_zhang, nil];
    [objs writeToFile:@"pdata1.txt" atomically:NO];
    [objs writeToFile:@"pdata1_a.txt" atomically:YES];
    NSArray* objs_fromFile = [NSArray arrayWithContentsOfFile:@"pdata1.txt"];
    //[(Coder*)[objs_fromFile objectAtIndex:0] ShowInfo]; // 不过这样不行的……

上面最后一行肯定不行,因为拿出来的是GSMutableString,可以打开存储的文件看看。序列化后的应该是XML,不过在windows上就是逗号分隔数据了。

(
    "<Coder: 0xd4d1a0>",
    "<Coder: 0xd712c0>",
    "<Person: 0xd44be0>"
)

如果希望将存储有自定义类的array或者dictionary持久化的话,可以想到的方案是实现下面NSCoding的方法,塞到array后给再下面说的NSUserDefault。

archiver

archiver以二进制方式存储数据。需要存储的类要实现两个定义在NSCoding协议里面的方法:

- (void) encodeWithCoder: (NSCoder*) coder {
    NSLog( @"encoding: %@", self );
    [coder encodeObject: name forKey:@"name"];
    [coder encodeInt: age forKey:@"pid"];
}
- (id) initWithCoder: (NSCoder*) decoder {
    NSLog( @"decoding: %@", self );
    return [self initWithArgs:[decoder decodeObjectForKey:@"name"] andAge:[decoder decodeIntForKey:@"age"]];
}

initWithCoder里面我用了自己实现的构造函数。默认情况下和init一样用[super init]再赋值也OK。注意用的时候归档和读档的类是不同的:

data = [NSKeyedArchiver archivedDataWithRootObject:siw];
    [data writeToFile:@"siwei.data" atomically:NO];
    NSData* data_fromArchive = [NSData dataWithContentsOfFile:@"siwei.data"];
    Coder* siw_fromArchive = [NSKeyedUnarchiver unarchiveObjectWithData:data_fromArchive];
    [siw_fromArchive ShowInfo];

UD

User Defaults类似于全局字典,访问数据更灵活:

NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    [userDefaults setObject:@"siwei" forKey:@"me"];
    //[userDefaults setObject:objs forKey:@"objs"];
    [userDefaults setInteger:42 forKey:@"int"];
    [userDefaults synchronize];

这样就存储完了。最后一句不必要。

NSArray* objs_fromUD = [userDefaults objectForKey:@"objs"];
    int i = 0;
    for ( i = 0; i < [objs_fromUD count]; i++ ) {
        if ( [[objs_fromUD objectAtIndex:i] respondsToSelector:@selector(GetInfo:TheSecondArg:)] ) {
            NSLog( @"array: %@", [[objs_fromUD objectAtIndex:i] GetInfo:100 TheSecondArg:YES] );
        }
    }
    NSLog( @"%@", [userDefaults objectForKey:@"me"] );
    NSLog( @"%d", [userDefaults integerForKey:@"int"] );

读取数据如上所示。不过在GNUStep上面不能把array给setObject到UD里面去,提示Argument Exception。按理说所有内置plist类型都是没问题的。

SQL

Sqlite持久化实现了传说中的ORM,适用于数据量比较大的情况。并且有第三方的开源项目进一步简化操作 Sqlist Persistent Objects

Core Data

Core Data是基于Model、Entity和Relationship的存储方式,利用sql语句直接访问持久化文件,通过各种和数据库概念相对应的类——例如NSEntityDescription对应表结构、NSFetchRequest对应查询等——实现对数据库的封装,免得去写SQL语句。不过实现起来复杂到恶心,不愧是企业级应用。有兴趣可以看看教程@notsos.im

06RTTI和动态特性2

isa指针

所有NSObject继承下来的对象都有一个isa指针,指向具体的Class定义,所以所有从一个类实例化出来的对象的isa指针都一样。看一下NSObject指针就能看出来,这个指针指向的是objc_class类型。所以id实际上就是一个指向指向objc_class指针的指针。

@interface NSObject <NSObject> {
   Class       isa;

typedef struct objc_class *Class;
typedef struct objc_object {
   Class isa;
} *id;
...

对于[class method]的调用,都会变成obj_msgSend这个函数的调用,看下面的函数原型可以知道,调用方法的时候,receiver的isa指针会首先被使用,查找SEL对象是否存在。如果不存在,就继续用当前Class对象的isa指针查找父类的SEL定义中是否有需要的,直到NSObject为止。如果都没有的话就抛一个NSInvalidArgument异常(见过最多的……)。

obj_msgSend

原型为objc_msgSend(id receiver,SEL selector,参数…)

继续沿着上面说的方法调用中的isa指针查找来看,如果找到了SEL的定义,那么拿到receiver的self指针进行SEL的调用,当然参数要传过去。

动态方法

obj-c 2.0里面引入了@property、@syncthesize指令方便地声明属性。对于仍然需要自定义setter/getter的属性来说可以用@dynamic指定。除了手动写之外,还可以在运行时指定

其实就是重写一个class method,将传入的SEL动态设定为setter或者getter

// 声明一个函数,用来动态添加。前两个参数不能变——因为要转换成IMP类型
void dynamicMethod(id self,SEL _cmd,float w){
  printf("dynamicMethod-%s\n",[NSStringFromSelector(_cmd)
cStringUsingEncoding:NSUTF8StringEncoding]);
  printf("%f\n",w);
}

// 类方法
+(BOOL) resolveInstanceMethod: (SEL) sel{
  NSString *methodName=NSStringFromSelector(sel);
  BOOL result=NO;
// 检查SEL的名字是必要的,因为只有符合命名规则的函数才可以用
  if ([methodName isEqualToString:@"setHeight:"]) {
   class_addMethod([self class], sel, (IMP) dynamicMethod,
                  "v@:f");
  result=YES;
}
  return result;
}

注意class_addMethod最后一个参数,这是函数签名的简单表示。v=void, @=id, :=SEL, f=float,要根据实际函数定义进行更改。

这段在apple的文档中也专门有一篇文章,不过写得晦涩到让人不得不觉得是故意让人看不懂。

有关self

Instance method里面的self是当前对象的首地址;Class method里面的则是Class指针,也就是isa。

下面语句位于Coder类的init方法中,会打印出来什么?第一反应肯定是Coder, Person。但实际上都是Coder……因为obj-c的消息机制是会不停地寻找父类方法。最后virtual到子类上。

NSLog( @"%@, %@", [self class], [super class] );

Method类型

定义,可以看到Method类型是SEL和IMP的合体成果

typedef struct objc_method *Method;
struct objc_method {
  SEL method_name;
  char *method_types;
  IMP method_imp;
};

动态替换方法,既然METHOD是struct,就可以直接用->访问它的成员了,例如在Coder类里面可以用父类Person的某个方法替换自己的:

SEL selGetInfo = @selector(GetInfo:);
    Method myMethod = class_getInstanceMethod([self class], selGetInfo);
    Method baseMethod = class_getInstanceMethod([Person class], selGetInfo);
    // 不过10.5之后就拿不到method_imp了
   myMethod->method_imp =baseMethod->method_imp;

07容器类、Block和NSPredicate

NS(Mutable)Array

遍历队列中对象的方法,使用makeObjectsPerformSelector。

Coder* siw = [Coder personWithName:@"Si Wei" andAge:25];
    [siw setLanguage:@"C#"];
    Coder* yexd = [Coder personWithName:@"Ye Xiaodong" andAge:25];
    [yexd setLanguage:@"Objective-C"];
    Person* nan_zhang = [Person personWithName:@"Zhang Nan" andAge:29];
    NSArray* objs = [NSArray arrayWithObjects:siw, yexd, nan_zhang, nil];
    [objs makeObjectsPerformSelector:@selector(GetInfo:TheSecondArg:) withObject:(id)10]; // GetInfo有两个参数,而且返回一个NSString*
    [objs makeObjectsPerformSelector:@selector(ShowInfo)];

不过比较郁闷的是这个方法不能返回值,也不能使用一个以上的参数调用SEL。如果需要返回值或者传入多个参数,还是要用NSInvocation。

writeToFile方法的第二个参数atomically,表示的是是否采用原子操作。即如果参数为YES,那么写入文件时候先写入临时文件,完成后覆盖所要真正写入的文件。这样可以避免写入过程中系统出现问题导致文件损坏。

NSArray也支持切片操作,不过语法太不灵活了——或者说obj-c中所有容器类的访问语法都太罗嗦。连续的元素切片用subarrayWithRange,不连续的用objectsAtIndexes

// 连续
    NSRange range;
    range.location = 0;
    range.length = 2;
    NSArray* objs_new = [objs subarrayWithRange:range];
    [objs_new makeObjectsPerformSelector:@selector(ShowInfo)];
    // 不连续
    NSMutableIndexSet* indices = [NSMutableIndexSet indexSet];
    [indices addIndex:0];
    [indices addIndex:1];
    NSArray* objs_fromIndex = [objs objectsAtIndexes:indices];
    [objs_fromIndex makeObjectsPerformSelector:@selector(ShowInfo)];

排序操作,NSArray提供了N个方法,按照所利用的排序参数,简单可以分为descriptor、selector、function和block排序

function排序

需要实现一个签名为NSInteger (*)(id, id, void *)的方法

NSInteger mySortByAge(id o1, id o2, void *context) {
    int v1 = [(Person*)o1 age];
    int v2 = [(Person*)o2 age];
    if (v1 < v2)
        return NSOrderedAscending;
    else if (v1 > v2)
        return NSOrderedDescending;
    else
        return NSOrderedSame;
}

使用的时候把指针传进去就行了

NSArray* objs_sortedByFunction = [objs sortedArrayUsingFunction:mySortByAge context:nil];
    [objs_sortedByFunction makeObjectsPerformSelector:@selector(ShowInfo)];

selector排序

需要排序的类要实现一个方法,传入一个和自身类型相同的参数,同self进行自定义比较。NSString的例子看下面的应用,在NSSortDescriptor初始化的时候传入了xxxCompare方法作为具体的比较方法,而其他内置类也都实现了compare方法,例如NSNumber就可以用compare方法的SEL。对于自己的类则需要实现一个方法,返回NSComparisonResult(其实就是NSInteger,枚举),要声明,因为不是继承来的。

- (NSComparisonResult) ageCompare:(id) rhsPerson {
    int v1 = [self age];
    int v2 = [(Person*)rhsPerson age];
    if (v1 < v2)
        return NSOrderedAscending;
    else if (v1 > v2)
        return NSOrderedDescending;
    else
        return NSOrderedSame;
}

使用和function没什么区别,只不过参数是SEL而已。

NSArray* objs_sortedBySelector = [objs sortedArrayUsingSelector:@selector(ageCompare:)];
    [objs_sortedBySelector makeObjectsPerformSelector:@selector(ShowInfo)];

descriptor排序

是一种比较特殊的包含比较信息的类,主要用于自定义类型的排序。需要指定按照什么key——也就是property进行排序,以及具体的排序算法(selector)和排序选项(例如是否倒序等)。看一下代码就清楚了

NSSortDescriptor *sortDesc = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES               selector:@selector(localizedCaseInsensitiveCompare:)];
    NSArray* objs_sortedByDescriptor = [objs sortedArrayUsingDescriptors:[NSArray arrayWithObjects:sortDesc, nil]];
    //NSArray* descriptors = [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:NO];
    NSArray* objs_sortedByDescriptor = [objs sortedArrayUsingDescriptors:descriptors];
    [objs_sortedByDescriptor makeObjectsPerformSelector:@selector(ShowInfo)];

block需要特别记录一下,所以分出一个条目。

Block

数组排序时候有个sortedArrayUsingComparator方法,这里的参数类型为NSComparator。这个类其实就是Block所定义的

typedef NSComparisonResult (^NSComparator)(id obj1, id obj2);

所以我们可以直接

NSComparator myComparator = ^(id obj1, id obj2){}:

NSArray中还有指明要使用Block的方法,例如- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block

Block其实可以看作JS里面的(){}();方法以及.Net中的匿名方法,即不需要声明就直接使用的方法,主要用处包括回调、事件处理和排序。声明和实现可以像上面的NSComparator一样分开,也可以写在一起:

NSInteger (^blockExample)(int, NSString*) = ^( int i, NSString* s ) {
        NSLog( @"%d, %@", i, s );
    };
    blockExample( 42, @"hello" );

对于NSArray的排序(或者其他需要Block作为参数的函数)来说,可以写成下面两种形式:

// 声明和使用
NSComparison ( ^ageComparator )( id, id ) = ^( id o1, id o2 ) {
    // 比较函数的实现
};
    NSArray* objs_sortedByComparator = [objs sortedArrayUsingComparator:ageComparator];
// 不声明,彻底匿名使用
    NSArray* objs_sortedByComparator = [objs sortedArrayUsingComparator:^( id o1, id o2 ) { //...
    }];

枚举一个容器则可以类似于:

NSArray *tmpArray = [timeZoneNames objectsAtIndexes:areaIndexes];
[tmpArray enumerateObjectsWithOptions:NSEnumerationConcurrent|NSEnumerationReverse
                           usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
                               [areaArray addObject:[obj substringFromIndex:[area length]+1]];
}];

实际上可以看作是obj-c里面闭包的最好例子。更多可以看http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Blocks/Articles/00_Introduction.html

NS(Mutable)Dictionary

基本的操作,例如初始化、插入、删除、查找等,不说了。

排序也不说了,其实就相当于将keys提取成NSArray进行排序。

那还有啥可说的呢……比如自定义的排序key。默认情况下只能使用NSString,但自定义类也可以当作hash key使用,需要实现下面这两项内容。实际情况中需要自定义key的场合非常少。
NSCopying协议。将key/value加入字典时候,系统实际上会拷贝一份key使用。所以copyWithZone必须实现。

hash()和isEqual()方法。这两个方法用于生成项目的hash值,以及比较两个对象的hash值。
- (NSUInteger) hash{
  return pid+[name hash];
}
-(BOOL) isEqual: (id) p{
  if(pid==[p pid]&&[name isEqualToString: [p name]]){     return YES;
  }else{
    return NO;
  }
}

MapTable

感觉上和NSMutableDictionary很像,也是根据key进行1-1对应的容器,也可以增删元素。但它不一样的地方在于内部是弱引用的。

NSPointerFunctionsOptions keyOptions=NSPointerFunctionsStrongMemory |
        NSPointerFunctionsObjectPersonality | NSPointerFunctionsCopyIn;
    NSPointerFunctionsOptions valueOptions=NSPointerFunctionsOpaqueMemory |
        NSPointerFunctionsOpaquePersonality;
    NSMapTable* mapTable=[NSMapTable mapTableWithKeyOptions: keyOptions valueOptions:valueOptions];
    NSString* key1 = @"siwei";
    NSMapInsert( mapTable, key1, siw );
    NSLog( @"%@, %@", *(int*)NSMapGet( mapTable, key1 ), [(Coder*)NSMapGet( mapTable, key1 ) language] );
    NSLog( @"%d", [siw retainCount] );  // 1

而其他内置容器类基本都是强引用

NSArray* objs = [NSArray arrayWithObjects:siw, yexd, nan_zhang, nil];
    NSLog( @"%d", [siw retainCount] );  // 2
    NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:siw, @"siwei", yexd, @"Ye Xiaodong", nan_zhang, @"Zhang Nan", nil];
    NSLog( @"%d", [siw retainCount] );  // 3

NS(Mutable)Set

Set里面的元素是无序排列且无key的。初始化和array一样,也可以直接用initWithArray从array创建。

NSSet* set = [NSSet setWithObjects:siw, yexd, nan_zhang, nil];
    NSLog( @"%d", [siw retainCount] );  // 2

和Dictionary一样,Set也有对应的弱引用容器类,就叫Hash Table

NSHashTable* hashTable=[[NSHashTable alloc] initWithOptions:
    NSPointerFunctionsOpaqueMemory |NSPointerFunctionsOpaquePersonality
    capacity: 1];
    NSHashInsert( hashTable, siw );
    NSLog( @"%d", [siw retainCount] );  // 1

NSPredicate

这东西用起来有点像.Net的LINQ,是根据条件从集合里面筛选数据的语法糖。

NSPredicate* predicate = [NSPredicate predicateWithFormat:@"age > 28"];
    int i = 0;
    for (; i < [objs count]; ++i ) {
        if ( [predicate evaluateWithObject: [objs objectAtIndex:i]] ) {
            NSLog( @"%@", [[objs objectAtIndex:i] GetInfo:1 TheSecondArg:YES] );
        }
    }

可以使用的运算符包括AND、OR、NOT、BETWEEN、IN、LIKE等

模板

NSPredicate* templatePredicate = [NSPredicate predicateWithFormat:@"name == $NAME"];
    NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
        @"Si Wei", @"NAME", nil];
    NSPredicate *pre=[templatePredicate predicateWithSubstitutionVariables: dict];
    //NSPredicate* predicate = [NSPredicate predicateWithFormat:@"age > 28"];
    int i = 0;
    for (; i < [objs count]; ++i ) {
        if ( [templatePredicate evaluateWithObject: [objs objectAtIndex:i]] ) {
            NSLog( @"%@", [[objs objectAtIndex:i] GetInfo:1 TheSecondArg:YES] );
        }
    }

其实上面Array里面切片方法也应该包括这个NSPredicate,因为它可以用来筛选array元素

NSPredicate* predicate = [NSPredicate predicateWithFormat:@"age > 28"];
    NSArray* objs_filtered = [objs filteredArrayUsingPredicate: predicate];
    NSLog( @"%@", [[objs_filtered objectAtIndex:0] GetInfo:1 TheSecondArg:YES] );

字符串运算

  • BEGINWITH, ENDWITH, CONTAINS
  • c和d分别表示大小写不敏感和重音符号不敏感,例如如果是”name BEGINWITH[d]’e'”的话,那么“étudent”这样的词也会符合条件

LIKE结合?和*可以进行通配符匹配

正则表达式

NSString* regex = @"^S.+i$";
    NSPredicate* regexPredicate= [NSPredicate predicateWithFormat:@"name MATCHES %@", regex];
    NSArray* objs_filtered = [objs filteredArrayUsingPredicate:regexPredicate];
    NSLog( @"%@", [[objs_filtered objectAtIndex:0] GetInfo:1 TheSecondArg:YES] );

容器类的深浅拷贝

默认情况下用一个容器去初始化另外一个容器是浅拷贝,也就是说只是retain里面的引用而已

可能的深拷贝,用重载的initWithArray:copyItems:YES。不过之所以说这是“可能的”,是因为容器里面的对象不一定实现了NSCopying协议!如果没有实现的话,对于对象本身的拷贝还是浅拷贝。

NSArray *deepCopyArray=[[NSArray alloc] initWithArray: someArray copyItems: YES];

真正的深拷贝,需要用到NSKeyedArchiver。但需要的时间和空间开销必然陡增。

NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
          [NSKeyedArchiver archivedDataWithRootObject: oldArray]];

4 Responses to “Objective-C学习笔记(4-7)”

  1. hhb说道:

    你好 Coder* me = [[Coder alloc] initWithArgs:@”Si Wei” andAge:25 andLanguage:@”C#”];
    Computer* computer = [[Computer alloc] init];
    NSNotificationCenter* notifyCenter = [NSNotificationCenter defaultCenter];
    [notifyCenter postNotificationName:@”workload_notify” object:computer];
    [computer setWorkload:10];
    [notifyCenter postNotificationName:@”workload_notify” object:computer];
    [me release];
    [computer release];
    为啥写了两句 [notifyCenter postNotificationName:@”workload_notify” object:computer]; 啥意思呢?

  2. Alonzo说道:

    Hello blogger, i found this post on 17 spot
    in google’s search results. I’m sure that your low rankings are caused by high
    bounce rate. This is very important ranking factor. One of the biggest reason for high bounce rate is due to visitors hitting the back button. The higher your bounce
    rate the further down the search results your posts and pages will end up, so having
    reasonably low bounce rate is important for improving your rankings
    naturally. There is very handy wp plugin which can help you.
    Just search in google for:
    Seyiny’s Bounce Plugin