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 发过来的信息。





Sunday, 28 April 2013

新的开始

      最近换了工作,接触到了新的同事,开始了新的项目,开始了新的旅程。

万事开头难,真的不是盖的,总是觉得自己很不适合这个公司,总觉得自己的压力大感觉挺不过去了,问了些前辈的,都说才开始进去,可能不适应这些。后来慢慢发现却是自己真的懒了,怕了,觉得输不起了,太多的现实冲击着我,比自己年轻的同事却比自己懂得更多,更加有潜力,比自己大点的同事,已经功成名就了,自己还在为跳槽了那点破事忙着,心态慢慢觉得变态了,甚至有时还问自己爱编程不?

这时候想起了乔帮主的语言

  • 你的時間有限,所以不要浪費時間活在別人的生活裡。
Your time is limited, so don't waste it living someone else's life.

确实呀,我的时间本来就不多了,为什么要去想这么多呢,为什么这么care人家呢,我就是我呀,而且我真的喜欢编程,我希望10年后,告诉自己太开心了又编了10年程序。。

自己在编程方面的喜欢的太多了,技术是层出不穷,感觉自己毕业到现在3年里,追过很多技术,但是自己的成就完全不能提。
做过一些小东西,就拿ruby来说,弄了rails小网站,然后就没有然后,看了些linux的东西,然后也没有然后了。。 自己本来是做Mobile Application开发的,感觉在Mobile方面也没有贡献,觉得自己专注真的有点不够,但是自己又觉得自己是什么天才,可以内外兼修,现在发觉不够的是自己时间。
感觉自己是时候要专注了,专注一个方向做好,做精对于现在的我才是王道。
为了自己最喜欢的linux, 决定制定个操作系统的学习计划,坚持到底是走下去,其他的爱好先再旁边站站,maybe 有一天我真的能成为kernel 核心开发人员。
但是为了工作,工作事情也要雄起呀,决定对于Mobile Application做个总结,希望我能在两者之前做到很好的平衡,来成就自己的未来。

本文献给那些向我这样遇到矛盾的人们,希望能给你们带来一些帮助,希望跟大家多多交流。


Regrecall

Tuesday, 19 March 2013

Improve your skills with github and stackoverflow

     I learned iOS one year ago,  started with one famous iOS book  iOS Programming: The Big Nerd Ranch Guide. For an entire year, I'm from an newbie to one that can develop some easy apps, but I think I encounter a bottleneck, I don't know how to improve my skills about iOS, even I develop another app, almost will copy some old things from Previous project, there's no new skills learned.

Fortunately, I found some ways to improve my skills, it's reading code and reading questions.

1.    Learning from Github

There are many open source projects hosted in the Github,  there is no secret in front of you.
Recently I learned one project QuickDialog,  it's used to help you create the forms with multiple text fields, or with thousands items.
It has one function, that can load the json file, and create the view according to the json file, make me understand how magic about the NSClassFromString.

When you init the root element with the json file,  it would parse the json file to the NSDictionary, and use the valueForKey(), get the value of the key, then use the NSClassFromString to construct the class which you type in the json file.

   "sections": [
                 {   "title":"password form", "footer":"You first use this app, please set your password.",
            "elements": [
                {   "type":"QEntryElement", "title":"Password","placeholder":"Password", "secureTextEntry":true, "bind":"textValue:password","key":"password"},
                {   "type":"QEntryElement", "title":"Confirm Password", "placeholder":"Password", "secureTextEntry":true, "bind":"textValue:confirmedPassword"}
                        ]
        },

when it found the "type" key, it will use  NSClassFromString  to construct the QEntryElement instance put in the root element tree.
these primary code about using the dictionary to constrcu the element.
    QSection *sect = nil;
    if ([obj valueForKey:[NSString stringWithFormat:@"type"]]!=nil){
        sect = [[NSClassFromString([obj valueForKey:[NSString stringWithFormat:@"type"]]) alloc] init];
    } else {
        sect = [[QSection alloc] init];
    }
    [self updateObject:sect withPropertiesFrom:obj];
    [root addSection:sect];
    for (id element in (NSArray *)[obj valueForKey:[NSString stringWithFormat:@"elements"]]){
       [sect addElement:[self buildElementWithObject:element] ];
    }


maybe before you used NSClassFromString to construct the class in the dynamic, but this is also awesome, it make me know the basic knowledge to config something or plug-in something from files.

Another skill (Key-Value Observing) from the project Kal,
In the Kal, there's  the KalLogic class holds the data about selected date and previous month and following month and so on.
It's the core datasource of the KalView which contains multiple subviews, so how it solve problem when view A changed, view B should change  according to view A in the KalView.

It use the Key-Value Observing, the KalView addObserver about the objects changed,  like the KalLogic changed the date, then KalView
Observing these change, then update the some subviews's frame and shown content of subvirew.

[logic addObserver:self forKeyPath:@"selectedMonthNameAndYear" options:NSKeyValueObservingOptionNew context:NULL];

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (object == gridView && [keyPath isEqualToString:@"frame"]) {
        CGFloat gridBottom = gridView.top + gridView.height;
        CGRect frame = tableView.frame;
        frame.origin.y = gridBottom;
        frame.size.height = tableView.superview.height - gridBottom;
        tableView.frame = frame;
        shadowView.top = gridBottom;
       
    } else if ([keyPath isEqualToString:@"selectedMonthNameAndYear"]) {
        NSDate * date = [change objectForKey:NSKeyValueChangeNewKey];
        [self.delegate updateYearDate:date];
   }
}

The KalLogic also have something we should learn:
implementing a class method that follows the naming convention keyPathsForValuesAffecting<Key>, where <Key> is the name of the attribute (first letter capitalized) that is dependent on the values.

+ (NSSet *)keyPathsForValuesAffectingSelectedMonthNameAndYear
{
    return [NSSet setWithObjects:@"baseDate", nil];
}

- (NSDate *)selectedMonthNameAndYear;
{
    return self.baseDate;
}


So when the baseDate changed, then the observer for the key path is @"selectedMonthNameAndYear" will be notified, and from the apple document, this methods normally allow you want to be notified when value A or Value B changed, like

+ (NSSet *)keyPathsForValuesAffectingFullName {

    return [NSSet setWithObjects:@"lastName", @"firstName", nil];

}


An application observing the fullName property must be notified when either the firstName or lastName properties change, as they affect the value of the property.

2.    Learning from Stack overflow

The core idea about learning from Stack overflow is answer question and read question,
These are my methods about Learning,
a.    First  you must see the votes about your interesting topic,  you can know something lost in your knowledge,
Like  me I learned  how to migrate to iPhone 5 app,    check the iOS version, one answer is so awesome, has a very nice marco

b.    After read so many question, maybe you want to answer someone, so you can answer the newest or unanswered question now.

Not Only iOS, I think many programming skill could use these two methods(read code and read questions) to improve,  If you have another
Methods, please tell me, I will be very appreciate.