内存管理策略

使用引用计数的内存管理的基本模型是由NSObject协议定义的一些方法与标准方法的命名约定所提供。NSObject类还定义了一个方法,当一个对象被释放时会自动调用dealloc方法,。本文介绍了您需要知道的在Cocoa程序中正确管理内存的基本规则,并提供了一些正确使用的例子。


实现dealloc方法来释放对象的使用权

原文:内存管理策略(Memory Management Policy)

引言(Introduction)


关于内存管理(About Memory Management)

应用程序的内存管理是程序在运行时分配内存的过程,运行它,使用完时将其释放。一个好的程序将使用尽可能小的内存。在Objective-C中,它也可以被视为多个数据和代码分配有限内存资源的所有权。当您学习完本指南时,你将有拥有所需要的管理你应用程序内存的知识,如清楚地知道如何管理对象的生命周期,并在它们不再使用时释放掉。

虽然内存管理通常在考虑一个单独的对象的层次,但是你的目标实际上是管理对象的整个周期图表。你要确保在内存中没有不需要的对象。

memory_management_2x.png


一眼看去

Objective-C提供了两种应用程序的内存管理方法。

  1. 在本指南中描述的方法,称为“手动保留释放”或MRR,您通过跟踪你的对象来明确地管理内存。这是用一个模型来实现,称为引用计数,结合运行时环境,由基础框架类NSObject所提供。
  2. 自动引用计数,或者ARC,该系统使用相同的引用计数系统MRR,但它会在编译期为你插入适当的内存管理方法调用。强烈建议在新项目中使用ARC。如果您使用的是ARC,通常不需要了解本文档中所描述的基本实现方法,尽管它可能在某些情况下是有用的。更多关于ARC,参考过渡到ARC释放笔记


良好的做法防止内存相关的问题

有2种主要的问题会产生不正确的内存管理

  • 释放或重写还仍在使用的数据
    这会导致内存损坏,通常会导致应用程序崩溃,或更糟糕的是,用户的数据被损坏。

  • 不再使用的数据未及时释放掉,导致内存泄漏
    内存泄漏是指已分配的却未被释放掉的内存,即便它再也不会被使用。内存泄漏将导致您的应用程序内存占用量不断增加,这反过来可能导致系统的性能不佳,您的应用程序可能会被关闭。

从引用计数(ARC)的角度来思考内存管理,往往适得其反,因为你倾向于考虑内存管理方面的实施细节,而不是你的实际目标。其实,你应该从对象所有权和对象图的角度来考虑内存管理。

Cocoa使用一个简单的命名规则来表示当你持有一个方法所返回的对象。

参考内存管理策略

基本策略看起来简单,然而有一些实用的步骤提供给,它们将使内存管理更容易,并帮助确保您的程序仍然是可靠和健壮的,同时,最大限度地减少所占用的资源。

参考实际的内存管理

自动释放池中提供了一种机制,你可以把一个对象的释放消息“推迟”。这是有用的情况下,你想放弃一个对象的所有权,但要避免它立刻释放(如你从一个方法返回一个对象时)。有些时候你可能会使用自己的自动释放池块。

参考使用自动释放池


使用分析工具来调试内存问题

查找代码在编译时的问题,你可以使用Xcode内部的Clang静态分析器。

很多工具和技术的阐述在技术文档TN2239中,而iOS调试魔法介绍关于使用NSZombie找到”过度释放”对象。

  • 如果内存管理问题仍然出现,有其他的工具和技术可以用来识别和诊断这些问题。

  • 您可以使用Instruments来追踪引用计数事件,并查找内存泄漏。参考收集你应用程序的数据

原文:引言(Introduction)

tableView的一些特性总结

一、属性

dataSource —设置UITableViewDataSource的代理
delegate —设置UItableViewDelegate的代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*当header、footer、cell的高度是固定值的话,用该方法直接设置表的高度,无需调用表的delegate方法设置它们的高度 eg:_table.rowHeight = 60 */
sectionHeaderHeight、sectionFooterHeight、rowHeight -- 设置表头、尾、cell的高度
sectionIndexColor -- 设置sectionIndexTitle(表索引子母)的颜色
[objc] view plain copy
@property (nonatomic) NSInteger sectionIndexMinimumDisplayRowCount; // show special section index list on right when row count reaches this value. default is 0
@property (nonatomic, strong, nullable) UIColor *sectionIndexColor NS_AVAILABLE_IOS(6_0) UI_APPEARANCE_SELECTOR; // color used for text of the section index
@property (nonatomic, strong, nullable) UIColor *sectionIndexBackgroundColor NS_AVAILABLE_IOS(7_0) UI_APPEARANCE_SELECTOR; // the background color of the section index while not being touched
@property (nonatomic, strong, nullable) UIColor *sectionIndexTrackingBackgroundColor NS_AVAILABLE_IOS(6_0) UI_APPEARANCE_SELECTOR; // the background color of the section index while it is being touched
估算元素的高度 // NS_AVAILABLE_IOS(7_0)
[objc] view plain copy
/*
连接文章 介绍了estimatedRowHeight的用的场景。(适用动态的cell.height的适配)
*/
理解iOS 8中的Self Sizing Cells和Dynamic Type
estimatedRowHeight --- 设置表格行的估算高度以改善性能
estimatedSectionHeaderHeight、estimatedSectionFooterHeight ----- 设置Section头和Section尾估算高度以改善性能
```objc
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(7_0);
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section NS_AVAILABLE_IOS(7_0);
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForFooterInSection:(NSInteger)section NS_AVAILABLE_IOS(7_0);
separatorEffect -------- 表的分割线(毛玻璃效果)//默认的分割线的色调暗

效果图:

1
2
3
4
5
6
UIImageView *backImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"4.jpg"]];
self.myTableView.backgroundView = backImageView;
UIBlurEffect *blureffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
UIVibrancyEffect *vinb = [UIVibrancyEffect effectForBlurEffect:blureffect];
self.myTableView.separatorEffect = vinb;
//cell.backgroundColor = [UIColor clearColor];

二、方法
初始化方法:
initWithFrame:———–设置表的大小和位置
initWithFrame:style———设置表的大小,位置和样式(组,单一)
setEditing:———-表格进入编辑状态,无动画
setEditing: animated:———表格进入编辑状态,有动画
reloadData—————刷新整个表视图
reloadSectionIndexTitles——–刷新索引栏
numberOfSections———–获取当前所有的组
numberOfRowsInSection:———获取某个组有多少行
rectForSection:———-获取某个组的位置和大小
rectForHeaderInSection:———获取某个组的头标签的位置和大小
rectForFooterInSection:———–获取某个组的尾标签的位置和大小
rectForRowAtIndex:———–获取某一行的位置和大小
indexPathForRowAtPoint————-点击某一个点,判断是在哪一行上的信息。
indexPathForCell:————获取单元格的信息
indexPathsForRowsInRect:———在某个区域里会返回多个单元格信息
cellForRowAtIndexPath:————-通过单元格路径得到单元格
visibleCells———–返回所有可见的单元格
indexPathsForVisibleRows——–返回所有可见行的路径

headerViewForSection:——–设置头标签的视图
footerViewForSection;———-设置尾标签的视图
UITableViewHeaderFooterView的使用说明

1
2
- (nullable UITableViewHeaderFooterView *)headerViewForSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
- (nullable UITableViewHeaderFooterView *)footerViewForSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
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
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
static NSString *HeaderIdentifier = @"header";
//表头、尾UITableViewHeaderFooterView的重用机制
//CustomHeaderView *myHeader = [tableView dequeueReusableHeaderFooterViewWithIdentifier:HeaderIdentifier];
if(!myHeader) {
// [tableView registerClass:[CustomHeaderView class] forHeaderFooterViewReuseIdentifier:HeaderIdentifier];
myHeader = [[[NSBundle mainBundle] loadNibNamed:@"CustomHeaderView"
owner:self
options:nil] objectAtIndex:0];
}
[myHeader.btnSomething setTitle:@"-" forState:UIControlStateNormal];
[myHeader.lblSomething setText:[NSString stringWithFormat:@"Section: %d",section]];
return myHeader;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomHeaderView *theHeaderView = (CustomHeaderView*)[tableView <span style="color:#FF0000;">headerViewForSection</span>:indexPath.section];
NSLog(@"%@",theHeaderView);
[theHeaderView.lblSomething setAlpha:theHeaderView.lblSomething.alpha-0.1];
[theHeaderView.btnSomething setTitle:@"+" forState:UIControlStateNormal];
}

重用机制(2个)

dequeueReusableCellWithI dentifier:——— 获取重用队列里的cell单元格

1
2
3
/**< 使用这个方法之前必须是使用了registerNib:forCellReuseIdentifier:<span>或者</span>registerClass:forCellReuseIdentifier:方法注册了Cell
*/
dequeueReusableHeaderFooterViewWithIdentifier -------获取重用队列里的UITableViewHeaderFooterView的单元格

注册一个包含指定标示符的cell or haderFooterView 的nib对象/类

1
2
- (void)registerNib:(nullable UINib *)nib forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(5_0);
- (void)registerClass:(nullable Class)cellClass forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
1
2
- (void)registerNib:(nullable UINib *)nib forHeaderFooterViewReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
- (void)registerClass:(nullable Class)aClass forHeaderFooterViewReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
beginUpdates and endUpdates两个方法,是配合起来使用的,标记了一个tableView的动画块。分别代表动画的开始开始和结束。两者成对出现,可以嵌套使用。
一般,在添加,删除,选择 tableView中使用,并实现动画效果。在动画块内,不建议使用reloadData方法,如果使用,会影响动画。
*/
调用范例
beginUpdates--------只添加或删除才会更新行数
endUpdates---------添加或删除后会调用添加或删除方法时才会更新
insertSections:withRowAnimation:-----------插入一个或多个组,并使用动画
insertRowsIndexPaths:withRowAnimation:-------插入一个或多个单元格,并使用动画
deleteSections:withRowAnimation:--------删除一个或多个组,并使用动画
deleteRowIndexPaths:withRowAnimation:--------删除一个或多个单元格,并使用动画
reloadSections:withRowAnimation:---------更新一个或多个组,并使用动画
reloadRowIndexPaths:withRowAnimation:-------------更新一个或多个单元格,并使用动画
moveSection:toSection:-------------移动某个组到目标组位置
moveRowAtIndexPath:toIndexPath:-----------移动个某个单元格到目标单元格位置
indexPathsForSelectedRow----------返回选择的一个单元格的路径
indexPathsForSelectedRows---------返回选择的所有的单元格的路径
selectRowAtIndexPath:animation:scrollPosition---------设置选中某个区域内的单元格
deselectRowAtIndexPath:animation:----------取消选中的单元格

UITableViewDataSource代理方法:

1
2
3
4
5
6
7
8
9
10
numberOfSectionsInTableView:------------设置表格的组数
tableView:numberOfRowInSection:----------设置每个组有多少行
tableView:cellForRowAtIndexPath:---------设置单元格显示的内容
tableView:titleForHeaderInSection:---------设置组表的头标签视图
tableView:titleForFooterInSection:-----------设置组表的尾标签视图
tableView:canEditRowAtIndexPath:---------设置单元格是否可以编辑
tableView:canMoveRowAtIndexPath:--------设置单元格是否可以移动
tableView:sectionIndexTitleForTableView:atIndex:-------设置指定组的表的头标签文本
tableView:commitEditingStyle:forRowAtIndexPath:----------编辑单元格(添加,删除)
tableView:moveRowAtIndexPath:toIndexPath ------- 单元格移动

tableView:indentationLevelForRowAtIndexPath ——- 返回行层次的深度
效果图:

1
2
3
4
5
6
7
8
9
10
11
/*
- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [indexPath row];
}
*/
//NS_AVAILABLE_IOS(8_0)
tableView:editActionsForRowAtIndexPath ----- 自定义左滑动的编辑功能样式(UITableViewRowAction
tableView:titleForDeleteConfirmationButtonForRowAtIndexPath ------ 改变默认左滑动出现的字样

eg:改变默认Delete 字为 下载 (return @“下载”;)

效果图:

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
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{ return YES;
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{ return UITableViewCellEditingStyleDelete;
}
-(NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewRowAction *layTopRowAction1 = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:@"删除" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
NSLog(@"点击了删除");
[tableView setEditing:NO animated:YES];
}];
layTopRowAction1.backgroundColor = [UIColor redColor];
UITableViewRowAction *layTopRowAction2 = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:@"置顶" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
NSLog(@"点击了置顶");
[tableView setEditing:NO animated:YES];
}];
layTopRowAction2.backgroundColor = [UIColor greenColor];
UITableViewRowAction *layTopRowAction3 = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:@"更多" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
NSLog(@"点击了更多");
[tableView setEditing:NO animated:YES];
}];
layTopRowAction3.backgroundColor = [UIColor blueColor];
NSArray *arr = @[layTopRowAction1,layTopRowAction2,layTopRowAction3];
return arr;
}

UITableViewDelegate代理方法:

在iOS8以上

1
2
3
tableView:willDisplayFooterView:forSection:,和tableView:willDisplayHeaderView:forSection:可以正常被调用
在iOS7(iOS6没测试)上却没有被调用
原来iOS7必须同时实现了Header和Footer这个delegate才会被调用所以
1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 0.f;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *view = [UIView new];
view.backgroundColor = [UIColor clearColor];
return view;
}
tableView: willDisplayHeaderView: forSection: -------- 设置当前section头的样式
tableView: willDisplayCell:forRowAtIndexPath:-----------设置当前的单元格

效果图:

1
2
3
4
5
6
7
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
cell.transform = CGAffineTransformMakeTranslation(320, 0);
[UIView animateWithDuration:indexPath.row*0.2 animations:^{
cell.transform = CGAffineTransformIdentity;
}];
}
1
2
3
4
5
6
7
tableView: heightForRowAtIndexPath:-----------设置每行的高度
tableView:tableViewheightForHeaderInSection:-----------设置组表的头标签高度
tableView:tableViewheightForFooterInSection:-------------设置组表的尾标签高度
tableView: viewForHeaderInSection:----------自定义组表的头标签视图
tableView: viewForFooterInSection: ----------自定义组表的尾标签视图
tableView:accessoryButtonTappedForRowWithIndexPath:-----------设置某个单元格上的右指向按钮的响应方法

效果图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
UIImage *image= [ UIImage imageNamed:@"man" ];
UIButton *button = [ UIButton buttonWithType:UIButtonTypeCustom ];
CGRect frame = CGRectMake( 0.0 , 0.0 , image.size.width , image.size.height );
button.frame = frame;
[button setBackgroundImage:image forState:UIControlStateNormal ];
button.backgroundColor = [UIColor clearColor ];
[button addTarget:self
action:@selector(accessoryButtonIsTapped:event:) forControlEvents:UIControlEventTouchUpInside];
- (void)accessoryButtonIsTapped:(id)sender event:(id)event{
NSSet *touches = [event allTouches];
UITouch *touch = [touches anyObject];
CGPoint currentTouchPosition = [touch locationInView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:currentTouchPosition];
if(indexPath != nil)
{
[self tableView:self.tableView accessoryButtonTappedForRowWithIndexPath:indexPath];
}
}
1
2
3
4
5
6
tableView:willSelectRowAtIndexPath:-----------获取将要选择的单元格的路径
tableView:didSelectRowAtIndexPath:-----------获取选中的单元格的响应事件
tableView: tableViewwillDeselectRowAtIndexPath:------------获取将要未选中的单元格的路径
tableView:didDeselectRowAtIndexPath:-----------获取未选中的单元格响应事件
NS_AVAILABLE_IOS(9_0)
remembersLastFocusedIndexPath -------- 使用Apple TV遥控器控制屏幕上的用户界面
1
2
3
4
5
- (BOOL)tableView:(UITableView *)tableView canFocusRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0);
- (BOOL)tableView:(UITableView *)tableView shouldUpdateFocusInContext:(UITableViewFocusUpdateContext *)context NS_AVAILABLE_IOS(9_0);
- (void)tableView:(UITableView *)tableView didUpdateFocusInContext:(UITableViewFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator NS_AVAILABLE_IOS(9_0);
- (nullable NSIndexPath *)indexPathForPreferredFocusedViewInTableView:(UITableView *)tableView NS_AVAILABLE_IOS(9_0);
cellLayoutMarginsFollowReadableWidth ------- 判断是否需要根据内容留有空白(Ipad)

原文

UITableView

TableView 的一些知识总结

TableViewCell注意点

  1. 每个TableViewCell里带一个contentView,cell的textLabel和imageView都是加载在contentView里的,所以以后要在cell上添加控件最好在contentView上添加,以免不必要的错误。

    contentView.png

  2. 创建自定义TableViewCell

    首先调用cellForRowAtIndexPath方法,如果是加载nib可以在ViewDidLoad中先注册nib文件的cell:

1
2
UINib * nib = [UINib nibWithNibName:NSStringFromClass([MZTableViewCell class]) bundle:nil];
[self.tableView registerNib:nib forCellReuseIdentifier:@"mzCellNib"];
然后从缓存池里查找
1
2
static NSString *reuseID = @"mzCellNib";
MZTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseID];
如果storyboard中有cell的话,设置storyboard中cell的重用标识,在缓存池中也能找到:

![storyboard设置cell.png](http://oc98nass3.bkt.clouddn.com/storyboard设置cell.png)

如果缓存池没有,storyboard里面也木有这样标识的cell的话就有两种方式来创建:

  • 1.创建一个继承自UITableViewCell的子类,比MZTableViewCell
1
2
3
if (cell == nil) {
cell = [[MZTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseID];
}
  • 2.也可以自己创建cel:从Nib里加载cell
1
2
3
4
if (cell == nil) {
//加载xib文件加载cell,方法放回的是一个数组,取最后一个对象
cell = [[[NSBundle mainBundle loadNibNamed:NSStringFromClass([MZTableViewCell class]) owner:nil options:nil] lastObject];
}

注意 如果nib,storyboard和.m文件中同时存在自定义的cell的话,需要注意cell的重用标识和代码的执行顺序,这里贴一张view的生命周期:

viewLifeCycle.png

TableViewController注意点

  1. TableViewController中自带了一个tableView,继承了代理和数据源协议。其中的self.view和self.tableView都指的是所自带的tableView。
  2. TableViewController还有一个UIRefreshControl,可以用来实现下拉刷新的效果。

UIRefreshControl使用非常简单,但是必须是在UITableViewController子类使用,而不能在UIViewController子类中使用。例如CustomViewController继承自UIViewController,那么就不能使用UIRefreshControl。

UIRefreshControl使用很简单,如下代码,RootTableViewController继承自UITableViewController,

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
//RootTaleiewController.h file
@interface RootTableViewController:UITableViewController
{
}
@end
//RootTableViewController.m file
@interface RootTableViewController()
@end
@implementation RootTableViewController
//省略不相干代码
- (void)viewDidLoad
{
[super viewDidLoad];
//初始化UIRefreshControl
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:@selector(refresh:) forControlEvents:UIControlEventValueChanged];
[self setRefreshControl:refreshControl];
}
/*
解释一下下面的代码:
当用户向下下拉刷新的时候,refresh触发,这时候请求url链接中的内容。这里使用AFNetworking来解析,代码块中的内容就是解析成功之后,设置数据源self.tweets中的内容,然后刷新UITableView界面,然后向UIRefreshControl对象发送endRefreshing消息,停止UIRefreshControl的动画效果。如果失败的话,也要停止UIRefreshControl的动画效果。
*/
- (void)refresh:(id)sender
{
NSURL *url = [NSURL URLWithString:@"http://search.twitter.com/search.json?q=ios%20development&rpp=100&include_entities=true&result_type=mixed/"];
// Initialize URL Request
NSURLRequest *urlRequest = [[NSURLRequest alloc] initWithURL:url];
// JSON Request Operation
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:urlRequest success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSArray *results = [(NSDictionary *)JSON objectForKey:@"results"];
if ([results count]) {
self.tweets = results;
// Reload Table View
[self.tableView reloadData];
// End Refreshing
[(UIRefreshControl *)sender endRefreshing];
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
// End Refreshing
[(UIRefreshControl *)sender endRefreshing];
}];
// Start Operation
[operation start];
}
@end

另外在iOS6和iOS7上面,效果是不同的,在iOS6效果如下图:

iOS6_RefreshControl

在iOS7运行效果如下图所示:

iOS7_RefreshControl

UITableView的referance

1
@protocol UITableViewDelegate<NSObject, UIScrollViewDelegate>
这里是展示cell的时候的回调
1
2
3
4
5
6
7
8
9
10
@optional
// Display customization 这里是展示cell的时候的回调
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView willDisplayFooterView:(UIView *)view forSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView didEndDisplayingHeaderView:(UIView *)view forSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView didEndDisplayingFooterView:(UIView *)view forSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
设定高度

(注意estimatedHeightForRowAtIndexPath方法)

1
2
3
4
5
6
7
8
9
10
// Variable height support
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;
// Use the estimatedHeight methods to quickly calcuate guessed values which will allow for fast load times of the table.
// If these methods are implemented, the above -tableView:heightForXXX calls will be deferred until views are ready to be displayed, so more expensive logic can be placed there.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(7_0);
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section NS_AVAILABLE_IOS(7_0);
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForFooterInSection:(NSInteger)section NS_AVAILABLE_IOS(7_0);
Section的头尾
1
2
3
4
// Section header & footer information. Views are preferred over title should you decide to provide both
- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section; // custom view for header. will be adjusted to default or specified header height
- (nullable UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section; // custom view for footer. will be adjusted to default or specified footer height
Selection选择事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Selection
// -tableView:shouldHighlightRowAtIndexPath: is called when a touch comes down on a row.
// Returning NO to that message halts the selection process and does not cause the currently selected row to lose its selected look while the touch is down.
- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView didUnhighlightRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0);
// Called before the user changes the selection. Return a new indexPath, or nil, to change the proposed selection.
- (nullable NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath;
- (nullable NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(3_0);
// Called after the user changes the selection.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(3_0);
编辑状态
1
2
3
4
5
6
7
8
9
10
11
12
13
// Editing
// Allows customization of the editingStyle for a particular cell located at 'indexPath'. If not implemented, all editable cells will have UITableViewCellEditingStyleDelete set for them when the table has editing property set to YES.
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath;
- (nullable NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(3_0) __TVOS_PROHIBITED;
- (nullable NSArray<UITableViewRowAction *> *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(8_0) __TVOS_PROHIBITED; // supercedes -tableView:titleForDeleteConfirmationButtonForRowAtIndexPath: if return value is non-nil
// Controls whether the background is indented while editing. If not implemented, the default is YES. This is unrelated to the indentation level below. This method only applies to grouped style table views.
- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath;
// The willBegin/didEnd methods are called whenever the 'editing' property is automatically changed by the table (allowing insert/delete/move). This is done by a swipe activating a single row
- (void)tableView:(UITableView*)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath __TVOS_PROHIBITED;
- (void)tableView:(UITableView*)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath __TVOS_PROHIBITED;

Calling sequence for reordering a row in a table view.png
  上图中当tableView进入到edit模式的时候,tableView会去对当前可见的cell逐个调用dataSource的tableView:canMoveRowAtIndexPath:方法(此处官方给出的流程图有点儿问题),决定当前cell是否显示reoedering控件,当开始进入拖动cell进行拖动的时候,每滑动过一个cell的时候,会去掉用delegate的tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath:方法,去判断当前划过的cell位置是否可以被替换,如果不行则给出建议的位置。当用户放手时本次reordering操作结束,调用dataSource中的tableView:moveRowAtIndexPath:toIndexPath:方法更新tableView对应的数据。

 当tableView进入编辑模式以后,cell上面显示的delete还是insert除了跟cell的editStyle有关,还与 tableView的delegate的tableView:editingStyleForRowAtIndexPath:方法的返回值有关(在这里唠叨一句,其实delegate提供了很多改变cell属性的机会,如非必要,还是不要去实现这些方法,因为执行这些方法也造成一定的开销)。

  delete和insert的流程如下苹果官方文档中给出的图所示:
Calling_sequence_for_inserting_or_deleting_rows_in_a_table_view.png

定义长按cell的copy和paste
1
2
3
4
5
// Copy/Paste. All three methods must be implemented by the delegate.
- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(5_0);
- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender NS_AVAILABLE_IOS(5_0);
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender NS_AVAILABLE_IOS(5_0);
设定cell的焦点

使用Apple TV遥控器控制屏幕上的用户界面 = =

1
2
3
4
5
// Focus
- (BOOL)tableView:(UITableView *)tableView canFocusRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0);
- (BOOL)tableView:(UITableView *)tableView shouldUpdateFocusInContext:(UITableViewFocusUpdateContext *)context NS_AVAILABLE_IOS(9_0);
- (void)tableView:(UITableView *)tableView didUpdateFocusInContext:(UITableViewFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator NS_AVAILABLE_IOS(9_0);
- (nullable NSIndexPath *)indexPathForPreferredFocusedViewInTableView:(UITableView *)tableView NS_AVAILABLE_IOS(9_0);
Data刷新 (刷新可见行,visibleCell,基于cell重用机制)
1
2
3
// Data
- (void)reloadData; // reloads everything from scratch. redisplays visible rows. because we only keep info about visible rows, this is cheap. will adjust offset if table shrinks
- (void)reloadSectionIndexTitles NS_AVAILABLE_IOS(3_0); // reloads the index bar.
行所对应的视图关系和坐标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@property (nonatomic, readonly) NSInteger numberOfSections;
- (NSInteger)numberOfRowsInSection:(NSInteger)section;
- (CGRect)rectForSection:(NSInteger)section; // includes header, footer and all rows
- (CGRect)rectForHeaderInSection:(NSInteger)section;
- (CGRect)rectForFooterInSection:(NSInteger)section;
- (CGRect)rectForRowAtIndexPath:(NSIndexPath *)indexPath;
- (nullable NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point; // returns nil if point is outside of any row in the table
- (nullable NSIndexPath *)indexPathForCell:(UITableViewCell *)cell; // returns nil if cell is not visible
- (nullable NSArray<NSIndexPath *> *)indexPathsForRowsInRect:(CGRect)rect; // returns nil if rect not valid
- (nullable __kindof UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath; // returns nil if cell is not visible or index path is out of range
@property (nonatomic, readonly) NSArray<__kindof UITableViewCell *> *visibleCells;
@property (nonatomic, readonly, nullable) NSArray<NSIndexPath *> *indexPathsForVisibleRows;
- (nullable UITableViewHeaderFooterView *)headerViewForSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
- (nullable UITableViewHeaderFooterView *)footerViewForSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
滚动列表到某一行或者临近行
1
2
- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated;
- (void)scrollToNearestSelectedRowAtScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated;
行的插入、删除、刷新
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Row insertion/deletion/reloading.
- (void)beginUpdates; // allow multiple insert/delete of rows and sections to be animated simultaneously. Nestable
- (void)endUpdates; // only call insert/delete/reload calls or change the editing state inside an update block. otherwise things like row count, etc. may be invalid.
- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation NS_AVAILABLE_IOS(3_0);
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection NS_AVAILABLE_IOS(5_0);
- (void)insertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
- (void)deleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
- (void)reloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation NS_AVAILABLE_IOS(3_0);
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath NS_AVAILABLE_IOS(5_0);


参考博文

UITableView学习笔记

UIKit中有很多控件,UITableView是经常用到,注意点比较多,比较难用好的控件,这里将重点介绍UIView、UITableView、UICollectionView、的一些基本知识和注意点。

  1. UIView

  2. UITableView

  3. UICollectionView

做iOS开发的需要学习内存管理,有一些概念在工作和面试中常常遇到:什么是深拷贝,什么是浅拷贝? mutableCopycopy有什么区别? 为啥NSString通常是copy?感觉网上一些资料讲的不是很清楚,这里说明几点:


一、 什么是浅拷贝、深拷贝?

A: 简单理解就是,浅拷贝是拷贝了指向对象的指针, 深拷贝不但拷贝了对象的指针,还在系统中再分配一块内存,存放拷贝对象的内容。


二、 如何判断浅拷贝、深拷贝?

A: 深浅拷贝取决于拷贝后的对象的是不是和被拷贝对象的地址相同,如果不同,则产生了新的对象,则执行的是深拷贝,如果相同,则只是指针拷贝,相当于retain一次原对象, 执行的是浅拷贝.

此处上码:

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
    NSString *str1 = [NSString string];
    NSArray *arr1 = [NSArray array];
    
    NSMutableString *mStr1 = [NSMutableString string];
    NSMutableArray *mArr1 = [NSMutableArray array];
    
    NSLog(@"-------------这是浅拷贝---未产生新对象----------\n\n");
    NSLog(@"-------------不可变对象使用copy------------");
    NSString *testStr1 = [str1 copy];
    NSArray *testArr1 = [arr1 copy];
    NSLog(@"str1: %p, testStr1 : %p", str1, testStr1);
    NSLog(@"arr1: %p, testArr1 : %p", arr1, testArr1);
    NSLog(@"\n\n");
    NSLog(@"-------------这是深拷贝---产生了新对象----------\n\n");
    NSLog(@"-----------不可变对象使用mutableCopy----------");
    NSMutableString *testmStr1 = [str1 mutableCopy];
    NSMutableArray *testmArr1 = [arr1 mutableCopy];
    NSLog(@"str1: %p, testmStr1 : %p", str1, testmStr1);
    NSLog(@"arr1: %p, testmArr1 : %p", arr1, testmArr1);
    NSLog(@"-------------可变对象使用copy-------------");
    NSString *testStr2 = [mStr1 copy];
    NSArray *testArr2 = [mArr1 copy];
    NSLog(@"mStr1: %p, testStr2 : %p", mStr1, testStr2);
    NSLog(@"mArr1: %p, testArr2 : %p", mArr1, testArr2);
    
    NSLog(@"-------------可变对象使用mutableCopy-----------");
    NSMutableString *testmStr2 = [mStr1 mutableCopy];
    NSMutableArray *testmArr2 = [mArr1 mutableCopy];
    NSLog(@"mStr1: %p, testmStr2 : %p", mStr1, testmStr2);
    NSLog(@"mArr1: %p, testmArr2 : %p", mArr1, testmArr2);

运行结果:

深浅拷贝1.png

其实常用的Foundation框架类型,比如NSString,浅拷贝,也就是木有产生新对象的情况,只有一种,就是不可变对象使用copy操作,借用网上的一张图:
深浅拷贝2.png

其他的比如NSArrayNSDictionary类似,可以参考 汉斯哈哈哈的文章


三、为啥NSString通常用copy?什么时候用strong?

A: 因为在使用NSString的时候,一般如果初始化后不想改变这个NSString的值,应该将其类型设为copy,如果用strong类型,比如NSStringNSMutableString赋值,指向其地址,当NSMutableString的值被改变时,这个NSString值也发生了变化,如果是copy修饰的NSString对象,在用NSMutableString给他赋值时,会进行深拷贝,即把其内容也给拷贝了一份,两者指向不同的位置,即使改变了NSMutableString的值,NSString的值也不会改变.

上码:

1
2
3
4
5
6
7
8
9
10
11
@property (nonatomic, strong) NSString *aStrongStr;
@property (nonatomic, copy) NSString *aCopyStr;
    NSMutableString *mStr = [NSMutableString stringWithString:@"hello"];
    self.aStrongStr = mStr;
    self.aCopyStr = mStr;
    NSLog(@"self.aStrongStr = %@", self.aStrongStr);
    NSLog(@"self.aCopyStr = %@", self.aCopyStr);
    
    [mStr appendString:@" world!"];
    NSLog(@"self.aStrongStr = %@", self.aStrongStr);
    NSLog(@"self.aCopyStr = %@", self.aCopyStr);

结果:

NSString定义copy.png


四、mutableCopy返回的是可变对象,copy返回的是不可变对象。


1
2
3
NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithObjects:@1, @2, nil];
NSMutableArray *array = [mutableArray copy];
[array addObject:@3];

虽然array定义的是NSMutableArray, 其实是copy方法返回的一个不可变的数组,所以在执行addObject的方法会报错。

如果有误,欢迎拍砖~

参考文章

iOS 浅谈:深.浅拷贝与copy.strong

关于Objective-C

Objective-C是一门编写OS XiOS上软件的初级语言。它是C语言的子集,并且提供了面向对象的能力和动态的运行时。Objective-C继承了C的语法,基本类型,并遵循了C的控制语句(control statements),并且为定义类和方法添加了新的语法。Objective-C也添加了对对象图文在语言层面的支持,提供了动态类型和绑定,让很多任务在运行时中处理。


一眼看去

这份文档包含了Objective-C语法并且提供了大量的应用示例。你将学会如何创建类来描述你的自定义对象,还有如何使用Cocoa, Cocoa Touch框架提供的一些类。虽然这些框架类和这门语言是分离的,但框架类的使用深深地植入在Objective-C编程中,并且很多语言功能特性依赖于框架类提供的方法。


从一堆对象中搭建起来的应用

当开发OS XiOS应用的时候,你将把大部分时间花在对象上。这些对象就是Objective-C类的实例。有一些是有由CocoaCocoa Touch提供的,而有一些则将由你自己编写。

如果你正在写你自己的类,先提供这个类的描述,估计该类实例对象所公开的细节。接口包含了封装相关数据的公有属性和方法。方法的声明隐示着一个对象可以接收哪种消息,并包括在调用方法时所需的参数的信息。您还得提供该类实现代码,包括在类接口中所声明方法的可执行代码。

关联章节: 定义类, 运用对象, 封装数据


拓展现有类的类

在现有的类上提供一点额外添加的功能,与其创建一整个新的类,不如在现有类上定义一个类别去添加自定义功能。你可以在任意类上使用类别添加任何方法,包括你没有其实现源代码的类,如框架类像NSString.

关联章节: 自定义现有的类


定义消息合同的协议

Objective-C应用中,大部份工作的发生是对象之间互相发送消息的结果。通常这些消息是清晰地定义在类接口的方法中。然而有时候,定义一组不直接绑到一个特定的类的相关方法是很有帮助的。

Objective-C运用协议定义一组相关的方法,如在对象里称为委托的方法,方法是可选或着必要的。任何类都可以表示它采用了一个协议,这意味着它还必须提供协议中所有必要的方法的实现。

关联章节: 运用协议


值和集合通常代表Objective-C对象

使用CocoaCocoa Touch类来代表各种值在Objective-C中很常见,NSString类用于字符串,NSNumber类用于不同类型的号码,如整数或浮点数,NSValue类则用于其他结构如C的结构体。你也可以使用任何由C语言定义的基本类型,如int,float或字符。
集合通常表示为一个集合类的实例,如NSArrayNSSet,或NSDictionary,用来收集其他的Objective-C对象。

关联章节: 值和集合


简化常见任务的Block

块是CObjective-CC++的一个引入的语言特性,代表一个任务单位;它封装了一个代码块及其状态,类似于其他的编程语言中的闭包。块通常用来简化常见的任务,如集合枚举、排序和测试。块也更容易地使用Grand Central Dispatch(GCD)技术并发或异步执行任务。

关联章节: 运用Block


用于运行时问题的Error对象

虽然Objective-C包括异常处理的语法,但是CocoaCocoa Touch只在编译错误(如数组越界访问)报异常,这是应用程序在上线前应该解决的问题。
所有其他错误,包括运行时的问题,如磁盘空间用尽或无法访问Web服务则用NSError的实例对象来表示。您的应用程序应该预计可能出现的错误,并计划如何最好地处理它们,以呈现最佳的用户体验。

关联章节: 处理错误


Objective-C代码遵循既定的惯例

编写Objective-C代码时,你应该记住一些既定的编码惯例。例如,方法名以小写字母开头,用驼峰命名法的情况下多个词;例如,doSomethingdoSomethingElse。这不只是大写的重要性,你还应该确保你的代码尽可能的可读,这意味着方法名称应该表达清楚,但不要太罗嗦。

此外,如果你想利用语言或框架的功能,有一些惯例是必需的。例如,存储属性的方法,必须遵循严格的命名规范,以适用于键值编码(KVC)或键值观察(KVO)技术。

关联章节: 惯例


先决条件

如果你是OS XiOS开发新手,你可以阅读本文档之前先阅读今天着手开发iOS应用程序今天着手开发Mac应用程序,对iOSOS X此外应用开发过程有个大致的概念。特别地,你应该通过练习大部分章节末尾的作业来熟悉Xcode。Xcode是用来为iOSOS X的构建应用程序的IDE;你会用它来编写你的代码,设计你应用程序的界面,测试你的应用程序,并调试任何出现的问题。
虽然用CC的基础语言如javaC#有些熟悉的是更好的,这个文档不包括C语言基本特性的内联的例子,如流程控制语句。如果你有一个高层次的编程语言知识,如RubyPython,你应该可以跟循本文内容。
(文档)适度描述了一般的面向对象编程规则,尤其是当它们应用在Objective-C语言环境中,但本文假定你至少有一点点熟悉面向对象的基本概念。如果你还不熟悉这些概念,你应该阅读在Objective-C编程概念的相关章节。


参考

本文档中的内容采用Xcode 4.4或更高版本,假设你的目标是不是OS X v10.7或迟或iOS 5或更高版本。关于Xcode的更多信息,参见Xcode概述。关于语言功能特性的信息,参考Objective-C的功能特点可用性索引

Objective-C程序使用引用计数来确定对象的生命周期。在大多数部分中,编译器的自动引用计数(ARC)功能为您的。如果您无法利用ARC,或需要转换或维护传统的代码,管理一个对象的内存,你应该阅读先进的内存管理编程指南

除了编译器,Objective-C使用运行时系统,使其具有动态的、面向对象的功能。通常你不需要担心Objective-C是如何工作的,直接与运行时系统进行交互也是可以的,如文档Objective-C编程指南Objective-C运行时参考所描述的。


(我的本篇总结)

别看这是开篇,中文看起来就这么点,我借用工具还是花了很多时间去校对的,因为有些句子可以翻译成多种含义,有时候需要考虑哪一种意思表达的最清楚贴切,对自己的翻译思维确实有很大的帮助。可以看出Apple的文档入门指南还是为新手考虑很多的。我觉得这个系列还是比较重要的.

原文:Programming with Objective-C

自己的英文还是高中的基础,其实还是可以读文档的,以前也尝试翻译过几篇英文文章,但是对于Apple提供的长长的文档,我总是有种畏惧感,想回去网上查找中文的资料。可惜的是,花了时间找到这些资料的解读往往不是很全,读完不很甚解。细想觉得这是抱着金矿哭穷的做法,我决定尽自己的能力翻译一篇,也是提高英文的机会。由于是初次翻译这么长的,肯定有很多纰漏,希望大家在评论中多多指正,为以后读者减少误导,互相进步。

使用Objective-C编程(Programming with Objective-C)


引言(Introduction)
定义类(Defining Classes)
运用对象(Working with Objects)
封装数据(Encapsulating Data)
自定义现有的类(Customizing Existing Classes)
运用协议(Working with Protocols)
值和集合(Values and Collections)
运用Block(Working with Blocks)
处理错误(Dealing with Errors)
管理(Conventions)

原文:Programming with Objective-C

我觉得EffectiveOC(暂且简称为EffectiveOC)这本书主要针对iOS开发中的一些常见问题和需要注意的地方供一些建议,但是每个点讲的比较深入,不仅告诉你这样做,并且从底层上讲解why,从讲解的why上我对OC有了更多的理解,这里做一点分享,也算做懒人的一种笔记吧.

EffectiveOC封面


一、 EffectiveOC目录

  • 优化Objective-C对象之间的互动与关系.
  • 掌握接口与API的设计原则,写出令开发者用起来得心应手的类.
  • 善用协议与分类,编写便于维护且不易出现bug的代码.
  • 在自动引用计数(ARC)环境下避免内存泄漏.
  • 用”块”与”大中枢派发”编写呈模块化且功能强大的代码.
  • 理解Objective-C中的协议与其他编程语言中的多重继承有何区别,并掌握协议的用法.
  • 通过数组、字典、集合等组合对象来提高代码性能.
  • 揭示CocoaCocoa Touch框架的强大之处.


二、 进一步熟悉内存机制


1
NSString *str = [[NSString alloc] initWithString:@"This is a string"];

常说的实例对象其实是指向对象内存地址的指针。

实例对象.png


三、 命名规范

OC的方法名可能很长,但是是为了避免歧义,在命名方面,先要保证表达清楚,没有歧义,然后再考虑长度优化。


四、 进一步理解消息转发机制

OC是一门极其动态语言,在编译器定义好的方法在运行期系统会查找、调用某方法的实现代码,才能真正确定所调用的方法,如果类无法立即响应某个Selector,就会启动消息转发流程。

1. objc_msgSend传递消息

objc_msgSend

1
id returnValue = [someObject messageName: parameter];

消息传递调用的核心函数叫做objc_msgSend,编译器会把刚才的方法转换成:

1
id returnValue = objc_msgSend(someObject, @selecor(messageName:), parameter);

objc_msgSend()方法中,主要通过以下步骤来查找和调用函数:

  • 根据对象obj找到对象类中存储的函数列表methodLists

  • 再根据SEL@selector(doSth)在methodLists中查找对应的函数指针method_imp

  • 根据函数指针method_imp调用响应的函数。

old_method_list结构体:

1
2
3
4
5
6
struct old_method_list {
void *obsolete; //废弃的属性
int method_count; //方法的个数
/* variable length structure */
struct old_method method_list[1]; //方法的首地址
};

old_method结构体:

1
2
3
4
5
struct old_method {
SEL method_name; //函数的SEL
char *method_types; //函数的类型
IMP method_imp; //函数指针
};
  • obj->isa(Class类型) obj对象通过isa属性拿到对应的Class
  • Class->methodLists(old_method_list类型) Class通过methodLists属性拿到存放所有方法的列表
  • old_method_list->old_method 在old_method_list中通过SEL查找到对应的old_method
  • old_method->method_imp(IMP类型) old_method通过method_imp属性拿到函数指针

  • method_imp->调用函数 通过函数指针调用函数
    objc_msgSend函数会根据接受者和Selector的类型来调用适当的方法,如果找到与Selector名称相符的方法名,就跳转到该方法的实现代码,如果没有就沿着继承体系继续向上查找,如果还是找不到,就执行消息转发。
    (流程图)


2. 消息转发
  • 2.1 “动态方法解析”(dynamic method resolution) 查看所属的类是否能动态添加方法,已处理当前的未知选择子(unknown selector).
  • 2.2 “完整的消息转发机制”(full forwatding mechanism)请接受者看看有没有其他对象能处理这个消息,如果可以就把消息转发给那个对象,如果没有”备援接受者”(replacement receiver)则启动完整的消息转发机制,运行期系统会把消息有关的全部细节封装到NSInvocation对象中,给receiver最后一次机会,设法解决这条未处理的消息.

消息转发

Selector是方法选择器,里面存放的是方法的名字。对应方法的映射列表。
objc_msgSend函数会一句及守着与Selector的类型来调用适当的方法,他会在方法接受者所属类中搜寻方法列表,如果找到了与Selector名称相符的方法。


3. Method Swizzing

使用method_exchangeImplemetations(originalMethod, swappedMethod);实现运行时的Selector交换

methodSwizzing


五、 类和元类


1. 理解类对象

比起类,可能对象的概念更熟悉一点,这是对象的定义:
对象的结构体
你会发现有一个定义成Class类型的isa,这是实例对象用以表明其所属类型的,指向Class对象的指针。通过Class搭建了类的继承体系(class hirerarchy)。

其实类也是对象,打开定义的头文件,发现是用一个结构体来存储类的信息。

类的结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct objc_class *Class;
struct objc_class {
Class isa; // 指向metaclass
Class superclass; // 指向父类Class
const char *name; // 类名
uint32_t version; // 类的版本信息
uint32_t info; // 一些标识信息,标明是普通的Class还是metaclass
uint32_t instance_size; // 该类的实例变量大小(包括从父类继承下来的实例变量);
struct old_ivar_list *ivars; //类中成员变量的信息
struct old_method_list **methodLists; 类中方法列表
Cache cache; 查找方法的缓存,用于提升效率
struct old_protocol_list *protocols; // 存储该类遵守的协议
}

类的结构体存放着该类的信息:类的方法列表,实例变量,协议,父类等信息。
每个类的isa指针指向该类的所属类型元类(metaClass),用来表述类对象的数据。每个类仅有一个类对象,而每个类对象仅有一个与之相关的”元类”。
比如一个继承NSObjct名叫SomeClass的类,其继承体系如下:

类的继承体系

Objective-C中任何的类定义都是对象。即在程序启动的时候任何类定义都对应于一块内存。在编译的时候,编译器会给每一个类生成一个且只生成一个”描述其定义的对象”,也就是水果公司说的类对象(class object),它是一个单例(singleton).
因此,程序里的所有实例对象(instace objec)都是在运行时由Objective-C的运行时库生成的,而这个类对象(class object)就是运行时库用来创建实例对象(instance object)的依据。


2.查询类型信息

有时候会需要查询一个”objct”对象的所属的类,有人会这样写:

1
2
3
4
id objct = /* ... */
if ([objct class] == [SomeClass class]) {
//objct is an instance of SomeClass.
}

其实OC中提供了专门用于查询类型信息的方法,由于runtime在运行时的动态性,对于对象所属类的查询,建议使用isKindOfClassisMemberOfClass,因为某些对象可能实现了消息转发功能,从而判断可能不准确.


3. 理解元类(meta class)

为了调用类里的方法,类的isa指针必须指向包含这些类方法的类结构体。
这就引出了元类的定义:元类是类对象的类。
简单说就是:

当你给对象发送消息时,消息是在寻找这个对象的类的方法列表。
当你给类发消息时,消息是在寻找这个类的元类的方法列表。
元类是必不可少的,因为它存储了类的类方法。每个类都必须有独一无二的元类,因为每个类都有独一无二的类方法。


4.”元类的类”

元类,就像之前的类一样,它也是一个对象。你也可以调用它的方法。自然的,这就意味着他必须也有一个类。

所有的元类都使用根元类(继承体系中处于顶端的类的元类)作为他们的类。这就意味着所有NSObject的子类(大多数类)的元类都会以NSObject的元类作为他们的类

根据这个规则,所有的元类使用根元类作为他们的类,根元类的元类则就是它自己。也就是说基类的元类的isa指针指向他自己。


5.类和元类的继承

类用 super_class指针指向了超类,同样的,元类用super_class指向类的super_class的元类。

说的更拗口一点就是,根元类把它自己的基类设置成了super_class。

在这样的继承体系下,所有实例、类以及元类(meta class)都继承自一个基类。

这意味着对于继承于NSObject的所有实例、类和元类,他们可以使用NSObject的所有实例方法,类和元类可以使用NSObject的所有类方法

这些文字看起来莫名其妙难以理解,可以用一份图谱来展示这些关系:

类和元类


总结

  1. 任何直接或间接继承了NSObject的类,它的实例对象 (instacne object)中都有一个isa指针,指向它的类对象(class object)。这个类对象(class object)中存储了关于这个实例对象(instace object)所属的类的定义的一切:包括变量,方法,遵守的协议等等。
  2. NSObjectisa指针指向所述的类,而类对象(class object)的isa指针指向元类对象(metaClass object),类对象包含了类的实例变量、实例方法的定义,是用来描述该类的对象的信息;元类对象中包含了类的类方法的定义,是用来描述类的信息(类名,版本,类方法).
  3. 元类(meta class)是Class对象的类。每个类(Class)都有自己独一无二的元类(每个类都有自己第一无二的方法列表)。这意味着所有的类对象都不同。所有的元类使用基类的元类作为自己的基类,对于顶层基类的元类也是一样,只是它指向自己而已。


后续关注内容

  1. git教程

  2. Programming with Objective-C

参考阅读

Objective-C特性:Runtime

Effective Objective C 2.0

Objective-C Runtime

Objective-C 中的元类(meta class)是什么?