便利构造函数 |
既然要说便利构造函数(Convenience Initializer)就不得不先提起指定构造函数(Designated Initializer)。前者是Swift特有的概念,后者是OC和Swift共有的初始化对象的方式,形如:
//OC
- (instancetype)initWithXXXX {if (self = [super init]) {}return self;
}
//Swift
init(....) {//属性的初始化super.init()
}
需要明确注意的是:OC中,初始化的对象需要显示返回,并且当前对象属性的初始化需要在父类初始化后进行。而在Swift中,以上过程恰恰相反。(指定构造器中)
指定构造器正常情况下都会创建对象,而便利构造器会根据条件判断创建对象与否,对于不合理调用构造函数就可以做到减小内存开销问题。便利构造器本身不负责对象的创建,这就要求其内部必须调用当前类的指定构造器来实例化对象。所以如果要在便利构造函数中使用当前对象的属性,就需要在调用指定构造函数之后。此外,还可用extension给类扩展一个便利构造函数简化对象的创建。
NSString 属性为什么用copy 关键字 |
要说明这个观点的必要性就先说明为什么不用strong
和retain
。它们两者都是增加原对象的引用计数,来达到持有该对象的目的。在类的关系能正确对应的情况下,即NSString
对象 -> NSString
类型的属性。对,当遇上NSMutableString
时,情况就变糟糕了。此时使用strong
或者retain
,当原对象改变了之后,属性引用的对象也会随之改变。
copy
是不可变的复制。针对于不可变对象,此时的复制只是浅复制。如NSArray、NSDictionary、NSString、NSSet
。此时针对于被复制的对象本身,使用strong
和retain
也不会有什么问题。当被复制的对象是可变时,复制后虽然会产生新的对象,但集合内部的元素只是增加了它的引用计数而已,元素本身没有被复制。使用LLDB
的expression
判断元素相等,你会得到肯定的结果。就效率和安全性来说,使用copy
是毫无疑问的。
UICollectionView自定义约束实现 |
系统提供了一个约束来为UICollectionView
实现流式布局——UICollectionViewFlowLayout
,也就是网格布局。这能满足一些场景的需求,如果你想要更灵活的玩耍UICollectionView
,你就需要自定义约束类来添加各种有意思的约束,这个类是UICollectionViewLayout
,你需要实现如下几个方法:
// 开始时调用的方法,每次只执行一次,准备工作,不执行任何布局。一般在该方法中设定一些必要的layout的结构和初始需要的参数等。以下用官方提供的圆形布局作为讲解参考
// 调用顺序:1
- (void)prepareLayout {// 保证约束实例能正常工作[super prepareLayout];// UICollectionViewLayout持有使用它布局的UICollectionView实例CGSize size = self.collectionView.frame.size;// 获得当前单元格的数量_cellCount = [self.collectionView numberOfItemsInSection:0];// 圆环的圆心_center = CGPointMake(size.width / 2.0, size.height / 2.0);// 以UICollectionView宽高中较小值的2.5分之一作为圆环的半径_radius = MIN(size.width, size.height) / 2.5;
}// 确定collection应该占据的尺寸。注意这里的尺寸不是指可视部分的尺寸,而应该是所有内容所占的尺寸。collectionView是scrollView的子类,因此需要这个尺寸来配置滚动行为。在这个例子中,不需要滚动,就直接返回视图的尺寸。
// 2
- (CGSize)collectionViewContentSize {// NSLog(@"%f", self.collectionView.frame.size.height);return self.collectionView.frame.size;
}// 指定CGRect范围内的单元格大小和位置
// 3
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {NSMutableArray *attributes = [NSMutableArray array];// rect表示的是UICollectionView所有内容所占的矩形区域的尺寸,即在CollectionViewContentSize设定的尺寸for (NSInteger i = 0; i < _cellCount; i++) {NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];[attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];}return attributes;
}// 返回UICollectionViewLayoutAttributes控制指定的单元格大小和位置。
// 4
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];// cosf, sinf分别表示余弦函数,正弦函数attributes.size = CGSizeMake(ITEM_SIZE, ITEM_SIZE);attributes.center = CGPointMake(_center.x + _radius * cosf(2 * M_PI * indexPath.item / _cellCount), _center.y + _radius * sinf(2 * M_PI *indexPath.item / _cellCount));return attributes;
}// 当单元格动态开始显示出来时自动调用该方法(实例化对单元格添加的约束)
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {// 设置item最初的约束,系统会以动画的形式让item完成最终的约束UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];attributes.alpha = 0.0;attributes.center = CGPointMake(_center.x, _center.y);return attributes;}// 当单元格消失的时候自动调用
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {// 设置item消失后的约束,与开始相反,系统以动画形式让item从现在的约束过渡到消失后指定的约束UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];attributes.alpha = 0.0;attributes.center = CGPointMake(_center.x, _center.y);attributes.transform3D = CATransform3DMakeScale(0.1, 0.1, 1.0);return attributes;
}
哈希表 |
关于哈希表(hash table
)的概述或者定义这里就不阐述了,因为这样的文章比比皆是,这里主要记录一些哈希表的特性以及在iOS中如何让自定义对象可哈希化。
特性
众所周知的是objc
中的字典的底层实现就是哈希表,而它在数据结构上的表现与数组是类似的,即内存空间都是连续的,它的每一元素被称为箱(bin),箱中装的就是键值对。
因此它也具有快速查找的能力,在理想的哈希函数中,无论数据体量有多大,它的一次查询操作的时间复杂度均是O(1)
。它实际上会将字典中使用键(key)来找值转换成在数组中使用下标来查找值,所以这里就涉及到如果将键映射成索引下标。
其实,这个过程在理解上很简单:根据指定的规则,以给定的键来计算哈希值(hash value)h,这会是一个数量类型的值。它在哈希表中的位置会通过下面这个等式算出:
// n为哈希表中元素的个数
location = h % n
这个location
就相当于数组中元素的下标。
因此就不难得出,发生键值对冲突的原因:相同的键被插入到同一哈希表中。在objc
中使用字符串作为键时,发生这种情况的话,后者的插入将会无效,即结果只保留前者。一种名叫拉链法
的解决方案是将属于同一箱子的键值对依次排列形成链表。
在正常情况下一个箱子中就只会有一个键值对,即负载因子(load factor:等于键值对总数除于箱子数)为1。负载因子越大表示哈希表越满,越容易发生冲突,效率就越低。一般程序语言中会设置一个阈值,当负载因子大于这个值时,哈希表会自动扩容。此时所有键值对的存放位置都可能发生改变,这个过程叫做重哈希(rehash)。此时由于需要分配新的内存空间,并将之前的键值对赋值到新的内存中,性能会明显下降。因此指定容量创建字典是个很好的决策。
自定义对象哈希化
覆写类的这两个方法:
- (BOOL)isEqual:(id)object {
//自定义键的判等条件
}- (NSUInteger)hash {
//尽可能保证哈希值的唯一
}
一个判等的参考示例是这样的:
- (BOOL)isEqual:(id)object {if (!object) {return NO;}if (self == object) {return YES;}if (![object isKindOfClass:[XXX class]]) {return NO;}return [self isEqualToXXX:(XXX *)object];
}- (BOOL)isEqualToXXX:(XXX *)object {//判等条件
}
这样做了之后,你的自定义对象就可以作为字典的键了。
(于2017-8-8记)