过去的一年以来,我做了进口葡萄酒行业

我自工作以来,一直从事IT及编码行业工作,做得相当快乐和辛苦,也非常有成就感,但是很多事情光有成就感已经远远不足以生存在我朝,人生需要有很多东东,很多体验,于是我过去的一年开始,做起了进口葡萄酒行业,直接销售了来自法国、美国、德国、西班牙、智利、阿根廷、澳洲等世界葡萄酒。

欢迎大家支持我的新事业,在这儿,我保证我销售的进口葡萄酒是一批高品质,而价格非常公道的原瓶进口葡萄酒。如果您能够买到品质比我的好,价格比我的低的同款行货(非假货,非仿货,非次货。。。),请告诉我它们的出处,我将非常乐意为您补单,或是给您意外惊喜。

欢迎查看我所销售的进口葡萄酒:请猛击这里。

同时也非常欢迎您直接跟我联系,我的手机:13132914138,我的微信号和QQ号:1272000

同时您也可以直接打开我的微店:请猛击这里。

HTML5演示Demo

Just clone some html5 js code from lightapp & make a demo. It’s used to build simple app for book, manual and posters. It supports audio, video & baidu map, etc.
刚刚从lightapp上克隆了一些javascript代码,用于手机端展示html5动态页面的,可以非常方便的制作一些简单的宣传类应用,它支持声音,视频,百度地图展示等功能。

It’s here. 猛击这里展示。 直接输入:http://airflypan.com/ttpogx/ 也行。

iOS日期处理

Dates

        NSDate类提供了创建date,比较date以及计算两个date之间间隔的功能。Date对象是不可改变的。

        如果你要创建date对象并表示当前日期,你可以alloc一个NSDate对象并调用init初始化:

  1. NSDate *now = [[NSDate alloc] init];  

        或者使用NSDate的date类方法来创建一个日期对象。如果你需要与当前日期不同的日期,你可以使用NSDate的initWithTimeInterval…或dateWithTimeInterval…方法,你也可以使用更复杂的calendar或date components对象。

        创建一定时间间隔的NSDate对象:

  1. NSTimeInterval secondsPerDay = 24 * 60 * 60;  
  2.   
  3. NSDate *tomorrow = [[NSDate alloc] initWithTimeIntervalSinceNow:secondsPerDay];  
  4.   
  5. NSDate *yesterday = [[NSDate alloc] initWithTimeIntervalSinceNow:-secondsPerDay];  
  6.   
  7. [tomorrow release];  
  8. [yesterday release];  


        使用增加时间间隔的方式来生成NSDate对象:

  1. NSTimeInterval secondsPerDay = 24 * 60 * 60;  
  2.   
  3. NSDate *today = [[NSDate alloc] init];  
  4. NSDate *tomorrow, *yesterday;  
  5.   
  6. tomorrow = [today dateByAddingTimeInterval: secondsPerDay];  
  7. yesterday = [today dateByAddingTimeInterval: -secondsPerDay];  
  8.   
  9. [today release];  


        如果要对NSDate对象进行比较,可以使用isEqualToDate:, compare:, laterDate:和 earlierDate:方法。这些方法都进行精确比较,也就是说这些方法会一直精确比较到NSDate对象中秒一级。例如,你可能比较两个日期,如果他们之间的间隔在一分钟之内则认为这两个日期是相等的。在这种情况下使用,timeIntervalSinceDate:方法来对两个日期进行比较。下面的代码进行了示例:

  1. if (fabs([date2 timeIntervalSinceDate:date1]) < 60) …  

 

NSCalendar & NSDateComponents

        日历对象封装了对系统日期的计算,包括这一年开始,总天数以及划分。你将使用日历对象对绝对日期与date components(包括年,月,日,时,分,秒)进行转换。

        NSCalendar定义了不同的日历,包括佛教历,格里高利历等(这些都与系统提供的本地化设置相关)。NSCalendar与NSDateComponents对象紧密相关。

        你可以通过NSCalendar对象的currentCalendar方法来获得当前系统用户设置的日历。

  1. NSCalendar *currentCalendar = [NSCalendar currentCalendar];  
  2.   
  3. NSCalendar *japaneseCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSJapaneseCalendar];  
  4.   
  5. NSCalendar *usersCalendar = [[NSLocale currentLocale] objectForKey:NSLocaleCalendar];  

        usersCalendar和currentCalendar对象是相等的,尽管他们是不同的对象。

        你可以使用NSDateComponents对象来表示一个日期对象的组件——例如年,月,日和小时。如果要使一个NSDateComponents对象有意义,你必须将其与一个日历对象相关联。下面的代码示例了如何创建一个NSDateComponents对象:

  1. NSDateComponents *components = [[NSDateComponents alloc] init];  
  2.   
  3. [components setDay:6];  
  4. [components setMonth:5];  
  5. [components setYear:2004];  
  6.   
  7. NSInteger weekday = [components weekday]; // Undefined (== NSUndefinedDateComponent)  


        要将一个日期对象解析到相应的date components,你可以使用NSCalendar的components:fromDate:方法。此外日期本身,你需要指定NSDateComponents对象返回组件。

  1. NSDate *today = [NSDate date];  
  2.   
  3. NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];  
  4.   
  5. NSDateComponents *weekdayComponents = [gregorian components:(NSDayCalendarUnit | NSWeekdayCalendarUnit) fromDate:today];  
  6.   
  7. NSInteger day = [weekdayComponents day];  
  8. NSInteger weekday = [weekdayComponents weekday];  
  9.   
  10. 同样你也可以从NSDateComponents对象来创建NSDate对象:  
  11. NSDateComponents *components = [[NSDateComponents alloc] init];  
  12.   
  13. [components setWeekday:2]; // Monday  
  14. [components setWeekdayOrdinal:1]; // The first Monday in the month  
  15. [components setMonth:5]; // May  
  16. [components setYear:2008];  
  17.   
  18. NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];  
  19.   
  20. NSDate *date = [gregorian dateFromComponents:components];  


        为了保证正确的行为,您必须确保使用的组件在日历上是有意义的。指定“出界”日历组件,如一个-6或2月30日在公历中的日期值产生未定义的行为。

        你也可以创建一个不带年份的NSDate对象,这样的操作系统会自动生成一个年份,但在后面的代码中不会使用其自动生成的年份。

  1. NSDateComponents *components = [[NSDateComponents alloc] init];  
  2.   
  3. [components setMonth:11];  
  4. [components setDay:7];  
  5.   
  6. NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];  
  7.   
  8. NSDate *birthday = [gregorian dateFromComponents:components];  


        下面的示例显示了如何从一个日历置换到另一个日历:

  1. NSDateComponents *comps = [[NSDateComponents alloc] init];  
  2.   
  3. [comps setDay:6];  
  4. [comps setMonth:5];  
  5. [comps setYear:2004];  
  6.   
  7. NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];  
  8.   
  9. NSDate *date = [gregorian dateFromComponents:comps];  
  10.   
  11. [comps release];  
  12. [gregorian release];  
  13.   
  14. NSCalendar *hebrew = [[NSCalendar alloc] initWithCalendarIdentifier:NSHebrewCalendar];  
  15.   
  16. NSUInteger unitFlags = NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit;  
  17.   
  18. NSDateComponents *components = [hebrew components:unitFlags fromDate:date];  
  19.   
  20. NSInteger day = [components day]; // 15  
  21. NSInteger month = [components month]; // 9  
  22. NSInteger year = [components year]; // 5764  

 

历法计算

        在当前时间加上一个半小时:

  1. NSDate *today = [[NSDate alloc] init];  
  2.   
  3. NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];  
  4.   
  5. NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];  
  6.   
  7. [offsetComponents setHour:1];  
  8. [offsetComponents setMinute:30];  
  9.   
  10. // Calculate when, according to Tom Lehrer, World War III will end  
  11. NSDate *endOfWorldWar3 = [gregorian dateByAddingComponents:offsetComponents toDate:today options:0];  


        获得当前星期中的星期天(使用格里高利历):

  1. NSDate *today = [[NSDate alloc] init];  
  2.   
  3. NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];  
  4.   
  5. // Get the weekday component of the current date  
  6. NSDateComponents *weekdayComponents = [gregorian components:NSWeekdayCalendarUnit fromDate:today];  
  7.   
  8. /*  
  9. Create a date components to represent the number of days to subtract from the current date.  
  10.   
  11. The weekday value for Sunday in the Gregorian calendar is 1, so subtract 1 from the number of days to subtract from the date in question.  (If today is Sunday, subtract 0 days.)  
  12. */  
  13.   
  14. NSDateComponents *componentsToSubtract = [[NSDateComponents alloc] init];  
  15.   
  16. [componentsToSubtract setDay: 0 – ([weekdayComponents weekday] – 1)];  
  17.   
  18. NSDate *beginningOfWeek = [gregorian dateByAddingComponents:componentsToSubtract toDate:today options:0];  
  19.   
  20. /*  
  21. Optional step:  
  22. beginningOfWeek now has the same hour, minute, and second as the original date (today).  
  23.   
  24. To normalize to midnight, extract the year, month, and day components and create a new date from those components.  
  25. */  
  26.   
  27. NSDateComponents *components = [gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate: beginningOfWeek];  
  28.   
  29. beginningOfWeek = [gregorian dateFromComponents:components];  

        如何可以计算出一周的第一天(根据系统的日历设置):

  1. NSDate *today = [[NSDate alloc] init];  
  2.   
  3. NSDate *beginningOfWeek = nil;  
  4.   
  5. BOOL ok = [gregorian rangeOfUnit:NSWeekCalendarUnit startDate:&beginningOfWeek interval:NULL forDate: today];  


        获得两个日期之间的间隔:

  1. NSDate *startDate = …;  
  2. NSDate *endDate = …;  
  3.   
  4. NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];  
  5.   
  6. NSUInteger unitFlags = NSMonthCalendarUnit | NSDayCalendarUnit;  
  7.   
  8. NSDateComponents *components = [gregorian components:unitFlags fromDate:startDate toDate:endDate options:0];  
  9.   
  10. NSInteger months = [components month];  
  11. NSInteger days = [components day];  

        使用Category来计算同一时代(AD|BC)两个日期午夜之间的天数:

  1. @implementation NSCalendar (MySpecialCalculations)  
  2.   
  3. -(NSInteger)daysWithinEraFromDate:(NSDate *) startDate toDate:(NSDate *) endDate {  
  4.      NSInteger startDay=[self ordinalityOfUnit:NSDayCalendarUnit inUnit: NSEraCalendarUnit forDate:startDate];  
  5.   
  6.      NSInteger endDay=[self ordinalityOfUnit:NSDayCalendarUnit inUnit: NSEraCalendarUnit forDate:endDate];  
  7.   
  8.      return endDay-startDay;  
  9. }  
  10.   
  11. @end  


        使用Category来计算不同时代(AD|BC)两个日期的天数:

  1. @implementation NSCalendar (MyOtherMethod)  
  2.   
  3. -(NSInteger) daysFromDate:(NSDate *) startDate toDate:(NSDate *) endDate {  
  4.   
  5.      NSCalendarUnit units=NSEraCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;  
  6.   
  7.      NSDateComponents *comp1=[self components:units fromDate:startDate];  
  8.      NSDateComponents *comp2=[self components:units fromDate endDate];  
  9.   
  10.      [comp1 setHour:12];  
  11.      [comp2 setHour:12];  
  12.   
  13.      NSDate *date1=[self dateFromComponents: comp1];  
  14.      NSDate *date2=[self dateFromComponents: comp2];  
  15.   
  16.      return [[self components:NSDayCalendarUnit fromDate:date1 toDate:date2 options:0] day];  
  17. }  
  18.   
  19. @end  


        判断一个日期是否在当前一周内(使用格里高利历):

  1. -(BOOL)isDateThisWeek:(NSDate *)date {  
  2.   
  3.      NSDate *start;  
  4.      NSTimeInterval extends;  
  5.   
  6.      NSCalendar *cal=[NSCalendar autoupdatingCurrentCalendar];  
  7.      NSDate *today=[NSDate date];  
  8.   
  9.      BOOL success= [cal rangeOfUnit:NSWeekCalendarUnit startDate:&start interval: &extends forDate:today];  
  10.   
  11.      if(!success)  
  12.         return NO;  
  13.   
  14.      NSTimeInterval dateInSecs = [date timeIntervalSinceReferenceDate];  
  15.      NSTimeInterval dayStartInSecs= [start timeIntervalSinceReferenceDate];  
  16.   
  17.      if(dateInSecs > dayStartInSecs && dateInSecs < (dayStartInSecs+extends)){  
  18.           return YES;  
  19.      }  
  20.      else {  
  21.           return NO;  
  22.      }  
  23. }  

iOS将字符串转换为日期时间格式

1、如何如何将一个字符串如“ 20110826134106”装化为任意的日期时间格式,下面列举两种类型:
   NSString* string = @”20110826134106″;
    NSDateFormatter *inputFormatter = [[[NSDateFormatter alloc] init] autorelease];
    [inputFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@”en_US”] autorelease]];
    [inputFormatter setDateFormat:@”yyyyMMddHHmmss”];
    NSDate* inputDate = [inputFormatter dateFromString:string];
    NSLog(@”date = %@”, inputDate);
    
    NSDateFormatter *outputFormatter = [[[NSDateFormatter alloc] init] autorelease]; 
    [outputFormatter setLocale:[NSLocale currentLocale]];
    [outputFormatter setDateFormat:@”yyyy年MM月dd日 HH时mm分ss秒”];
    NSString *str = [outputFormatter stringFromDate:inputDate];
    NSLog(@”testDate:%@”, str);
两次打印的结果为:
    date = 2011-08-26 05:41:06 +0000
    testDate:2011年08月26日 13时41分06秒

说明:上面的时间是美国时间,下面的没有设置

   NSString* string = @”Wed, 05 May 2011 10:50:00 +0800″;
    NSDateFormatter *inputFormatter = [[[NSDateFormatter alloc] init] autorelease];
    [inputFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@”en_US”] autorelease]];
    [inputFormatter setDateFormat:@”EEE, d MMM yyyy HH:mm:ss Z”];
    NSDate* inputDate = [inputFormatter dateFromString:string];
    NSLog(@”date = %@”, inputDate);

2、以前一直为这个事情纠结,无奈只能拼接字符串:

NSString *str=@”20120403000000″;

NSString *dateStr=[NSString stringWithFormat:@”有效期至:%@年%@月%@日”,
                           [str substringWithRange:NSMakeRange(0, 4)],
                           [str substringWithRange:NSMakeRange(4, 2)],
                           [str substringWithRange:NSMakeRange(6, 2)]];
这个方法笨,可是没办法,查了好多资料,都没明白,今天突然明白了,呵呵,只要把那个[inputFormatter setDateFormat:@”EEE, d MMM yyyy HH:mm:ss Z”];@“”里面的格式转化为你字符串的格式一切就OK了,不知道我说明白了吗?


3、iOS-NSDateFormatter 格式说明:

G: 公元时代,例如AD公元
    yy: 年的后2位
    yyyy: 完整年
    MM: 月,显示为1-12
    MMM: 月,显示为英文月份简写,如 Jan
    MMMM: 月,显示为英文月份全称,如 Janualy
    dd: 日,2位数表示,如02
    d: 日,1-2位显示,如 2
    EEE: 简写星期几,如Sun
    EEEE: 全写星期几,如Sunday
    aa: 上下午,AM/PM
    H: 时,24小时制,0-23
    K:时,12小时制,0-11
    m: 分,1-2位
    mm: 分,2位
    s: 秒,1-2位
    ss: 秒,2位
    S: 毫秒

常用日期结构:
yyyy-MM-dd HH:mm:ss.SSS
yyyy-MM-dd HH:mm:ss
yyyy-MM-dd
MM dd yyyy

iOS开发UITableView中行的操作

文章写得很好,转载自:http://my.oschina.net/plumsoft/blog/53271

这篇文章主要讲的表格的操作包括:标记行、移动行、删除行、插入行。

这次就不从头建立工程了,在http://www.oschina.net/code/snippet_164134_9876下载工程。这个工程就是最简单的产生一个表格并向其中写入数据。用Xcode 4.2打开它,在这个工程基础上实现以上操作。

1、标记行

这里讲的标记行指的是单击此行,可以实现在此行右边出现一个勾,如下图所示:

为了实现标记功能,在ViewController.m中@end之前添加代码:

#pragma mark -
#pragma mark Table Delegate Methods
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 
    UITableViewCell *oneCell = [tableView cellForRowAtIndexPath: indexPath];
    if (oneCell.accessoryType == UITableViewCellAccessoryNone) {
        oneCell.accessoryType = UITableViewCellAccessoryCheckmark;
    } else 
        oneCell.accessoryType = UITableViewCellAccessoryNone;
    [tableView deselectRowAtIndexPath:indexPath animated:YES]; 
}

该代码实现:单击某行时,若此行未被标记,则标记此行;若此行已经被标记,则取消标记。

运行效果如上图。

上面的代码实际上就是修改某行的accessoryType属性,这个属性可以设为四个常量:

UITableViewCellAccessoryCheckmark
UITableViewCellAccessoryDetailDisclosureButton
UITableViewCellAccessoryDisclosureIndicator
UITableViewCellAccessoryNone

效果依次如下图所示:

            

   UITableViewCellAccessoryCheckmark            UITableViewCellAccessoryDetailDisclosureButton

                 

UITableViewCellAccessoryDisclosureIndicator                   UITableViewCellAccessoryNone

注意,上面第二张图片中的蓝色圆圈不仅仅是一个图标,还是一个控件,点击它可以触发事件,在上一篇博客《iOS开发16:使用Navigation Controller切换视图》使用过。

2、移动行

想要实现移动或者删除行这样的操作,需要启动表格的编辑模式。使用的是setEditing:animated:方法。

2.1 打开ViewController.xib,将其中的表格控件映射成Outlet到ViewController.h,名称为myTableView。

2.2 打开ViewController.m,在viewDidLoad方法最后添加代码:

//启动表格的编辑模式
[self.myTableView setEditing:YES animated:YES];

2.3 在@end之前添加代码:

//打开编辑模式后,默认情况下每行左边会出现红的删除按钮,这个方法就是关闭这些按钮的
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView
           editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath { 
    return UITableViewCellEditingStyleNone; 
} 

//这个方法用来告诉表格 这一行是否可以移动
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath { 
    return YES; 
}

//这个方法就是执行移动操作的
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)
        sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
    NSUInteger fromRow = [sourceIndexPath row]; 
    NSUInteger toRow = [destinationIndexPath row]; 
    
    id object = [list objectAtIndex:fromRow]; 
    [list removeObjectAtIndex:fromRow]; 
    [list insertObject:object atIndex:toRow]; 
}

editingStyleForRowAtIndexPath这个方法中用到了常量UITableViewCellEditingStyleNone,它表示不可编辑,这里的编辑指的是删除和插入。表示表格行的编辑模式的常量有:

UITableViewCellEditingStyleDelete
UITableViewCellEditingStyleInsert
UITableViewCellEditingStyleNone

顾名思义,第一个表示删除,第二个表示插入,第三个表示不可编辑。

若将editingStyleForRowAtIndexPath方法中的UITableViewCellEditingStyleNone依次换成上面三个值,则它们运行的效果依次如下图所示:

      

2.4 运行,从下图可以看到实现了行的移动:

但是也会发现,现在无法对每行进行标记了。这说明,在编辑模式下,无法选择行,从而didSelectRowAtIndexPath这个方法不会执行。

3、删除行

从第2步过来,实现删除某行,其实比较简单了。

3.1将editingStyleForRowAtIndexPath方法中的UITableViewCellEditingStyleNone修改成UITableViewCellEditingStyleDelete。

3.2 在@end之前添加代码:

//这个方法根据参数editingStyle是UITableViewCellEditingStyleDelete
//还是UITableViewCellEditingStyleDelete执行删除或者插入
- (void)tableView:(UITableView *)tableView commitEditingStyle:
    (UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    NSUInteger row = [indexPath row];
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        [self.list removeObjectAtIndex:row]; 
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                         withRowAnimation:UITableViewRowAnimationAutomatic]; 
    }
}

在这个方法中又出现了一个常量:UITableViewRowAnimationAutomatic,它表示删除时的效果,类似的常量还有:

UITableViewRowAnimationAutomatic
UITableViewRowAnimationTop
UITableViewRowAnimationBottom
UITableViewRowAnimationLeft
UITableViewRowAnimationRight
UITableViewRowAnimationMiddle
UITableViewRowAnimationFade
UITableViewRowAnimationNone

它们的效果就不一一介绍了,可以在实际使用时试试。

3.3 运行,看看效果:

      

刚运行时显示如左边的图片,点击某一行左边的圆圈图标,会显示如中间图片所示。然后点击Delegate按钮,那一行就会被删除掉,如右边的那张图片所示,它显示的是删除时的效果。

4、插入行

这个与删除行类似。

4.1 首先将editingStyleForRowAtIndexPath方法中的UITableViewCellEditingStyleDelete修改成UITableViewCellEditingStyleInsert。

4.2在3.2添加的方法中添加代码:

else {
    //我们实现的是在所选行的位置插入一行,因此直接使用了参数indexPath
    NSArray *insertIndexPaths = [NSArray arrayWithObjects:indexPath,nil];
    //同样,将数据加到list中,用的row
    [self.list insertObject:@"新添加的行" atIndex:row];
    [tableView insertRowsAtIndexPaths:insertIndexPaths withRowAnimation:UITableViewRowAnimationRight];
}

上面的代码中也可以不用insertRowsAtIndexPaths方法,而直接使用[tableView reloadData];语句,但是这样就没有添加的效果了。

4.3 好了,运行一下:

      

刚运行时如上面左图所示,单击了某个加号后,新的一行就从右边飞进来了,因为在insertRowsAtIndexPaths中用了参数UITableViewRowAnimationRight。

Objective-C中的Class(类类型),Selector(选择器SEL),函数指针(IMP)

看到了一篇牛文“Objective-C 2.0 with Cocoa Foundation— 5,Class类型,选择器Selector以及函数指针”,讲得十分精彩,忍不住把它的代码加上注释整理于此,以便日后查看。
个人体会:obj-C中的“Class类型变量”比c#中的Object基类还要灵活,可以用它生成任何类型的实例(但是它又不是NSObject)。而选择器SEL与函数指针IMP,如果非要跟c#扯上关系的话,这二个结合起来,就点类似c#中的反射+委托,可以根据一个方法名称字符串,直接调用方法。
“牛”的基类 Cattle.h
1
2
3
4
5
6
7
8
#import <Foundation/Foundation.h>
@interface Cattle : NSObject {
    int legsCount;
}
- (void)saySomething;
- (void)setLegsCount:(int) count;
@end
 Cattle.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import "Cattle.h"
@implementation Cattle
-(void) saySomething
{
    NSLog(@"Hello, I am a cattle, I have %d legs.", legsCount);
}
-(void) setLegsCount:(int) count
{
    legsCount = count;
}
@end
子类“公牛” Bull.h
1
2
3
4
5
6
7
8
9
10
#import <Foundation/Foundation.h>
#import "Cattle.h"
@interface Bull : Cattle {
    NSString *skinColor;
}
- (void)saySomething;
- (NSString*) getSkinColor;
- (void) setSkinColor:(NSString *) color;
@end
Bull.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#import "Bull.h"
@implementation Bull
-(void) saySomething
{
    NSLog(@"Hello, I am a %@ bull, I have %d legs.", [self getSkinColor],legsCount);
}
-(NSString*) getSkinColor
{
    return skinColor;
}
- (void) setSkinColor:(NSString *) color
{
    skinColor = color;
}
@end
代理类DoProxy.h (关键的代码都在这里)
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
#import <Foundation/Foundation.h>
//定义几个字符串常量
#define SET_SKIN_COLOR @"setSkinColor:"
#define BULL_CLASS @"Bull"
#define CATTLE_CLASS @"Cattle"
@interface DoProxy : NSObject {
    BOOL notFirstRun;
    
    id cattle[3];
    //定义二个选择器
    SEL say;
    SEL skin;
    
    //定义一个函数指针(传统C语言的处理方式)
    void(*setSkinColor_Func)(id,SEL,NSString*);
    
    //定义一个IMP方式的函数指针(obj-C中推荐的方式)
    IMP say_Func;
    
    //定义一个类
    Class bullClass;
}
-(void) doWithCattleId:(id) aCattle colorParam:(NSString*) color;
-(void) setAllIVars;
-(void) SELFuncs;
-(void) functionPointers;
@end
DoProxy.m
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 "DoProxy.h"
#import "Cattle.h"
#import "Bull.h"
@implementation DoProxy
//初始化所有变量
- (void) setAllIVars
{  
    cattle[0] = [Cattle new];
    
    bullClass = NSClassFromString(BULL_CLASS);
    //即cattle[1],cattle[2]都是Bull类的实例
    cattle[1] = [bullClass new];
    cattle[2] = [bullClass new];
    
    say = @selector(saySomething);
    skin = NSSelectorFromString(SET_SKIN_COLOR);
}
//初始化id
- (void) doWithCattleId:(id) aCattle colorParam:(NSString*) color
{
    //第一次运行的时候
    if(notFirstRun == NO)
    {
        NSString *myName = NSStringFromSelector(_cmd);//取得当前正在执行的方法的名字
        NSLog(@"Running in the method of %@", myName);
        notFirstRun = YES;//修改初次运行标志位
    }
    
    NSString *cattleParamClassName = [aCattle className];//取得aCattle的"类名称"
    
    //如果aCattle是Bull或Cattle类的实例
    if([cattleParamClassName isEqualToString:BULL_CLASS] || [cattleParamClassName isEqualToString:CATTLE_CLASS])
    {
        [aCattle setLegsCount:4];//设置牛的4条腿
        if([aCattle respondsToSelector:skin])//如果aCattle对应的是类中,有定义方法"setSkinColor"
        {
            [aCattle performSelector:skin withObject:color];//则调用setSkinColor方法
        }
        else
        {
            NSLog(@"Hi, I am a %@, have not setSkinColor!", cattleParamClassName);//否则输出相应的提示信息
        }
        [aCattle performSelector:say];//最后执行saySomething方法(这二个方法在Bull与Cattle类中都有,所以肯定能运行)
    }
    else //如果aCattle即不是Bull类也不是Cattle类的实例
    {
        NSString *yourClassName = [aCattle className];
        NSLog(@"Hi, you are a %@, but I like cattle or bull!", yourClassName);//显示这个"异类"的相关信息
    }
}
//初始化选择器以及相应函数
- (void) SELFuncs
{
    [self doWithCattleId:cattle[0] colorParam:@"brown"];
    [self doWithCattleId:cattle[1] colorParam:@"red"];
    [self doWithCattleId:cattle[2] colorParam:@"black"];
    [self doWithCattleId:self colorParam:@"haha"];//这里故意传入一个异类self(即DoProxy本身),DoProxy当然不是Bull或Cattle
}
//函数指针测试
- (void) functionPointers
{
    //取得函数指针的第一种方式
    setSkinColor_Func=(void (*)(id, SEL, NSString*)) [cattle[1] methodForSelector:skin];
    //上面的语句其实等效于下面这种方法
    //IMP setSkinColor_Func = [cattle[1] methodForSelector:skin];
    
    //用第二种方法取得saySomething的函数指针
    say_Func = [cattle[1] methodForSelector:say];
    
    //用函数指针的形式调用setSkinColor
    setSkinColor_Func(cattle[1],skin,@"verbose");
    
    NSLog(@"Running as a function pointer will be more efficiency!");
    
    //调用saySomething方法
    say_Func(cattle[1],say);
}
@end
测试主函数main()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import <Foundation/Foundation.h>
#import "DoProxy.h"
int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    DoProxy *doProxy = [DoProxy new];
    
    [doProxy setAllIVars];
    [doProxy SELFuncs];
    [doProxy functionPointers];
    
    [doProxy release];
    [pool drain];
    return 0;
}

运行结果:

2011-02-28 21:40:33.240 HelloSelector[630:a0f] Running in the method of doWithCattleId:colorParam:
2011-02-28 21:40:33.245 HelloSelector[630:a0f] Hi, I am a Cattle, have not setSkinColor!
2011-02-28 21:40:33.247 HelloSelector[630:a0f] Hello, I am a cattle, I have 4 legs.
2011-02-28 21:40:33.248 HelloSelector[630:a0f] Hello, I am a red bull, I have 4 legs.
2011-02-28 21:40:33.250 HelloSelector[630:a0f] Hello, I am a black bull, I have 4 legs.
2011-02-28 21:40:33.251 HelloSelector[630:a0f] Hi, you are a DoProxy, but I like cattle or bull!
2011-02-28 21:40:33.252 HelloSelector[630:a0f] Running as a function pointer will be more efficiency!
2011-02-28 21:40:33.254 HelloSelector[630:a0f] Hello, I am a verbose bull, I have 4 legs.

作者:菩提树下的杨过
出处:http://yjmyzz.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

Objective-c的动态调用函数的方法

最近在看关于selector的资料,感觉很好玩。

首先我能理解什么叫selector已经不容易了,查阅了很多资料。
其次是要动态调用,首先网上找到的方法是,这样的用法:

-(void)traceThem:(int)a traceThem2:(int)b{

NSLog(@”hello:%d 你好%d”,a,b);

}

[self performSelector:@selector(traceThem:traceThem2:) withObject:(id)1 withObject:(id)2];

上面代码的意思要调用函数名为traceThem:traceThem2:的函数,参数分别是1,2

(OC就叫发消息,它的函数说法是有一条traceThem:traceThem2:的消息,reciver是self,其实编译的时候还不是自己改成调用函数的概念。);

performSelector这样的调用函数方法,最多只能支持2个参数,你可以把参数放到NSDictionary传递。

搞到百度到的一大堆blog文都是自己写了一大段代码来支持performSelector多个参数的调用。。。

其实还有个好方法objc_msgSend:

objc_msgSend(self,@selector(traceThem:traceThem2:traceThme3:),参数1,参数2,参数3);

但是动态调用,就是要求@selector()的参数能动态输入,例如是配置到一个配置表中的字符串(NSString),方法如下:

SEL function = NSSelectorFromString(@”traceThem:traceThem2:traceThem3:”);

objc_msgSend(self,function,1,2,3);

BTW:

由于objc_msgSend是运行时的方法,所以要加入头文件,用open quickly可帮到大忙,如下:

,即需要的头文件:#import <objc/message.h>

 

objective-c的动态调用函数的方法 - Sylar_Lin - 低调做人高调做事

WWDC2014之App Extensions

一、关于App Extensions

extension是iOS8新开放的一种对几个固定系统区域的扩展机制,它可以在一定程度上弥补iOS的沙盒机制对应用间通信的限制。

extension的出现,为用户提供了在其它应用中使用我们应用提供的服务的便捷方式,比如用户可以在Todaywidgets中查看应用展示的简略信息,而不用再进到我们的应用中,这将是一种全新的用户体验;但是,extension的出现可能会减少用户启动应用的次数,同时还会增大开发者的工作量。

几个关键词

  • extension point

系统中支持extension的区域,extension的类别也是据此区分的,iOS上共有TodayShareActionPhoto EditingStorage ProviderCustom keyboard几种,其中Today中的extension又被称为widget

每种extension point的使用方式和适合干的活都不一样,因此不存在通用的extension。

  • app extension

即为本文所说的extension。extension并不是一个独立的app,它有一个包含在app bundle中的独立bundle,extension的bundle后缀名是.appex。其生命周期也和普通app不同,这些后文将会详述。

extension不能单独存在,必须有一个包含它的containing app。

另外,extension需要用户手动激活,不同的extension激活方式也不同,比如: 比如Today中的widget需要在Today中激活和关闭;Custom keyboard需要在设置中进行相关设置;Photo Editing需要在使用照片时在照片管理器中激活或关闭;Storage Provider可以在选择文件时出现;ShareAction可以在任何应用里被激活,但前提是开发者需要设置Activation Rules,以确定extension需要在合适出现。

  • containing app

尽管苹果开放了extension,但是在iOS中extension并不能单独存在,要想提交到AppStore,必须将extension包含在一个app中提交,并且app的实现部分不能为空,这个包含extension的app就叫containing app。

extension会随着containing app的安装而安装,同时随着containing app的卸载而卸载。

  • host app

能够调起extension的app被称为host app,比如widget的host app就是Today

二、extension和containing app、host app

2.1 extension和host app

extension和host app之间可以通过extensionContext属性直接通信,该属性是新增加的UIViewController类别:

1
2
3
4
5
6
@interface UIViewController(NSExtensionAdditions) <NSExtensionRequestHandling>
// Returns the extension context. Also acts as a convenience method for a view controller to check if it participating in an extension request.
@property (nonatomic,readonly,retain) NSExtensionContext *extensionContext NS_AVAILABLE_IOS(8_0);
@end

实际上extension和host app之间是通过IPC(interprocess communication)实现的,只是苹果把调用接口高度抽象了,我们并不需要关注那么底层的东西。

2.2 containing app和host app

他们之间没有任何直接关系,也从来不需要通信。

2.3 extension和containing app

这二者之间的关系最复杂,纠纠缠缠扯不清关系。

  • 不能直接通信

首先,尽管extension的bundle是放在containing app的bundle中,但是他们是两个完全独立的进程,之间不能直接通信。不过extension可以通过openURL的方式启动containing app(当然也能启动其它app),不过必须通过extensionContext借助host app来实现:

1
2
3
4
5
6
7
8
//通过openURL的方式启动Containing APP
- (void)openURLContainingAPP
{
    [self.extensionContext openURL:[NSURL URLWithString:@"appextension://123"]
                 completionHandler:^(BOOL success) {
                     NSLog(@"open url result:%d",success);
                 }];
}

extension中是无法直接使用openURL的。

  • 可以共享Shared resources

extension和containing app可以共同读写一个被称为Shared resources的存储区域,这是通过App Groups实现的,后文将会详述。

三者间的关系可以通过官网给的两张图片形象地说明:

detailed_communication

app_extensions_container_restrictions

  • containing app能够控制extension的出现和隐藏

通过以下代码,containing app可以让extension出现或隐藏(当然extension也可以让自己隐藏):

1
2
3
4
5
6
7
8
9
10
11
//让隐藏的插件重新显示
- (void)showTodayExtension
{
    [[NCWidgetController widgetController] setHasContent:YES forWidgetWithBundleIdentifier:@"com.wangzz.app.extension"];
}
//隐藏插件
- (void)hiddeTodayExtension
{
    [[NCWidgetController widgetController] setHasContent:NO forWidgetWithBundleIdentifier:@"com.wangzz.app.extension"];
}

三、App Groups

这是iOS8新开放的功能,在OS X上早就可用了。它主要用于同一group下的app共享同一份读写空间,以实现数据共享。

extension和containing app共同读写一份数据是很合理的需求,比如系统的股市应用,widget和app中都需要展示几个公司的股票数据,这就可以通过App Groups实现。

3.1 功能开启

为了便于后续操作,请先确保你的开发者账号在Xcode上处于登录状态。

  • 在app中开启

App Groups位于:

1
TARGETS-->AppExtensionDemo-->Capabilities-->App Groups

找到以后,将App Groups右上角的开关打开,然后选择添加groups,比如我的是group.wangzz,当然这是为了测试随便起得名字,正规点得命名规则应该是:group.com.company.app。

添加成功以后如下图所示:

app_group

  • 在extension中开启

我创建的是widget,target名称为TodayExtension,对应的App Groups位于:

1
TARGETS-->TodayExtension-->Capabilities-->App Groups

开启方式和app中一样,需要注意的是必须保证这里地App Groups名称和app中的相同,即为group.wangzz。

四、extension和containing app数据共享

App Groups给我们提供了同一group内app可以共同读写的区域,可以通过以下方式实现数据共享:

4.1 通过NSUserDefaults共享数据

  • 存数据

通过以下方式向NSUserDefaults中保存数据:

1
2
3
4
5
6
7
- (void)saveTextByNSUserDefaults
{
    NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.wangzz"];
    [shared setObject:_textField.text forKey:@"wangzz"];
    [shared synchronize];
}

需要注意的是:

1.保存数据的时候必须指明group id;

2.而且要注意NSUserDefaults能够处理的数据只能是可plist化的对象,详情见Property List Programming Guide

3.为了防止出现数据同步问题,不要忘记调用[shared synchronize];

  • 读数据

对应的读取数据方式:

1
2
3
4
5
6
7
- (NSString *)readDataFromNSUserDefaults
{
    NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.wangzz"];
    NSString *value = [shared valueForKey:@"wangzz"];
    return value;
}

4.2 通过NSFileManager共享数据

NSFileManager在iOS7提供了containerURLForSecurityApplicationGroupIdentifier方法,可以用来实现app group共享数据。

  • 保存数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (BOOL)saveTextByNSFileManager
{
    NSError *err = nil;
    NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"];
    containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/good"];
    NSString *value = _textField.text;
    BOOL result = [value writeToURL:containerURL atomically:YES encoding:NSUTF8StringEncoding error:&err];
    if (!result) {
        NSLog(@"%@",err);
    } else {
        NSLog(@"save value:%@ success.",value);
    }
    return result;
}
  • 读数据
1
2
3
4
5
6
7
8
9
- (NSString *)readTextByNSFileManager
{
    NSError *err = nil;
    NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"];
    containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/good"];
    NSString *value = [NSString stringWithContentsOfURL:containerURL encoding:NSUTF8StringEncoding error:&err];
    return value;
}

在这里我试着保存和读取的是字符串数据,但读写SQlite我相信也是没问题的。

  • 数据同步

两个应用共同读取同一份数据,就会引发数据同步问题。WWDC2014的视频中建议使用NSFileCoordination实现普通文件的读写同步,而数据库可以使用CoreData,Sqlite也支持同步。

五、extension和containing app代码共享

和数据共享类似,extension和containing app很自然地会有一些业务逻辑上可以共用的代码,这时可以通过iOS8中刚开放使用的framework实现。苹果在App Extension Programming Guide中是这样描述的:

In iOS 8.0 and later, you can use an embedded framework to share code between your extension and its containing app. For example, if you develop image-processing code that you want both your Photo Editing extension and its containing app to share, you can put the code into a framework and embed it in both targets.

即将framework分别嵌入到extension和containing app的target中实现代码共享。但这样岂不是需要分别要将framework分别copy到extension和containing app的main bundle中?

参考extension和containing app数据共享,我试想能不能将framework只保存一份放在App Groups区域?

5.1 copy framework到App Groups

在app首次启动的时候将framework放到App Groups区域:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (BOOL)copyFrameworkFromMainBundleToAppGroup
{
    NSFileManager *manager = [NSFileManager defaultManager];
    NSError *err = nil;
    NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"];
    NSString *sorPath = [NSString stringWithFormat:@"%@/Dylib.framework",[[NSBundle mainBundle] bundlePath]];
    NSString *desPath = [NSString stringWithFormat:@"%@/Library/Caches/Dylib.framework",containerURL.path];
    BOOL removeResult = [manager removeItemAtPath:desPath error:&err];
    if (!removeResult) {
        NSLog(@"%@",err);
    } else {
        NSLog(@"remove success.");
    }
    BOOL copyResult = [[NSFileManager defaultManager] copyItemAtPath:sorPath toPath:desPath error:&err];
    if (!copyResult) {
        NSLog(@"%@",err);
    } else {
        NSLog(@"copy success.");
    }
    return copyResult;
}

5.2 使用framework:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (BOOL)loadFrameworkInAppGroup
{
    NSError *err = nil;
    NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"];
    NSString *desPath = [NSString stringWithFormat:@"%@/Library/Caches/Dylib.framework",containerURL.path];
    NSBundle *bundle = [NSBundle bundleWithPath:desPath];
    BOOL result = [bundle loadAndReturnError:&err];
    if (result) {
        Class root = NSClassFromString(@"Person");
        if (root) {
            Person *person = [[root alloc] init];
            if (person) {
                [person run];
            }
        }
    } else {
        NSLog(@"%@",err);
    }
    return result;
}

经过测试,竟然能够加载成功。

需要说明的是,这里只是说那么用是可以成功加载framework,但还面临不少问题,比如如果用户在启动app之前去使用extension,这时framework还没有copy过去,怎么处理;另外iOS的机制或者苹果的审核是否允许这样使用等。

在一切确定下来之前还是乖乖按文档中的方式使用吧。

六、生命周期

extension和普通app的最大区别之一是生命周期。

  • 开始

在用户通过host app点击extension时,系统就会实例化extension应用,这是生命周期的开始。

  • 执行任务

在extension启动以后,开始执行它的使命。

  • 终止

在用户取消任务,或者任务执行结束,或者开启了一个长时后台任务时,系统会将其杀掉。

由此可见,extension就是为了任务而生!

下图来自官方文档,它将生命周期划分的更详细:

app_extensions_lifecycle

通过打印日志发现,Today中的widget在将Today切换到全部或者未读通知时都会被杀掉。

七、 调试

extension和普通app的调试方式差不多,开始调试前先选中extension对应的target,点击run,就会弹出下图所示选择框:

extension_debug

需要选择一个host app,这里选择Today

然后即可和普通app一样调试了,不过我在实际使用过程中,发现有各种奇怪的事情,比如NSLog无法在控制台输出,应该是bug吧。

八、 iOS8应用文件系统

发现iOS8的文件系统发生了变化,新的文件系统将可执行文件(即原来的.app文件)从沙盒中移到了另外一个地方,这样感觉更合理。

  • 测试代码

下述代码用于打印App Groups路径、应用的可执行文件路径、对应的Documents路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)logAppPath
{
    //app group路径
    NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"];
    NSLog(@"app group:\n%@",containerURL.path);
    //打印可执行文件路径
    NSLog(@"bundle:\n%@",[[NSBundle mainBundle] bundlePath]);
    //打印documents
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *path = [paths objectAtIndex:0];
    NSLog(@"documents:\n%@",path);
}
  • containing app执行结果
1
2
3
4
5
6
2014-06-23 19:35:03.944 AppExtensionDemo[7471:365131] app group:
/private/var/mobile/Containers/Shared/AppGroup/89CCBFB1-CA5E-4C7F-80CB-A3EB9E841816
2014-06-23 19:35:03.946 AppExtensionDemo[7471:365131] bundle:
/private/var/mobile/Containers/Bundle/Application/1AC73797-A3BB-4BDE-A647-3D083DA6871A/AppExtensionDemo.app
2014-06-23 19:35:03.948 AppExtensionDemo[7471:365131] documents:
/var/mobile/Containers/Data/Application/E5E6E516-0163-4754-9D10-A5F6C33A6261/Documents
  • extension执行结果
1
2
3
4
5
6
Jun 23 19:37:49 autonavis-iPad com.foogry.AppExtensionDemo.TodayExtension[7638] <Warning>: app group:
  /private/var/mobile/Containers/Shared/AppGroup/89CCBFB1-CA5E-4C7F-80CB-A3EB9E841816
Jun 23 19:37:49 autonavis-iPad com.foogry.AppExtensionDemo.TodayExtension[7638] <Warning>: bundle:
  /private/var/mobile/Containers/Bundle/Application/596717B7-7CB8-4F53-BCD4-380F34ABD30F/AppExtensionDemo.app/PlugIns/com.foogry.AppExtensionDemo.TodayExtension.appex
Jun 23 19:37:49 autonavis-iPad com.foogry.AppExtensionDemo.TodayExtension[7638] <Warning>: documents:
  /var/mobile/Containers/Data/PluginKitPlugin/57581433-3DBD-4930-971F-78D30C150E8A/Documents

由此可见,不管是extension还是containing app,他们的可执行文件和保存数据的目录都是分开存放的,即所有app的可执行文件都放在一个大目录下,保存数据的目录保存在另一个大目录下,同样,AppGroup放在另一个大目录下。

说明

  • 本文用到的demo已经上传到github上。

  • 文中可能有理解有误的地方,还请指出。

参考文档

Swift中文教程(23)高级运算符

转载自letsswift.com

除了基本操作符中所讲的运算符,Swift还有许多复杂的高级运算符,包括了C语和Objective-C中的位运算符和移位运算。

不同于C语言中的数值计算,Swift的数值计算默认是不可溢出的。溢出行为会被捕获并报告为错误。你是故意的?好吧,你可以使用Swift为你准备的另一套默认允许溢出的数值运算符,如可溢出加&+。所有允许溢出的运算符都是以&开始的。

自定义的结构,类和枚举,是否可以使用标准的运算符来定义操作?当然可以!在Swift中,你可以为你创建的所有类型定制运算符的操作。

可定制的运算符并不限于那些预设的运算符,自定义有个性的中置,前置,后置及赋值运算符,当然还有优先级和结合性。这些运算符的实现可以运用预设的运算符,也可以运用之前定制的运算符。

位运算符

位操作符通常在诸如图像处理和创建设备驱动等底层开发中使用,使用它可以单独操作数据结构中原始数据的比特位。在使用一个自定义的协议进行通信的时候,运用位运算符来对原始数据进行编码和解码也是非常有效的。

Swift支持如下所有C语言的位运算符:

按位取反运算符

按位取反运算符~对一个操作数的每一位都取反。

Art/bitwiseNOT_2x.png

这个运算符是前置的,所以请不加任何空格地写着操作数之前。

let initialBits:UInt8=0b00001111
let invertedBits =~initialBits  // 等于 0b11110000

UInt8是8位无符整型,可以存储0~255之间的任意数。这个例子初始化一个整型为二进制值00001111(前4位为0,后4位为1),它的十进制值为15

使用按位取反运算~initialBits操作,然后赋值给invertedBits这个新常量。这个新常量的值等于所有位都取反的initialBits,即1变成00变成1,变成了11110000,十进制值为240

按位与运算符

按位与运算符对两个数进行操作,然后返回一个新的数,这个数的每个位都需要两个输入数的同一位都为1时才为1。

Art/bitwiseAND_2x.png

以下代码,firstSixBitslastSixBits中间4个位都为1。对它俩进行按位与运算后,就得到了00111100,即十进制的60

let firstSixBits:UInt8=0b11111100
let lastSixBits:UInt8=0b00111111
let middleFourBits = firstSixBits & lastSixBits  // 等于 00111100
按位或运算

按位或运算符|比较两个数,然后返回一个新的数,这个数的每一位设置1的条件是两个输入数的同一位都不为0(即任意一个为1,或都为1)。

Art/bitwiseOR_2x.png

如下代码,someBitsmoreBits在不同位上有1。按位或运行的结果是11111110,即十进制的254

let someBits:UInt8=0b10110010
let moreBits:UInt8=0b01011110
let combinedbits = someBits | moreBits  // 等于 11111110
按位异或运算符

按位异或运算符^比较两个数,然后返回一个数,这个数的每个位设为1的条件是两个输入数的同一位不同,如果相同就设为0

Art/bitwiseXOR_2x.png

以下代码,firstBitsotherBits都有一个1跟另一个数不同的。所以按位异或的结果是把它这些位置为1,其他都置为0

let firstBits:UInt8=0b00010100
let otherBits:UInt8=0b00000101
let outputBits = firstBits ^ otherBits  // 等于 00010001
按位左移/右移运算符

左移运算符<<和右移运算符>>会把一个数的所有比特位按以下定义的规则向左或向右移动指定位数。

按位左移和按位右移的效果相当把一个整数乘于或除于一个因子为2的整数。向左移动一个整型的比特位相当于把这个数乘于2,向右移一位就是除于2

无符整型的移位操作

对无符整型的移位的效果如下:

已经存在的比特位向左或向右移动指定的位数。被移出整型存储边界的的位数直接抛弃,移动留下的空白位用零0来填充。这种方法称为逻辑移位。

以下这张把展示了 11111111 << 1(11111111向左移1位),和 11111111 >> 1(11111111向右移1位)。蓝色的是被移位的,灰色是被抛弃的,橙色的0是被填充进来的。

Art/bitshiftUnsigned_2x.png

let shiftBits:UInt8=4// 即二进制的00000100
shiftBits <<1// 00001000
shiftBits <<2// 00010000
shiftBits <<5// 10000000
shiftBits <<6// 00000000
shiftBits >>2// 00000001

你可以使用移位操作进行其他数据类型的编码和解码。

let pink:UInt32=0xCC6699
let redComponent =(pink &0xFF0000)>>16// redComponent 是 0xCC, 即 204
let greenComponent =(pink &0x00FF00)>>8// greenComponent 是 0x66, 即 102
let blueComponent = pink &0x0000FF// blueComponent 是 0x99, 即 153

这个例子使用了一个UInt32的命名为pink的常量来存储层叠样式表CSS中粉色的颜色值,CSS颜色#CC6699在Swift用十六进制0xCC6699来表示。然后使用按位与(&)和按位右移就可以从这个颜色值中解析出红(CC),绿(66),蓝(99)三个部分。

0xCC66990xFF0000进行按位与&操作就可以得到红色部分。0xFF0000中的0了遮盖了OxCC6699的第二和第三个字节,这样6699被忽略了,只留下0xCC0000

然后,按向右移动16位,即 >> 16。十六进制中每两个字符是8比特位,所以移动16位的结果是把0xCC0000变成0x0000CC。这和0xCC是相等的,都是十进制的204

同样的,绿色部分来自于0xCC66990x00FF00的按位操作得到0x006600。然后向右移动8們,得到0x66,即十进制的102

最后,蓝色部分对0xCC66990x0000FF进行按位与运算,得到0x000099,无需向右移位了,所以结果就是0x99,即十进制的153

有符整型的移位操作

有符整型的移位操作相对复杂得多,因为正负号也是用二进制位表示的。(这里举的例子虽然都是8位的,但它的原理是通用的。)

有符整型通过第1个比特位(称为符号位)来表达这个整数是正数还是负数。0代表正数,1代表负数。

其余的比特位(称为数值位)存储其实值。有符正整数和无符正整数在计算机里的存储结果是一样的,下来我们来看+4内部的二进制结构。

Art/bitshiftSignedFour_2x.png

符号位为0,代表正数,另外7比特位二进制表示的实际值就刚好是4

负数呢,跟正数不同。负数存储的是2的n次方减去它的绝对值,n为数值位的位数。一个8比特的数有7个数值位,所以是2的7次方,即128。

我们来看-4存储的二进制结构。

Art/bitshiftSignedMinusFour_2x.png

现在符号位为1,代表负数,7个数值位要表达的二进制值是124,即128 – 4。

Art/bitshiftSignedMinusFourValue_2x.png

负数的编码方式称为二进制补码表示。这种表示方式看起来很奇怪,但它有几个优点。

首先,只需要对全部8个比特位(包括符号)做标准的二进制加法就可以完成 -1 + -4 的操作,忽略加法过程产生的超过8个比特位表达的任何信息。

Art/bitshiftSignedAddition_2x.png

第二,由于使用二进制补码表示,我们可以和正数一样对负数进行按位左移右移的,同样也是左移1位时乘于2,右移1位时除于2。要达到此目的,对有符整型的右移有一个特别的要求:

对有符整型按位右移时,使用符号位(正数为0,负数为1)填充空白位。

Art/bitshiftSigned_2x.png

这就确保了在右移的过程中,有符整型的符号不会发生变化。这称为算术移位。

正因为正数和负数特殊的存储方式,向右移位使它接近于0。移位过程中保持符号会不变,负数在接近0的过程中一直是负数。

溢出运算符

默认情况下,当你往一个整型常量或变量赋于一个它不能承载的大数时,Swift不会让你这么干的,它会报错。这样,在操作过大或过小的数的时候就很安全了。

例如,Int16整型能承载的整数范围是-3276832767,如果给它赋上超过这个范围的数,就会报错:

var potentialOverflow =Int16.max
// potentialOverflow 等于 32767, 这是 Int16 能承载的最大整数
potentialOverflow +=1// 噢, 出错了

对过大或过小的数值进行错误处理让你的数值边界条件更灵活。

当然,你有意在溢出时对有效位进行截断,你可采用溢出运算,而非错误处理。Swfit为整型计算提供了5个&符号开头的溢出运算符。

  • 溢出加法 &+
  • 溢出减法 &-
  • 溢出乘法 &*
  • 溢出除法 &/
  • 溢出求余 &%
值的上溢出

下面例子使用了溢出加法&+来解剖的无符整数的上溢出

var willOverflow =UInt8.max
// willOverflow 等于UInt8的最大整数 255
willOverflow = willOverflow &+1// 这时候 willOverflow 等于 0

willOverflowInt8所能承载的最大值255(二进制11111111),然后用&+加1。然后UInt8就无法表达这个新值的二进制了,也就导致了这个新值上溢出了,大家可以看下图。溢出后,新值在UInt8的承载范围内的那部分是00000000,也就是0

Art/overflowAddition_2x.png

值的下溢出

数值也有可能因为太小而越界。举个例子:

UInt8的最小值是0(二进制为00000000)。使用&-进行溢出减1,就会得到二进制的11111111即十进制的255

Art/overflowUnsignedSubtraction_2x.png

Swift代码是这样的:

var willUnderflow =UInt8.min
// willUnderflow 等于UInt8的最小值0
willUnderflow = willUnderflow &-1// 此时 willUnderflow 等于 255

有符整型也有类似的下溢出,有符整型所有的减法也都是对包括在符号位在内的二进制数进行二进制减法的,这在 “按位左移/右移运算符” 一节提到过。最小的有符整数是-128,即二进制的10000000。用溢出减法减去去1后,变成了01111111,即UInt8所能承载的最大整数127

Art/overflowSignedSubtraction_2x.png

来看看Swift代码:

var signedUnderflow =Int8.min
// signedUnderflow 等于最小的有符整数 -128
signedUnderflow = signedUnderflow &-1// 如今 signedUnderflow 等于 127
除零溢出

一个数除于0 i / 0,或者对0求余数 i % 0,就会产生一个错误。

let x =1
let y = x /0

使用它们对应的可溢出的版本的运算符&/&%进行除0操作时就会得到0值。

let x =1
let y = x &/ 0
// y 等于0

优先级和结合性

运算符的优先级使得一些运算符优先于其他运算符,高优先级的运算符会先被计算。

结合性定义相同优先级的运算符在一起时是怎么组合或关联的,是和左边的一组呢,还是和右边的一组。意思就是,到底是和左边的表达式结合呢,还是和右边的表达式结合?

在混合表达式中,运算符的优先级和结合性是非常重要的。举个例子,为什么下列表达式的结果为4

2+3*4%5// 结果是 4

如果严格地从左计算到右,计算过程会是这样:

  • 2 plus 3 equals 5;
  • 2 + 3 = 5
  • 5 times 4 equals 20;
  • 5 * 4 = 20
  • 20 remainder 5 equals 0
  • 20 / 5 = 4 余 0

但是正确答案是4而不是0。优先级高的运算符要先计算,在Swift和C语言中,都是先乘除后加减的。所以,执行完乘法和求余运算才能执行加减运算。

乘法和求余拥有相同的优先级,在运算过程中,我们还需要结合性,乘法和求余运算都是左结合的。这相当于在表达式中有隐藏的括号让运算从左开始。

2+((3*4)%5)

(3 4) is 12, so this is equivalent to: 3 4 = 12,所以这相当于:

2+(12%5)

(12 % 5) is 2, so this is equivalent to: 12 % 5 = 2,所这又相当于

2+2

计算结果为 4。

查阅Swift运算符的优先级和结合性的完整列表,请看表达式。

注意:

Swift的运算符较C语言和Objective-C来得更简单和保守,这意味着跟基于C的语言可能不一样。所以,在移植已有代码到Swift时,注意去确保代码按你想的那样去执行。

运算符函数

让已有的运算符也可以对自定义的类和结构进行运算,这称为运算符重载。

这个例子展示了如何用+让一个自定义的结构做加法。算术运算符+是一个两目运算符,因为它有两个操作数,而且它必须出现在两个操作数之间。

例子中定义了一个名为Vector2D的二维坐标向量 (x,y) 的结构,然后定义了让两个Vector2D的对象相加的运算符函数。

structVector2D{var x =0.0, y =0.0}@infix func +(left:Vector2D, right:Vector2D)->Vector2D{returnVector2D(x: left.x + right.x, y: left.y + right.y)}

该运算符函数定义了一个全局的+函数,这个函数需要两个Vector2D类型的参数,返回值也是Vector2D类型。需要定义和实现一个中置运算的时候,在关键字func之前写上属性 @infix 就可以了。

在这个代码实现中,参数被命名为了leftright,代表+左边和右边的两个Vector2D对象。函数返回了一个新的Vector2D的对象,这个对象的xy分别等于两个参数对象的xy的和。

这个函数是全局的,而不是Vector2D结构的成员方法,所以任意两个Vector2D对象都可以使用这个中置运算符。

let vector =Vector2D(x:3.0, y:1.0)
let anotherVector =Vector2D(x:2.0, y:4.0)
let combinedVector = vector + anotherVector
// combinedVector 是一个新的Vector2D, 值为 (5.0, 5.0)

这个例子实现两个向量 (3.0,1.0) 和 (2.0,4.0) 相加,得到向量 (5.0,5.0) 的过程。如下图示:

Art/vectorAddition_2x.png

前置和后置运算符

上个例子演示了一个双目中置运算符的自定义实现,同样我们也可以玩标准单目运算符的实现。单目运算符只有一个操作数,在操作数之前就是前置的,如-a; 在操作数之后就是后置的,如i++

实现一个前置或后置运算符时,在定义该运算符的时候于关键字func之前标注 @prefix 或 @postfix属性。

@prefix func -(vector:Vector2D)->Vector2D{returnVector2D(x:-vector.x, y:-vector.y)}

这段代码为Vector2D类型提供了单目减运算-a@prefix属性表明这是个前置运算符。

对于数值,单目减运算符可以把正数变负数,把负数变正数。对于Vector2D,单目减运算将其xy都进进行单目减运算。

let positive =Vector2D(x:3.0, y:4.0)
let negative =-positive
// negative 为 (-3.0, -4.0)
let alsoPositive =-negative
// alsoPositive 为 (3.0, 4.0)
组合赋值运算符

组合赋值是其他运算符和赋值运算符一起执行的运算。如+=把加运算和赋值运算组合成一个操作。实现一个组合赋值符号需要使用@assignment属性,还需要把运算符的左参数设置成inout,因为这个参数会在运算符函数内直接修改它的值。

@assignment func +=(inout left:Vector2D, right:Vector2D){
    left = left + right
}

因为加法运算在之前定义过了,这里无需重新定义。所以,加赋运算符函数使用已经存在的高级加法运算符函数来执行左值加右值的运算。

var original =Vector2D(x:1.0, y:2.0)
let vectorToAdd =Vector2D(x:3.0, y:4.0)
original += vectorToAdd
// original 现在为 (4.0, 6.0)

你可以将 @assignment 属性和 @prefix 或 @postfix 属性起来组合,实现一个Vector2D的前置运算符。

@prefix@assignment func ++(inout vector:Vector2D)->Vector2D{
    vector +=Vector2D(x:1.0, y:1.0)return vector
}

这个前置使用了已经定义好的高级加赋运算,将自己加上一个值为 (1.0,1.0) 的对象然后赋给自己,然后再将自己返回。

var toIncrement =Vector2D(x:3.0, y:4.0)
let afterIncrement =++toIncrement
// toIncrement 现在是 (4.0, 5.0)// afterIncrement 现在也是 (4.0, 5.0)

注意:

默认的赋值符是不可重载的。只有组合赋值符可以重载。三目条件运算符 a?b:c 也是不可重载。

比较运算符

Swift无所知道自定义类型是否相等或不等,因为等于或者不等于由你的代码说了算了。所以自定义的类和结构要使用比较符==!=就需要重载。

定义相等运算符函数跟定义其他中置运算符雷同:

@infix func ==(left:Vector2D, right:Vector2D)->Bool{return(left.x == right.x)&&(left.y == right.y)}@infix func !=(left:Vector2D, right:Vector2D)->Bool{return!(left == right)}

上述代码实现了相等运算符==来判断两个Vector2D对象是否有相等的值,相等的概念就是他们有相同的x值和相同的y值,我们就用这个逻辑来实现。接着使用==的结果实现了不相等运算符!=

现在我们可以使用这两个运算符来判断两个Vector2D对象是否相等。

let twoThree =Vector2D(x:2.0, y:3.0)
let anotherTwoThree =Vector2D(x:2.0, y:3.0)if twoThree == anotherTwoThree {
    println("这两个向量是相等的.")}// prints "这两个向量是相等的."
自定义运算符

标准的运算符不够玩,那你可以声明一些个性的运算符,但个性的运算符只能使用这些字符/ = - + * % < >!& | ^。~

新的运算符声明需在全局域使用operator关键字声明,可以声明为前置,中置或后置的。

operator prefix +++{}

这段代码定义了一个新的前置运算符叫+++,此前Swift并不存在这个运算符。此处为了演示,我们让+++Vector2D对象的操作定义为 双自增 这样一个独有的操作,这个操作使用了之前定义的加赋运算实现了自已加上自己然后返回的运算。

@prefix@assignment func +++(inout vector:Vector2D)->Vector2D{
    vector += vector
    return vector
}

Vector2D 的 +++ 的实现和 ++ 的实现很接近, 唯一不同的前者是加自己, 后者是加值为 (1.0, 1.0) 的向量.

var toBeDoubled =Vector2D(x:1.0, y:4.0)
let afterDoubling =+++toBeDoubled
// toBeDoubled 现在是 (2.0, 8.0)// afterDoubling 现在也是 (2.0, 8.0)
自定义中置运算符的优先级和结合性

可以为自定义的中置运算符指定优先级和结合性。可以回头看看优先级和结合性解释这两个因素是如何影响多种中置运算符混合的表达式的计算的。

结合性(associativity)的值可取的值有leftrightnone。左结合运算符跟其他优先级相同的左结合运算符写在一起时,会跟左边的操作数结合。同理,右结合运算符会跟右边的操作数结合。而非结合运算符不能跟其他相同优先级的运算符写在一起。

结合性(associativity)的值默认为none,优先级(precedence)默认为100

以下例子定义了一个新的中置符+-,是左结合的left,优先级为140

operator infix +-{ associativity left precedence 140}
func +-(left:Vector2D, right:Vector2D)->Vector2D{returnVector2D(x: left.x + right.x, y: left.y - right.y)}
let firstVector =Vector2D(x:1.0, y:2.0)
let secondVector =Vector2D(x:3.0, y:4.0)
let plusMinusVector = firstVector +- secondVector
// plusMinusVector 此时的值为 (4.0, -2.0)

这个运算符把两个向量的x相加,把向量的y相减。因为他实际是属于加减运算,所以让它保持了和加法一样的结合性和优先级(left140)。查阅完整的Swift默认结合性和优先级的设置,请移步表达式;

本文部分原文来自于http://www.swiftguide.cn/翻译小组的译文,共同校对中。

感谢翻译小组成员:李起攀(微博)、若晨(微博)、YAO、粽子、山有木兮木有枝、渺-Bessie、墨离、矮人王、CXH、Tiger大顾(微博)
个人转载请注明出处和原始链接http://letsswift.com/2014/06/advanced-operators/ ‎,商业转载请联系我们~ 感谢您对我们工作的支持~