Cảm ơn bạn đã đọc và ủng hộ blog KTMT ʘ‿ʘ Từ bây giờ chúng tôi sẽ là kipalog.com !

Comments

1. Giới thiệu

Trong quá trình vận hành 1 website chúng ta thường có các task như nâng phiên bản máy chủ web, nâng phiên bản cơ sở dữ liệu (vì mục đích nâng cao hiệu năng hoặc đảm bảo bảo mật). Với những hệ thống sẵn có, quản lý lượng dữ liệu lớn, tác vụ này thường không hề đơn giản, đòi hỏi 1 trình tự hơp lý. Bài viết này tổng kết là trình tự nâng cấp mysql từ phiên bản 5.0.x lên phiên bản 5.6.x và những vấn đề mình gặp phải trong quá trình nâng cấp này.

2. Bài toán

Website sử dụng hệ thống cơ sơ dữ liệu mysql, dưới mô hình master/slave. Cả master/slave đều đang chạy mysql 5.0. Ta cần nâng cấp mysql lên phiên bản 5.6.

Điều kiện

Mọi bài toán đều không có gì khó nếu như không có giới hạn gì. Ở đây ta bắt gặp 1 vài giới hạn cần chú ý như sau:

  • Các query được dùng trong website có thể không tương thích với mysql phiên bản mới.
  • Nâng cấp master đồng thời với slave sẽ có rủi ro khi mà cả 2 gặp lỗi.
  • Dung lượng ổ cứng: khi dung lượng ổ cứng sao lưu không cho phép, ta sẽ phải thực hiện các thao tác công phu hơn 1 chút.

Do vậy, cách tốt nhất là nâng cấp lần lượt slave -> master. Tức là slave sẽ chạy mysql5.6 và master sẽ chạy mysql5.0

Phương pháp

Có 2 cách nâng cấp mysql.

  • Cách truyền thống như được viết trong tài liệu hướng dẫn nâng cấp. Cụ thể ta nâng cấp dần dần qua các phiên bản trung gian bằng script mysql_upgrade.
  • Cách ngắn gọn: dump toàn bộ dữ liệu ở phiên bản cũ và import lại vào phiên bản mới.

Mỗi cách có ưu nhược điểm riêng; cụ thể cách 1 đảm bảo khả năng thành công cao, tuy vậy lại có nhược điểm là chuẩn bị môi trường cho các phiên bản rườm rà, tốn thời gian. Với khoảng cách 2 phiên bản lớn (5.0 -> 5.6: qua 5.1, 5.5) thì việc upgrade khá tốn thời gian.

Cách 2 có ưu điểm nhanh, tuy vậy lại có nhược điểm là tính tương thích giũa 2 phiên bản không tốt, dẫn đến khả năng lỗi sau khi upgrade cao.

Bài viết sẽ trình bày quy trình upgrade theo cách 2.

3. Quy trình:

Bước 1: dump toàn bộ dữ liệu

Ta có thể dump toàn bộ dữ liệu từ slave hoặc master hiện tại.

  1. Dump dữ liệu từ slave
    mysql> stop slave
    mysql> show slave status;

Ghi nhớ các thông tin: master_host, master_port, replication_user, relay_master_log_file,

$ mysqldump -u username -p --all-database --single-transaction | gzip -f > dumpfile.gz
mysql> start slave;
  1. Dump dữ liệu từ master
mysql> flush tables with read lock;
mysql> show master status;
mysqldump -u username -p --all-database --single-transaction | gzip -f > dumpfile.gz
mysql> unlock tables;

Bước 2: Cài đặt mysql phiên bản 5.6

$ /etc/init.d/mysql stop                 # stop mysql-5.0
$ cd /usr/local
$ wget http://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.13-linux-glibc2.5-x86_64.tar.gz/from/http://cdn.mysql.com/
 
$ tar xfz mysql-5.6.13-linux-glibc2.5-x86_64.tar.gz
$ mv mysql-5.6.13-linux-glibc2.5-x86_64 mysql-5.6.13
 
# symbolic link張替える
$ rm mysql
$ ln -sf mysql-5.6.13 mysql
$ cd mysql
$ chown -R mysql:mysql .

Bước 3: Khởi động mysql và import dữ liệu

$ cp /tmp/my.cnf /usr/local/mysql
 
# データ容量そろえる
$ cd /dbdata/data
$ rm -rf *
 
# 起動するため、テンポラリデータフォルダを作る
$ cd /usr/local/mysql
$ cd data                             # 空きフォルダか確認
$ ll 
$ cd ..
$ ./scripts/mysql_install_db --user=mysql
$ cp support-files/mysql.server /etc/init.d/mysql
$ /etc/init.d/mysql start
$ mysqladmin -u root password 'pass'
 
 
# data フォルダは /dbdata/dataにする
$ mv data data.bk
$ ln -sf /dbdata/data data

# import data file
$ mysql -u root -p < バックアップファイル

Bước 4: Khởi động lại mysql.

$ /etc/init.d/mysql stop
$ /etc/init.d/mysql start

Bước 5: Cài đặt sao chép (replication)

$ mysql -u root -p
mysql> show slave status¥G;   # replication されてない確認
 
....
 
mysql> change master to master_host='master ip',
         master_user='レプリケーションユーザ名',
         master_password='パスワード',
         master_log_file='mysql-bin.000003',
         master_log_pos=73;
  Query OK, 0 rows affected (0.03 sec)
mysql>
mysql> show slave status¥G
*************************** 1. row ***************************
             Slave_IO_State:
                Master_Host: master ip
                Master_User: レプリケーションユーザ名
                Master_Port: 3306
              Connect_Retry: 60
            Master_Log_File: mysql-bin.001378
        Read_Master_Log_Pos: 348578503
             Relay_Log_File: mysqld-relay-bin.000001
              Relay_Log_Pos: 4
      Relay_Master_Log_File: mysql-bin.001378
           Slave_IO_Running: No
          Slave_SQL_Running: No
            Replicate_Do_DB:
        Replicate_Ignore_DB:
         Replicate_Do_Table:
     Replicate_Ignore_Table:
    Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
                 Last_Errno: 0
                 Last_Error:
               Skip_Counter: 0
        Exec_Master_Log_Pos: 348578503
            Relay_Log_Space: 98
            Until_Condition: None
             Until_Log_File:
              Until_Log_Pos: 0
         Master_SSL_Allowed: No
         Master_SSL_CA_File:
         Master_SSL_CA_Path:
            Master_SSL_Cert:
          Master_SSL_Cipher:
             Master_SSL_Key:
      Seconds_Behind_Master: NULL
1 row in set (0.00 sec)
  
mysql> start slave;

Bước 6: Kiểm tra cài đặt replication.

mysql> show slave status¥G
*************************** 1. row ***************************
             Slave_IO_State:
                Master_Host: master ip
                Master_User: レプリケーションユーザ名
                Master_Port: 3306
              Connect_Retry: 60
            Master_Log_File: mysql-bin.001378
        Read_Master_Log_Pos: 348578503
             Relay_Log_File: mysqld-relay-bin.000001
              Relay_Log_Pos: 4
      Relay_Master_Log_File: mysql-bin.001378
           Slave_IO_Running: No
          Slave_SQL_Running: No
            Replicate_Do_DB:
        Replicate_Ignore_DB:
         Replicate_Do_Table:
     Replicate_Ignore_Table:
    Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
                 Last_Errno: 0
                 Last_Error:
               Skip_Counter: 0
        Exec_Master_Log_Pos: 348578503
            Relay_Log_Space: 98
            Until_Condition: None
             Until_Log_File:
              Until_Log_Pos: 0
         Master_SSL_Allowed: No
         Master_SSL_CA_File:
         Master_SSL_CA_Path:
            Master_SSL_Cert:
          Master_SSL_Cipher:
             Master_SSL_Key:
      Seconds_Behind_Master: NULL
1 row in set (0.00 sec)
  
mysql> start slave;

4. Các lỗi có thể có

Nếu làm mọi việc suôn sẻ, ta có thể hoàn thành thao tác nâng cấp ở bước 4. Tuy vậy tuỳ vào đặc tính dữ liệu, mà mọi việc có thể không suôn sẻ như vậy. Trong lần nâng cấp này, mình gặp 1 lỗi sau sau khi khởi động mysql.

2013-08-25 11:07:00 19807 [Note] Server socket created on IP: '0.0.0.0'.
02:07:00 UTC - mysqld got signal 11 ;
This could be because you hit a bug. It is also possible that this binary
or one of the libraries it was linked against is corrupt, improperly built,
or misconfigured. This error can also be caused by malfunctioning hardware.
We will try our best to scrape up some info that will hopefully help
diagnose the problem, but since we have already crashed,
something is definitely wrong and this may fail.

key_buffer_size=134217728
read_buffer_size=2097152
max_used_connections=0
max_threads=100
thread_count=0
connection_count=0
It is possible that mysqld could use up to
key_buffer_size + (read_buffer_size + sort_buffer_size)*max_threads = 542029 K  bytes of memory
Hope that's ok; if not, decrease some variables in the equation.

Thread pointer: 0xe57de70
Attempting backtrace. You can use the following information to find out
where mysqld died. If you see no messages after this, something went
terribly wrong...
stack_bottom = 7fffd70eced8 thread_stack 0x40000
/usr/local/mysql/bin/mysqld(my_print_stacktrace+0x35)[0x8fa385]
/usr/local/mysql/bin/mysqld(handle_fatal_signal+0x3e8)[0x66cfd8]
/lib64/libpthread.so.0[0x3a9300eb10]
/usr/local/mysql/bin/mysqld(_Z9get_fieldP11st_mem_rootP5Field+0x3c)[0x77b16c]
/usr/local/mysql/bin/mysqld[0x68c5bc]
/usr/local/mysql/bin/mysqld(_Z10acl_reloadP3THD+0x459)[0x68f059]
/usr/local/mysql/bin/mysqld(_Z8acl_initb+0x117)[0x6901c7]
/usr/local/mysql/bin/mysqld(_Z11mysqld_mainiPPc+0x543)[0x582e13]
/lib64/libc.so.6(__libc_start_main+0xf4)[0x3a9241d994]
/usr/local/mysql/bin/mysqld(__gxx_personality_v0+0x2e1)[0x5779e9]

Trying to get some variables.
Some pointers may be invalid and cause the dump to abort.
Query (0): is an invalid pointer
Connection ID (thread ID): 0
Status: NOT_KILLED

The manual page at http://dev.mysql.com/doc/mysql/en/crashing.html contains
information that should help you find out what is causing the crash.
130825 11:07:00 mysqld_safe Number of processes running now: 0
130825 11:07:00 mysqld_safe mysqld restarted
2013-08-25 11:07:01 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
2013-08-25 11:07:01 19847 [Note] Plugin 'FEDERATED' is disabled.
2013-08-25 11:07:01 19847 [Note] InnoDB: The InnoDB memory heap is disabled
2013-08-25 11:07:01 19847 [Note] InnoDB: Mutexes and rw_locks use GCC atomic builtins
2013-08-25 11:07:01 19847 [Note] InnoDB: Compressed tables use zlib 1.2.3
2013-08-25 11:07:01 19847 [Note] InnoDB: Using Linux native AIO
2013-08-25 11:07:01 19847 [Note] InnoDB: Not using CPU crc32 instructions
2013-08-25 11:07:01 19847 [Note] InnoDB: Initializing buffer pool, size = 512.0M
2013-08-25 11:07:01 19847 [Note] InnoDB: Completed initialization of buffer pool
2013-08-25 11:07:01 19847 [Note] InnoDB: Highest supported file format is Barracuda.
130825 11:07:09 mysqld_safe mysqld from pid file /usr/local/mysql/data/nl-dbdev-slave.pid ended

mysql khởi động gặp bug về bộ nhớ (segmentation fault) và bị kill bởi signal 11. Mysql liên tục khởi động và bị kill.

Để tránh trường hợp này, sau khi import dữ liệu ta nên chạy mysql_upgrade 1 lần để script này sửa các bảng trong trạng thái lỗi trước khi khởi động lại để tránh lỗi ở trên.

5. Tổng kết

Bài viết tóm tắt quy trình nâng cấp phiên bản mysql cũng như cách dump toàn bộ dữ liệu trong mysql cũng như lỗi có thể gặp phải + cách giải quyết. Hy vọng với tóm tắt này, bạn sẽ không bở ngỡ khi phải backup hay nâng cấp phiên bản cho mysql.

iOS
Comments

Mở đầu

Đối với những lập trình viên khi mới tiếp xúc với Objective-C, chắc hẳn sẽ gặp phải 1 số bỡ ngỡ với các cú pháp của nó. Tuy được kế thừa từ C nhưng Objective-C lại có cách gọi hàm, sử dụng biến khác hẳn. Vì thế, bài viết này sẽ giới thiệu cho mọi người 1 số mẹo để lập trình hiệu quả với Objective-C, đặc biệt là đối với những ai chưa có thời gian dài tiếp xúc với nó.

Objective-C Literals

  • Thứ nhất là đối với NSNumber, thay vì phải khởi tạo dài dòng như [NSNumber numberWithInt:x]… chúng ta có thể thay thế bằng các cách dưới đây:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// character literals.
  NSNumber *theLetterZ = @'Z';          // tương đương với [NSNumber numberWithChar:'Z']

  // integral literals.
  NSNumber *fortyTwo = @42;             // tương đương với [NSNumber numberWithInt:42]
  NSNumber *fortyTwoUnsigned = @42U;    // tương đương với [NSNumber numberWithUnsignedInt:42U]
  NSNumber *fortyTwoLong = @42L;        // tương đương với [NSNumber numberWithLong:42L]
  NSNumber *fortyTwoLongLong = @42LL;   // tương đương với [NSNumber numberWithLongLong:42LL]

  // floating point literals.
  NSNumber *piFloat = @3.141592654F;    // tương đương với [NSNumber numberWithFloat:3.141592654F]
  NSNumber *piDouble = @3.1415926535;   // tương đương với [NSNumber numberWithDouble:3.1415926535]

  // BOOL literals.
  NSNumber *yesNumber = @YES;           // tương đương với [NSNumber numberWithBool:YES]
  NSNumber *noNumber = @NO;             // tương đương với [NSNumber numberWithBool:NO]

#ifdef __cplusplus
  NSNumber *trueNumber = @true;         // tương đương với [NSNumber numberWithBool:(BOOL)true]
  NSNumber *falseNumber = @false;       // tương đương với [NSNumber numberWithBool:(BOOL)false]
#endif
  • Tạo mảng nhanh: Thay vì dùng khởi tạo [NSArray arrayWithObjects:…] chúng ta có thể dùng:
1
NSArray *array = @[ @"Hello", NSApp, [NSNumber numberWithInt:42] ];

Và tạo NSDictionary:

1
2
3
4
5
NSDictionary *dictionary = @{
    @"name" : name1,
    @"date" : [NSDate date],
    @"processInfo" : [ProcessInfo processInfo]
};

Cách gọi trên kia sẽ tạo ra 1 NSDictionary với 3 key: name, date, processInfo và các value tương ứng. Các value phải là đối tượng của ObjectiveC và phải khác nil (nếu không sẽ crash). Tiện thể với dictionary, khi khởi tạo 1 NSDictionary:

1
2
3
4
5
6
[NSDictionary dictionaryWithObjectsAndKeys:
                value_1, @"key1",
                value_2, @"key2",
                value_3, @"key3",
                ...
                value_n, @"keyn", nil]

Nếu có 1 trong các value từ value_1 đến value_n bằng nil, vd là value_i, thì NSDictionary được tạo ra sẽ chỉ nhận được các key và value trong khoảng từ value_1 đến value_(i-1) chứ không làm crash chương trình. Vì vậy, trong lúc lập trình, nên chú ý điều này để tránh việc tìm không ra lỗi.

  • Sử dụng toán tử chỉ số cho array và dictionary giống C:
1
2
3
4
5
6
7
8
9
10
NSMutableArray *array = ...;
NSUInteger idx = ...;
id newObject = ...;
id oldObject = array[idx];  // tương đương với oldObject = [array objectAtIndex:idx]
array[idx] = newObject;         // tương đương với [array replaceObjectAtIndex:idx withObject:newObject]

NSMutableDictionary *dictionary = ...;
NSString *key = ...;
oldObject = dictionary[key];    // tương đương với oldObject = [dictionary objectForKey:key]
dictionary[key] = newObject;    // tương đương với [dictionary setObject:newObject forKey:key]

Chú ý là replace object chỉ dùng được cho NSMutableArray và NSMutableDictionary, không dùng được cho NSArray và NSDictionary.

Mẹo debug với XCode

Khi debug code Objective C, chương trình sẽ nhảy ra hàm main int retVal = UIApplicationMain(argc, argv, nil, @"AppDelegate"); mỗi khi có crash. Màn hình log thì có quá ít thông tin để giúp cho việc debug lỗi crash này. Vậy thì làm thế nào để khắc phục điều này, giúp cho XCode stop lại ở đúng nơi nó bị crash? Đầu tiên là mở panel Breakpoint Navigator và click vào button + ở góc trái dưới màn hình, chọn Add Exception Breakpoint:

Sau đó ấn Done button để tạo 1 exception breakpoint mới:

Chuột phải vào breakpoint mới tạo ra, chọn Move breakpoint to > User để áp dụng cho tất cả các workspaces khác:

Vậy là xong, kể từ bây giờ bạn sẽ được nhìn thấy nơi chôn rau cắt rốn của đống crash :)

Tổng kết

Những tips trong bài viết này tuy nhỏ nhưng có thể sẽ rất hữu ích trong quá trình code của bạn, giúp code ngắn gọn và sáng sủa hơn. Tất nhiên vẫn còn rất nhiều kỹ thuật đặc biệt khác trong Objective-C mà trong khuôn khổ bài viết này chưa thể đề cập hết được. Vì thế, hãy đợi bài viết sau nhé :)

Comments

Mở đầu

Để tiếp nối chuỗi bài về TableView, hôm nay mình cũng viết một bài liên quan đến TableView. Trong iOS TableView là class được dùng khá nhiều. Khi dùng TableView chúng ta thường phải set datasource và delegate cho TableView. Thường thì datasource của TableView là một array.

Khá nhiều bạn thường set datasource cho Tableview ngay trong ViewController (tableview.datasource = self). Và khi đấy trong ViewController chúng ta luôn luôn phải implement delegate cho TableViewDataSource như sau:

TmpViewController.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
#pragma mark - UITableViewDataSource delegate
- (NSInteger)tableView:(UITalbeView *)tableView numberOfRowsInSection:(NSInteger)section
{
  return [self.dataArray count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  static NSString *cellIdentifier = @"MyCell";
  // lấy cell có sẵn
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
  // nếu không có cell có sẵn thì tạo cell mới
  if(cell == nil) {
    cell = [UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                 reuseIdentifier:cellIdentifier];
  }

  // lấy dữ liệu cho cell hiện tại. (Ví dụ dữ liệu là NSString)
  NSString *item = [self.dataArray objectAtIndex:indexPath.row];
  // gán dữ liệu cho cell
  [cell.textLabel setText:item];

  return cell;
}

Việc viết như trên đối với những ứng dụng nhỏ thì không vấn đề gì nhưng khi ứng dụng sử dụng nhiều tableview thì trong từng ViewController chúng ta luôn phải viết đi viết lại đoạn code trên. Nếu nhìn kỹ đoạn code trên bạn sẽ thấy thực ra với mỗi TableView khác nhau chúng ta chỉ cần thay đổi phần #gán dữ liệu cho cell tuỳ theo cấu trúc của từng cell. Còn đâu những phần còn lại chúng ta có thể sử dụng lại code. Ngoài ra nếu chúng ta để những đoạn code này trong ViewController sẽ khiến ViewController trở nên dài hơn bởi vì bản thân ViewController đã chứa rất nhiều code như delegate, code xử lý sự kiện, gesture. Vì vậy để có một ViewController ngắn gọn hơn, dễ hiểu hơn, lại tăng tính sử dụng lại code chúng ta sẽ tạo 1 class datasource riêng tên là TVArrayDataSource.

Tạo class TVArrayDataSource

Vậy chúng ta sẽ chuyển hết code ở trên sang class TVArrayDataSource và trong các ViewController chúng ta chỉ cần viết phần #gán dữ liệu cho cell tuỳ theo cấu trúc của cell. Vậy trong TVArrayDataSource cần những property gì?

Đầu tiên là NSArray *items trỏ đến array data của chúng ta trong ViewController để chúng ta có thể lấy data tương ứng cho từng cell và cell identifier NSString *cellIdentifier là string dùng để định danh cell.

TVArrayDatasource.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
@interface TVArrayDataSource()

@property (strong, nonatomic) NSArray *items;
@property (copy, nonatomic) NSString *cellIdentifier;

@end

@implementation TVArrayDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
  return [self.items count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // tìm cell có sẵn
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifier];
    // tạo cell mới nếu không tìm thấy
    if (cell == nil) {
      ...
    }

    // lấy data cho cell
    id item = [self.items objectAtIndex:indexPath.row];

    // gán dữ liệu cho cell
    ...

    return cell;
}

@end

Đầu tiên chúng ta sẽ nói về đoạn ... tại phần gán dữ liệu cho cell. Tại vì tuỳ từng trường hợp của tableview mà cell của chúng ta có cấu trúc khác nhau, data source có cấu trúc khác nhau nên phần gán dữ liệu này là khác nhau. Do đó tại đây chúng ta có thể gọi đến các hàm callback trong ViewController để gán dữ liệu cho cell theo cách mà chúng ta muốn. Có nhiều cách như dùng block, selector hay delegate. Mình thì thấy tiện nhất và ngắn nhất là block và selector nên mình sẽ tạo class TVArrayDataSource có thể dùng block hoặc selector.

Với block thì chúng ta cần tạo 1 property để lưu block và execute block tại đoạn gán dữ liệu. Chúng ta sẽ thêm block property vào TVArrayDataSource.m và tạo 1 method khởi tạo dataSource với block như sau:

TVArrayDataSource.m
1
2
3
4
5
6
7
8
typedef void (^TVCellConfigureBlock)(id, id);

@interface TVArrayDataSource : NSObject <UITableViewDataSource>

/* khởi tạo datasource với block */
- (id)initWithItems:(NSArray *)items
     cellIdentifier:(NSString *)cellIdentifier
 cellConfigureBlock:(TVCellConfigureBlock) configureBlock;
TVArrayDataSource.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
...
// thêm block property vào
@property (copy, nonatomic) TVCellConfigureBlock configureBlock;

// và method khởi tạo chỉ đơn giản như sau
- (id)initWithItems:(NSArray *)items
     cellIdentifier:(NSString *)cellIdentifier
 cellConfigureBlock:(TVCellConfigureBlock)configureBlock
{
    self = [super init];
    if(self) {
        self.items = items;
        self.cellIdentifier = cellIdentifier;
        self.configureBlock = configureBlock;
    }
    return self;
}

// và chúng ta thêm phần execute block tại đoạn gán dữ liệu cho cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
   // tìm cell có sẵn
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifier];
    // tạo cell mới nếu không tìm thấy
    if (cell == nil) {
      ...
    }

    // lấy data cho cell
    id item = [self.items objectAtIndex:indexPath.row];

    // execute block để gán dữ liệu cho cell
    self.configureBlock(cell, item);

    return cell;
}

Khi đó bên ViewController chúng ta chỉ cần tạo 1 block để thực hiện việc gán dữ liệu cho cell. Và block này sẽ được execute bằng self.configureBlock(cell, item) với tham số là cell hiện tại và data tương ứng của cell. Bởi vì tham số của block là cell hiện tại và data cho cell đấy nênchúng ta hoàn toàn có thể tự do tuỳ chỉnh cell theo ý muốn. Và code bên ViewController sẽ rất ngắn và đẹp như sau:

ViewController1.m
1
2
3
4
5
6
7
8
9
10
// configure block. Kiểu tham số có thể tuỳ chỉnh theo kiểu data bất kỳ của bạn.
TVCellConfigureBlock configureCell = ^(CellClassName *cell, DataType *name) {
  // gán dữ liệu cho cell. ví dụ như sau:
  [cell.title setText:name];
};
// tạo instance dataSource của TVArrayDataSource và khởi tạo với block ở trên
dataSource = [[TVArrayDataSource alloc] initWithItems:items
                                       cellIdentifier:@"MYCELL"
                                   cellConfigureBlock:configureCell];
tableView.datasource = dataSource;

Bạn thấy đấy giờ trong ViewController thì phần code cho dataSource của tableView khá là đẹp. Đôi khi bạn muốn viết đoạn gán dữ liệu cho cell vào một method khác trong class ViewController thay vì dùng block. Để cho những trường hợp đó như đã nói ở trên chúng ta có thể dùng selector. Tương tự như block chúng ta cũng sẽ tạo một @property (assign, nonatomic) SEL configureSelector; và đối tượng để execute method của selector này @property (weak, nonatomic) id target; (Đối tượng này chính là ViewController). Chúng ta cũng cần tạo một hàm khởi tạo datasource khác với selector. Cuối cùng trong phần gán dữ liệu cho cell chúng ta execute method của selector với objc_msgSend(self.target, self.configureSelector, cell, item);. Do phần này tương tự như đối với block nên mình không giải thích thêm mà các bạn có thể xem code trên github.

Tiếp theo còn một đoạn ... tại phần tạo cell mới khi mà không tìm thấy cell có thể dùng lại. Như bạn thấy đấy để tạo cell mới chúng ta cần biết Class của cell. Với Objective-C chúng ta có thể tạo 1 instance từ tên class. Khi đó chúng ta có thể tạo 1 cell như sau:

cell = [[NSClassFromString(CELL_CLASS_NAME) alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:self.cellIdentifier];

Như vậy class TVArrayDataSource chỉ cần có thêm thông tin là tên class của cell là mọi việc có thể hoàn tất. Ngoài ra nhiều khi chúng ta muốn tạo cell từ file Xib. Để tạo cell từ file xib chúng ta cũng chỉ cần biết thêm tên file xib. Thế nên mình tạo thêm một property cellName để lưu tên class của cell hoặc tên file Xib tuỳ theo trường hợp cell tạo từ file xib hay từ code. Như vậy việc tạo class TVArrayDatasource đã hoàn thành. Và bây giờ trong ViewController chúng ta chỉ implement đoạn code ngắn như sau: Khi sử dụng với block

ViewController.m
1
2
3
4
5
6
7
8
9
10
// tạo block
TVCellConfigureBlock configureCell = ^(CELL_CLASS_NAME *cell, DATATYPE *name) {
  [cell.title setText:name];
};

dataSource = [[TVArrayDataSource alloc] initWithItems:items
                                       cellIdentifier:@"MYCELL"
                                   cellConfigureBlock:configureCell];
[dataSource setXibFileName:@"XibFileName"];
tableview.datasource = dataSource;

Hoặc khi sử dụng với selector.

ViewController.m
1
2
3
4
5
6
7
8
9
10
11
12
13
dataSource = [[TVArrayDataSource alloc] initWithItems:items
                                       cellIdentifier:@"MYCELL"
                                               target:self
                                     cellConfigureSel:@selector(configureCell:andItem:)];
[dataSource setCellClassName:@"CELL_CLASS_NAME"];
tableView.dataSource = dataSource;


// selector
- (void)configureCell:(CELL_CLASS_NAME *)cell andItem:(DATA_TYPE *)item
{
    [cell.title setText:item];
}

Tổng kết

Bài viết trình bày về cách tạo class datasource riêng cho tableView thay vì implement trực tiếp trong ViewController. Điều này sẽ giúp ViewController ngắn gọn hơn và code trông đẹp hơn, cũng như tăng khả năng sử dụng lại code. Chúng ta có thể dùng lại class TVArrayDataSource tại nhiều ViewController mà không cần phải implement lại các hàm delegate của TableViewDataSource. Thế nhưng hiện tại class này chỉ dùng cho những tableview có 1 section. Toàn bộ code của class này cũng như sample bạn có thể tham khảo tại: https://github.com/ktmt/TVDataSource

Hoặc để sử dụng class này bạn có thể cài qua coccoapod bằng cách thêm pod 'TVArrayDataSource' vào Podfile.

Copyright © 2015 kỹ thuật máy tính