Monday, 17 June 2013

iOS TableView Part I Cell Reused.

1.  我们为什么要重用cell?
 答案很简单,就是performance的原因。
 每次当我们滑动的时候,遇到未知的位置,我们就需要找到个cell来填充,这时候我们可以选  择创建个cell出来,然后把需要的信息填进cell,这样的坏处是什么呢?
Object allocation 是一个非常消耗效率和时间的事,而且特别在一个很短周期内重复地alloc Object。所以这时候当我们scroll table view的时候,我们就可以利用已经被划出屏幕的cell, 重用这些cell,并不是重新创建cell, 这样我们就大大地提高了我们 table view的效率。

2. 重用cell的原理

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    static NSInteger count = 0;
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier ];
    if (cell == nil) {
        cell = [[UITableViewCell allocinitWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
        count++;
        NSLog(@"%d", count);
    }
    
    cell.textLabel.text = [NSString stringWithFormat:@"Row %d", indexPath.row];
    
    return cell;
}



so 大家可以理解的是,当我们启动程序的时候,我们创建了这一屏幕个数的cell, 就是11个cell,
然后等我们滑动一下,第12个cell被创建了,我们继续滑动,就没有然后了,创建cell的数量就停在12个上面。

通过上面的现象我们就可以理解到cell,  当我们创建了tableview 和 加载第一屏的cell时候,我们就创建了一个内部的队列用来管理可以重用的cell, 像上图一样,当我们滑动出row11时候,这时候我们的重用队列里面没有可以重用的cell, 我们就只有重新再创建一个cell出来,然后把已经移除屏幕的cell 0加进我们的重用队列里面去。然后我们继续滑动出row 12的时候,这时候我们去查看我们的重用队列,发现里面有可以重用的cell 0, 这时候我们就把cell 0 重用来作为cell 12, 这样就完成了,我们对cell 的重用。

为了验证我们的结论,就让我们来调试一下代码,看看是怎么回事吧,
所以我们在tableView:cellForRowAtIndexPath: 每次创建cell的时候打上断点,然后我们发现cell 0 被创建出来了,address是 0x0f608530,  这时候我们发现了tableview 里面有个成员叫做
_reusableTableCells的dictionary, 为什么是个dictionary, 因为我们可能有多种 cell identifier 可能需要重用。当我们没有滑动时候,这时候_reusableTableCells没有key.

当我们滑动时候,这时候tableView:cellForRowAtIndexPath: 又被调用,这时候_reusableTableCells 里面还是没有可以重用的,所以我们只能再创建出cell 11来,当第0个cell 被完全移除屏幕的时候,这时候我们的_reusableTableCells 把cell 0 加进来,当我们滑动出cell 12的时候,我们就可以发现,这时候我们_reusableTableCells里面有东西了,然后我们通过dequeueReusableCellWithIdentifier 把它取出来时,这话cell的地址就就是我们cell 0, 0x0f608530.
这就是iOS tableView 重用cell的原理。












Friday, 7 June 2013

iOS Load and initialize


+(void)load 是用来干什么的?

      无论是否一个class 或者是一个category 静态或者是动态的加载进来的时候,都会调用load 方法。
      说白了,就是当你申明了一个class,加载进工程的时候,即使你没有用到它,这个class的load 方法已经被调用了。

#import "BaseObject.h"

@implementation BaseObject

+ (void)load {
    NSLog(@"BaseObject, %s", __FUNCTION__);    //不应该在call [super load] 在load 方法里面。
}
@end

在我的工程里,我也没有用它,但是这个BaseObject 的load 方法也被调用了。

通常当一个class有多个category的时候,如果有2个category 都实现了同一个方法的时候,只会有一个category的方法会被调用,另外一个category的方法就不会。
但是对于load就是个特例了,每个category对于一个class的load方法只要实现了,都会被调用。
当我们用+load()方法时候,是没有autorelease pool 在这里的,所以如果需要用autorelease 的话,我们就要自己设置为autorelease pool 来管理objects.

那我们一般会+load()方法里面做什么呢?
总结了下用法,主要还是做一些预处理的事情,例如经典的wrapper class 用我们的实现替换原有的实现就可以在+load()方法里面。

+ (void)load
{
    Method original, swizzled;
    
    original = class_getInstanceMethod(self, @selector(synchronize));
    swizzled = class_getInstanceMethod(self, @selector(swizzled_synchronize));
    method_exchangeImplementations(original, swizzled);
}

+(void)initialize 是用来干什么的?
       当我们第一次对一个Object 发消息的时候,  +initialize()就被调用起来了, 当然+load() 方法 我们不能算作第一调用。
同load不一样的是,子类的initialize 会触发父类的initialize,  当子类没有实现initialize的时候,他就会去用父类的initialize.

@implementation BaseObject

+ (void)initialize {
    NSLog(@"%@, %s", [self class], __FUNCTION__);
}
@end


@implementation RChildObject
@end


当我们去向RChildObject 发送事件的时候,我们就会发现,输出结果是这样的
BaseObject, +[BaseObject initialize]
RChildObject, +[BaseObject initialize]

不像+load()方法那样,在调用+initialize()的时候,我们已经有了context了,所以autorelease pool 就可以用了。

与init的不同,+initialize()是针对于class而言的,所以不管你实例化多少个实例,这个initialize只会被调用一次。
    RChildObject *objc1 = [[RChildObject alloc] init];
    RChildObject *objc2 = [[RChildObject alloc] init];
    RChildObject *objc3 = [[RChildObject alloc] init];
只会有一次输出
RChildObject, +[BaseObject initialize]

因为这样的特性,我们就可以用+initialize() 来完成我们的singleton,因为+initialize() 对于class而言只会被调用一次,并且是thread-safe.

static MySingleton *sharedSingleton;

+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        sharedSingleton = [[MySingleton alloc] init];
    }
}

这个+initialize()只会被调用一次,那为什么会加这个BOOL变量呢?
原因就是前面说的那样,如果有个subclass 没有实现+initialize()方法,那么父类的+initialize()就会调用,如果这里不加判断,就会出现泄露的问题, 如果你想加变量,你也可以这样来判断
self == [xxx class], 来判断是否是我们想要的class 发过来的信息。