Core Data Migration 之拆分Entity

参考文章:http://blog.slalom.com/2010/01/08/splitting-an-entity-in-a-coredata-migration/

最近自己做的背单词软件,在之前的设计上有一个非常大的缺陷就是把单词读音的语音文件放在数据库里面了,而且作为word表里面的一个字段储存。一开始测试的时候没有什么大问题,但是单词越来越多之后查询就变得非常之慢。后面自己加上的一些功能都要频繁地对比数据库,所以做了一个优化就是在core data fetch request里面指定要获取的字段,在这里排除读音字段的话,查询就非常快了,尤其当我把单词本身的string做了index之后。但是代码就很难看了。

core data里面的讲到类似情况的优化时说,需要把大数据文件新建一个Entity独立出来,然后和以前的Entity建立一对一关系。然而"Core Data Model Versioning and Data Migration Programming"这篇文档完全忽略了怎么样拆分Entity这块内容,今天搜到了一篇文章才知道怎么做。

以下就是一个示范,基于Xcode生成的core data模板程序,并且我假定你已经知道最基本的Model versioning和mapping object的知识。

原始的Model是这样的:

假设我们现在的目标是把address分离到一个独立的entity中去。

现在新建立一个Model Version,假设命名为version2,把它搞成这样:

这样,就把address分离出去了。

接下来要做Migration了,新建一个Mapping Model文件:

根据向导,将source设置为原始的Model文件,target设置为新的Model文件。然后Xcode会生成如下的Mapping文件:

在这个文件基础上,我们只要稍作修改就能完成了。

首先把ENTITY MAPPING下面的Address重命名为EventToAddress(这步可做可不做),然后在右边栏中的Entity Mapping中,设置Source为Event:

接下来改address的Value Expression为$source.address 如下:

然后在左边的ENTITY MAPPINGS栏中切换到EventToEvent,选中Relation Mappings中的address,在右边栏中设置Key Path为$source,设置Mapping Name为EventToAddress(如果刚才没改名,就用刚才的名字)。

然后设置model的current version为version2:

最后一步,在程序中初始化NSPersistentStoreCoordinator的地方,加上NSMigratePersistentStoresAutomaticallyOption的选项,代码类似如下:

 

_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    } 

最后,运行程序,在程序初始化的时候,就会自动做migration了。如果要migration的数据量非常大,就会非常的慢。

完整源代码见:https://gitcafe.com/hikui/iOS-Example/tree/master/CoreDataMigrationDemo

iOS6 NavigationController的orientation问题

iOS 6的orientation还是比较奇葩的。我一开始以为只要把之前的- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation 改成 - (BOOL)shouldAutorotate和- (NSUInteger)supportedInterfaceOrientations的组合就行了,但是套了一层UINavigationController之后情况又变了:无论child view controller支持哪些orientation,UINavigationController总是会支持UIInterfaceOrientationMaskAllButUpsideDown。所以要让UINavigationController中的某些viewController通过自身的supportedInterfaceOrientations设置来调整orientation变得不可能。

最后在stackoverflow上面看到一个解决办法,看起来也是比较山寨的,但至少解决了问题:

@implementation UINavigationController (Rotation_IOS6)

-(BOOL)shouldAutorotate
{
    return [[self.viewControllers lastObject] shouldAutorotate];
}

-(NSUInteger)supportedInterfaceOrientations
{
    return [[self.viewControllers lastObject] supportedInterfaceOrientations];
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
    return [[self.viewControllers lastObject] preferredInterfaceOrientationForPresentation];
}

@end

一个页面切换效果的拙劣模仿

模仿的是iBooks打开书本时的效果,即缩略图开始放大,然后变成了一本书的内容。包括weico里面打开一个微博的图片也有类似的效果。

但是我的模仿参照了github上面的某一串代码(我现在又搜不到了),进行了一些改进。但是和iBooks以及weico比起来,又少了一些东西,所以比较拙劣。

我的效果是这样的。

原理:

使用两个ViewController。其中DetailViewController当作modalViewController来弹出。并设置detailVC.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;

这时,两个ViewController之间的切换就有渐变过渡了。

然后,detailViewController中设置一个property:originalRect,来保存tableViewController中,点到的cell.imageView.frame相对于顶级View的rect。

在detailViewController的viewWillAppear中,让detailViewController.imageView.frame从originalRect变换到全屏。

这样看上去就好像这个图片从tableViewCell里面飞出来变全屏一样。

但是仔细看和iBooks还是不一样。iBooks应该也是用了modalViewController,并且应该也用了UIModalTransitionStyleCrossDissolve。但是在Dissolve之前,tableViewCell中的image确实做了一点CoreAnimation的东西。但是如何计算我不太清楚。希望各位大神能够告诉我。

关键代码:

ViewController.m

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    UIImageView *imgView = cell.imageView;
    CGRect frame = imgView.frame;
    NSLog(@"original frame:%@",[NSValue valueWithCGRect:frame]);
    CGRect rectInParentView = [imgView convertRect:frame toView:self.view];
    NSLog(@"rect in parent view:%@",[NSValue valueWithCGRect:rectInParentView]);
    
    DetailViewController *detailVC = [[DetailViewController alloc]init];
    detailVC.originalRect = rectInParentView;
    detailVC.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
    [self presentModalViewController:detailVC animated:YES];
}

DetailViewController.m

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor scrollViewTexturedBackgroundColor];
    UIImageView *imgView = [[UIImageView alloc]initWithFrame:self.originalRect];
    imgView.contentMode = UIViewContentModeScaleAspectFit;
    imgView.image = [UIImage imageNamed:@"flag.jpg"];
    imgView.userInteractionEnabled = YES;
    UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapImage:)];
    [imgView addGestureRecognizer:tapGesture];
    [self.view addSubview:imgView];
    self.imageView = imgView;
}

- (void)viewWillAppear:(BOOL)animated
{
    [UIView animateWithDuration:0.4 delay:0.1 options:0 animations:^{
        self.imageView.frame = self.view.bounds;
    } completion:nil];
}

- (void)tapImage:(id)sender
{
    NSLog(@"tap image");
    [UIView animateWithDuration:0.4 animations:^{
        self.imageView.frame = self.originalRect;
    }];
    [self performSelector:@selector(dismissModalViewControllerAnimated:) withObject:[NSNumber numberWithBool:YES] afterDelay:0.2];
}

完整代码点此

iOS Orientation一些事(不包括iOS6)

完整代码可在 gitcafe项目主页 中下载。

之前的一篇文章提到对iOS系统键盘的hack,其中有提到新版的东方财富通的数字键盘是自定义键盘。在新的需求里,个股搜索需要支持横屏,本人又比较懒,不想写代码一个键一个键的调frame,就想到了通过判断Orientation从xib载入不同的view直接贴上去。但是碰到了一个不小的问题。

因为代码是属于公司的,我不好直接拿出来贴。在这里我把问题简化地提出来:

如图有两个ViewController,设左边为A,右边为B。点击A时,把B push进navigationController。要求进入B时,如果画面为横屏,则在label显示landscape,如果是竖屏,则显示portrait。在B中,要随着设备的orientation变化,切换label显示landscape或者portrait。

 

继续阅读

对iOS系统键盘的hack以及注意事项

本文写于iOS5时代,后面几个版本的iOS版本对自带控件的view结构做了很大的变化,所以不适用了。

建议少用这类的hack方法,以保证iOS更新迭代的时候不出问题。

在做东方财富通新版的时候,需求组给出的新的需求是,数字键盘使用我们自己做的,而字母键盘使用系统自带的键盘,然而这里出现的一个问题就是,系统键盘进行输入法切换的时候是没有办法切换到自带的键盘中去的。这时候就需要对系统自带键盘进行hack。

原理很简单,就是在系统键盘弹出来时找到系统键盘所在的UIView,然后贴上自己的按钮覆盖掉原先的按钮。难点在于如何找到系统键盘所在的View。

效果图:

从效果图上可见,原先系统键盘的123和地球的图标被替换成了自定义的按钮。

做法:首先在当前的ViewController中监听:UIKeyboardDidShowNotification

 

[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];

 

然后自己实现keyboardDidShow方法,在里面根据需要调用下面hackSystemKeyboard方法:

- (void)hackSystemKeyboard
{
    UIView *foundKeyboard = [self getSystemKeyboard];
    if (foundKeyboard) {
        //如果有原先存在的hack button,删之
        [self clearKeyboardHack];
        //hackButton 为123button
        //新建hackButton,根据orentation定义不同的frame
        UIButton *hackButton = [UIButton buttonWithType:UIButtonTypeCustom];
        hackButton.backgroundColor = [UIColor clearColor];
        hackButton.tag = 19100;
        if (self.isLandscape) {
            hackButton.frame = CGRectMake(3, 124, 90, 38);
            UIImage *btnBG = [UIImage imageNamed:@"hackButtonLandscape.png"];
            [hackButton setBackgroundImage:btnBG forState:UIControlStateNormal];
        }else {
            hackButton.frame = CGRectMake(2, 173, 77, 43);
            UIImage *btnBG = [UIImage imageNamed:@"hackButton.png"];
            [hackButton setBackgroundImage:btnBG forState:UIControlStateNormal];
        }
        [hackButton setShowsTouchWhenHighlighted:YES];
        [hackButton addTarget:self action:@selector(switchToNumberPad:) forControlEvents:UIControlEventTouchUpInside];
        [foundKeyboard addSubview:hackButton];
    }
}

这里要注意的是,如果这个ViewController支持横屏,则需要准备两套button图片。并且在willAnimateRotationToInterfaceOrientation中需要调用hackSystemButton这个方法。

里面比较重要的是getSystemKeyboard方法:

 

- (UIView *)getSystemKeyboard
{
    UIView *foundKeyboard = nil;
    
    UIWindow *keyboardWindow = nil;
    for (UIWindow *testWindow in [[UIApplication sharedApplication] windows])
    {
        if (![[testWindow class] isEqual:[UIWindow class]])
        {
            keyboardWindow = testWindow;
            break;
        }
    }
    if (!keyboardWindow)
        return nil;
    
    for (UIView *possibleKeyboard in [keyboardWindow subviews])
    {
        //iOS3
        if ([[possibleKeyboard description] hasPrefix:@"<UIKeyboard"])
        {
            foundKeyboard = possibleKeyboard;
            break;
        }
        else
        {
            // iOS 4 sticks the UIKeyboard inside a UIPeripheralHostView.
            if ([[possibleKeyboard description] hasPrefix:@"<UIPeripheralHostView"])
            {
                possibleKeyboard = [[possibleKeyboard subviews] objectAtIndex:0];
            }
            
            if ([[possibleKeyboard description] hasPrefix:@"<UIKeyboard"])
            {
                foundKeyboard = possibleKeyboard;
                break;
            }
        }
    }
    return foundKeyboard;
}

这里利用了iOS系统组件的Description进行View的判断,iOS 4以后的版本和之前的版本不一样,新版本中把UIKeyboard放在UIPeripheralHostView中。

最后需要注意的是,一个系统键盘在一个应用程序里面是共享的,所以如果这个ViewController中对键盘进行了hack,而在另外一个ViewController中不需要hack,那么需要在本ViewController退出的时候把Hack清理掉。但是在ViewWillDisappear中,我发现getSystemKeyboard方法寻找到的View并不是真正的keyboard,我的一个变通的办法就是在所有会导致ViewWillDisappear的步骤中插入clearKeyboardHack方法。

101年10月17日更新:

清理hack的时机应该是键盘将要隐藏前。只要在NotificationCenter中注册名为UIKeyboardWillHideNotification,收到notification的时候进行一次清理即可。

hidesBottombarWhenPushed的副作用

本文写于iOS5时代,后面几个版本的iOS版本对自带控件的view结构做了很大的变化,所以不适用了。

建议少用这类的hack方法,以保证iOS更新迭代的时候不出问题。

在UITabbarController包含的UINavigationController应用中,如果UINavigationController某一页(某个level)需要隐藏Tabbar,之前的做法是在push那一页之前,将那一页的ViewController中的hidesBottombarWhenPushed参数设为YES,这样当那一页push进UINavigationController中时,底部的Tabbar就会隐藏掉。

但是这种方法有一个潜在的而又巨大的问题,假设现在我有3个ViewController A,B,C,其中为Navigation的RootViewController,A中push B,B中push C。这时,如果我想要在B中隐藏Tabbar,而在C中显示Tabbar,根据最朴素的想法就是:

 

//SecondLevel即为B,此代码在A中进行
SecondLevelViewController *_2vc = [[SecondLevelViewController alloc]initWithNibName:@"SecondLevelViewController" bundle:nil];
    _2vc.hidesBottomBarWhenPushed = YES;
    [self.navigationController pushViewController:_2vc animated:YES];  

//ThirdLevel即为C,此代码在B中进行
ThirdLevelViewController *_3vc = [[ThirdLevelViewController alloc]initWithNibName:@"ThirdLevelViewController" bundle:nil];
    _3vc.hidesBottomBarWhenPushed = NO;
    [self.navigationController pushViewController:_3vc animated:YES];

然而很可惜,虽然A中push B能让Tabbar隐藏,但是B中push C,就没办法让Tabbar显示出来了。这是一个比较郁闷的问题,我只能猜想其中的原理是,当设置某一层hidesBottomBarWhenPushed = YES之后,UITabbarController就像不存在一样,就连调用self.tabbarController或者self.navigationController.tabbarController也应该是nil。

所以解决办法只能是手动隐藏Tabbar。

在B中,加入以下代码:

 

- (void) hideTabBar:(UITabBarController *) tabbarcontroller {
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.5];
    for(UIView *view in tabbarcontroller.view.subviews)
    {
        if([view isKindOfClass:[UITabBar class]])
        {
            [view setFrame:CGRectMake(view.frame.origin.x, 480, view.frame.size.width, view.frame.size.height)];
        } 
        else 
        {
            [view setFrame:CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, 480)];
        }
    }
    [UIView commitAnimations];
}

- (void) showTabBar:(UITabBarController *) tabbarcontroller {

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.5];
    for(UIView *view in tabbarcontroller.view.subviews)
    {
        NSLog(@"%@", view);

        if([view isKindOfClass:[UITabBar class]])
        {
            [view setFrame:CGRectMake(view.frame.origin.x, 431, view.frame.size.width, view.frame.size.height)];
        } 
        else 
        {
            [view setFrame:CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, 431)];
        }
    }

    [UIView commitAnimations]; 
}

-(void)viewWillAppear:(BOOL)animated
{
   [super viewWillAppear:animated];
   [self hideTabbar:self.tabbarController];
}

-(void)viewWillDisAppear:(BOOL)animated
{
   [super viewWillDisAppear:animated];
   [self showTabbar:self.tabbarController];
}

以上代码参考了 http://stackoverflow.com/questions/5272290/how-to-hide-uitabbarcontroller

iOS编程新手的一些问题(2)

2、Cocoa collections的问题。

NSArray, NSDictionary, NSSet,以及这些类型的Mutable形式类型有一个共同点,就是只能接受对象作为Value。结构体和基本类型是不能保存进collections的。所以诸如

    NSMutableArray *array = [[NSMutableArray alloc]init];
    int i = 1;
    NSInteger nsi = 2;
    CGRect rect = CGRectMake(0, 0, 0, 0);
    [array addObject:1];
    [array addObject:nsi];
    [array addObject:rect];

都是不正确的。int是基本类型,CGRect是结构体,所以不能被collections接受。

这里要注意的就是NSInteger声明的变量不是对象,它只是int或者long的别名。NSInteger的定义是这样的:

 

#if __LP64__ || TARGET_OS_EMBEDDED || TARGET_OS_IPHONE || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
#else
typedef int NSInteger;
#endif

另外的CGFloat也是一样的。

所以要将这种东西存储到collections里面,需要包装成对象。比如数字类型的东西都包装成NSNumber,CG等类型的结构体包装成NSValue,然后存入collections里面,读取的时候,调用相应的xxValue方法就能还原成基本类型。

3、使用Delegate模式需要小心:

我上次调试别人的程序时,几乎所有的崩溃都是因为使用delegate模式不当造成的。Java、C#使用delegate没有什么困难,而Objective-C里面,在没有ARC的情况下,需要手动管理内存,问题就出现了。

现在有A、B两个对象,A作为B的delegate,即B.delegate = A。假设B需要花很长的时间做完工作,做完工作之后调用A实现的delegate方法,而此时A已经被销毁了。这时B.delegate依旧指向A原来的地址而不是nil,B就会向原来地址的已经被销毁的对象发送消息。这时程序就崩溃了。

所以当B.delegate = A的情况下,在A会被销毁的时候,必须设置B.delegate = nil。这样,即便B要调用delegate方法时,A已经被销毁,B也是像nil发送消息。在Objective -C中,这样是完全合法的,并且会什么都不做。

很多时候,给B设置delegate的工作是在A内部做的,即在A的某个方法里面,有B.delegate = self这样的语句。所以A的dealloc方法里面需要增加

if (B.delegate==self){
    B.delegate = nil;
}

这样的语句。

4、viewDidUnload需要做事:

很多人在写代码的时候,总是忽略viewDidUnload这个方法。但是在使用xcode生成的ViewController模板里面又一直会看到viewDidUnload这个方法。模板里面明显有两行注释:

 

// Release any retained subviews of the main view.

// e.g. self.myOutlet = nil;

这是很重要的信息。它的意思是说,如果你在ViewController的对象里面retain了一些subview,就需要在这里将他们释放掉。

如果不写的话,乍看程序不会有什么问题,既不报错也不崩溃。但是在系统内存紧缺时,你的程序被系统kill的概率就非常的高了。

iOS在内存紧缺时发送memory warning。收到这个信号后,你的程序会自动调用现在还活着的ViewController的didReceiveMemoryWarning方法,并且如果你的ViewController并不在当前显示状态(可能在NavigationController栈里面),就会销毁这个ViewController.view。此时,view是retain过它的subview的,而如果viewController也retain过里面某些subview,那么这些subview的retainCount==2。系统销毁view时,对里面所有的subview进行release,此时被ViewController retain过的subview的retainCount==1,系统无法销毁。接下来,系统调用ViewController的viewDidUnload,如果此时你做了self.someSubview = nil这件事,那么对这个subview来说,又进行了release(由于点表达式是setter方法,参加前篇),此时retainCount==0,就可以被销毁了。如果什么都没做,那么这个Subview就不会被销毁,额外增加了内存负担,就提高被系统杀死的风险了。

这里另外要注意的是,写self.someSubview = nil是在someSubview为ViewController的@property(retain)时才能用的,如果不是property,而又在程序里retain了,那么viewDidUnload必须写成:

 

[someSubview release];
someSubview = nil;

切不可直接写someSubview = nil,否则就内存泄漏了。

iOS编程新手的一些问题(1)

这几天加班改另外几个已经离职的实习生遗留下来的代码,真是吐血三升啊。不知道这个公司为什么招这么多没有什么经验的iOS实习生,写出来的程序几秒钟就crash一次。

Objective-C因为只有苹果在用,所以接触的机会比较少,大多数人都是从别的语言转过来的,我也是,所以我也犯过很多错误,在错误中渐渐学会了Objective-C。

这里说一下这几天改代码发现的常见错误:

0、retain, release问题:

虽然现在有ARC了,不需要写retain, release,但是由于这个只能在iOS5以上才能实现全部功能,所以现在很多项目依然使用手动内存管理。retain, release的原理很简单,当你创建一个对象之后,什么时候把它销毁呢?答案是在没有人用它的时候就可以销毁了。每个对象有一个计数器叫做retainCount。当对象被创建时,retainCount为1,如果另外有一段代码要用这个对象一段时间,除了获取对象地址之外,还要向对象发送retain消息,这时候retainCount就会+1,此时,即便对象原先的拥有者释放了对象,该对象也不会被销毁。所谓的释放就是向对象发送release消息,retainCount会-1,表明又有一个人不需要用它了,当retainCount==0时,就说明没有人要用它了,便销毁了。

手动管理内存的Objective-C对此必须斤斤计较,否则很容易出现内存泄漏情况。很多Objective-C新手经常忘记release,导致大量的内存泄漏。在手机开发中是很危险的,因为程序会被系统kill掉,看起来就像崩溃一样。

初学者容易犯的错误是:没有retain之后没有release掉。导致大量内存泄漏。

Objective-C的内存管理有个规则:

当你是对象的创造者时(使用alloc, copy方法),在你不用它时,向它发送release。

当你需要保留不是你创造的对象一段时间时,需要向这个对象发送retain,并且用完后向它发送release。如果不需要保留一段时间,则不需要发送retain,也不需要发送release。

当一个函数需要返回一个对象时,需要将这个对象标记为autorelease。

就这三点。

1、getter, setter问题:

用Java的时候写getter setter再容易不过了,事实上只要用IDE生成一下就行了。但是由于Objective-C需要管理内存,所以没那么简单。

setter的写法是这样的:

 

-(void)setSomething:(SomeClass *)anObj
{
    if(anObject != _something){
        [_something release];
        _something = [anObj retain];
    }
}

因为setter和getter都是针对成员变量的,所以要拥有成员变量一段时间,加之你用setter设置成员变量,说明要赋给成员变量的那个anObj不是你创造的,所以需要retain。但是这还没完。当你第二次用setter设置成员变量时,原先的那个对象需要被释放掉,否则原先的对象的retainCount无法清零了。所以首先要release掉原来的,在retain新的。那么第一次使用setter时,something是nil,岂不是变成[nil release]了?没错,这在objective-c里面完全合法,向nil发送任何消息都返回nil且没有错误。

主要问题还不是在这里,现在的Objective C增加了@property和@synthesize指令,说白了就是自动帮你生成setter和getter,并且用了这两个指令之后,可以用点表达式进行getter和setter调用,看起来就像Java一样:

 

anObject.someProperty = newPropertyObject; //相当于调用[anObject setSomeProperty];
tempProperty = anObject.someProperty; //相当于调用[anObject someProperty];

而这个情况放在self的情况下就容易让人疑惑,在Java里面,在对象方法里面访问成员变量可以直接写someProperty,也可以写成this.someProperty,两者几乎完全一样。然而在Objective-C里面则完全不同。

self.someProperty = xxx时,实际上是执行了[self setSomeProperty],而这个方法里面包含了内存管理的方法。如果直接调用someProperty = xxx时,则是直接将someProperty指向xxx的地址,并没有做内存管理,此时就发生内存泄漏了。

初学者往往会被这个迷惑,从而写出

 

[someProperty release];
self.someProperty = nil;

这样的语句(多数在dealloc中)。编码者原来的意图是好的,将这个对象释放掉之后,将指针归到nil。然而这样写是错误的,因为在调用self.someProperty = xxx时,又会发生一次[someProperty release],但是此时someProperty已经被上面一句代码释放了,再释放一次就报错了。另外,

 

self.someProperty = [[SomeClass alloc]init];

这样的写法也是错误的,因为[[SomeClass alloc]init]时,新对象的retainCount==1,self.someProperty = xxx的语句执行了setter,又被retain了一次,此时retainCount==2了。所以之后就总是多1,无法清零了。

正确的写法是:

 

SomeClass *temp = [[SomeClass alloc]init];
self.someProperty = temp;
[temp release];

或者

self.someProperty = [[[SomeClass alloc]init]autorelease];

或者

someProperty = [[SomeClass alloc]init];

实际上,苹果的官方模板在写@synthesize时,特地写成了:

 

@synthesize someProperty = _someProperty;

这样一来,就只能使用self.someProperty和_someProperty了,someProperty是无效的,这样可以避免混淆。