受 2014 年 WWDC 会话 '使用集合视图的高级用户界面,' 的启发,AncestorKit 提供了一种轻松创建基于实例的属性值继承的方法。利用它,您可以创建模型或配置对象,其属性值会逐级向下传递给后代。请相信我,当您看到实际应用时,它会更有意义。
例如,假设我们正在构建一个家谱
@interface Person : AKAncestor
@property (copy, nonatomic) NSString *firstName;
@property (copy, nonatomic) NSString *lastName;
- (NSString *)fullName;
@end
现在我们定义了一个类,我们可以创建一个祖先
Person *arthur = [Person new];
arthur.firstName = @"Arthur";
arthur.lastName = @"Weasley";
[arthur fullName]; // "Arthur Weasley"
从祖先,我们可以推导出后代
Person *bill = [arthur descendant];
bill.firstName = @"William";
[bill fullName]; // "William Weasley"
令人惊讶的是!因为我们没有定义 bill.lastName
,所以它继承了 arthur.lastName
的值。请注意,这仅适用于 对象属性。只要我们喜欢,我们就可以不断延续后代链。
Person *victoire = [bill descendant];
victoire.firstName = @"Victoire";
[victoire fullName]; // "Victoire Weasley"
现在,“哈利·波特与死亡圣器”强烈暗示维克托里娅·韦斯莱最终会与泰迪·洛夫林结婚。如果她决定采用他的姓,我们可以更新我们的家谱
victoire.lastName = @"Lupin";
[victoire fullName]; // "Victoire Lupin"
通过为 victoire.lastName
提供一个值,我们就阻止了从其后代继承值,就如同我们预期的那样!
尽管属性值继承是 AncestorKit 的目标,但有时您需要在一个特定的属性上禁用继承。
使用我们已有的 Person
模型
Person *tony = [Person new];
tony.firstName = @"Silvio";
tony.lastName = @"Ciccone";
[tony fullName]; // "Silvio Ciccone"
托尼·西科内有一个女儿
Person *madonna = [tony descendant];
madonna.firstName = @"Madonna";
[madonna fullName]; // "Madonna Ciccone"
麦当娜·西科内将放弃她的姓并成为流行歌手,因此我们需要我们的模型停止继承其姓
[madonna stopInheritingValuesForPropertyName:@"lastName"];
[madonna fullName]; // "Madonna"
太完美了!
假设您正在使用 AKAncestor
的子类来设置集合视图的区段缩进
@interface CollectionViewSectionAttributes : AKAncestor
// Interact with this property
@property (assign, nonatomic) UIEdgeInsets sectionInsets;
@end
@interface CollectionViewSectionAttributes (Private)
// This property acts as storage for the primitive sectionInsets property, and is inheritable
@property (strong, nonatomic) NSValue *sectionInsetsValue;
@end
如果您能找到一种方法将您的原始类型转换成对象,您就可以使用私有存储属性来实际存储数据,使它们有资格进行继承。当然,这意味着您需要自己为原始属性创建适当的getter和setter
@implementation CollectionViewSectionAttributes
- (UIEdgeInsets)sectionInsets
{
return (self.sectionInsetsValue) ? [self.sectionInsetsValue UIEdgeInsetsValue] : UIEdgeInsetsZero;
}
- (void)setSectionInsets:(UIEdgeInsets)sectionInsets
{
if (!UIEdgeInsetsEqualToEdgeInsets(self.sectionInsets, sectionInsets))
{
self.sectionInsetsValue = [NSValue valueWithUIEdgeInsets:sectionInsets];
}
}
- (NSSet *)keyPathsForValuesAffectingSectionInsets
{
return [NSSet setWithObject:@"sectionInsetsValue"];
}
@end
假设我们必须观察我们的 CollectionViewSectionAttributes
对象的 sectionInsets
属性
CollectionViewSectionAttributes *rootAttrs = [CollectionViewSectionAttributes new];
rootAttrs.sectionInsets = UIEdgeInsetsMake(15.0, 15.0, 15.0, 15.0);
...
CollectionViewSectionAttributes *sectionAttrs = [rootAttrs descendant];
[sectionAttrs addObserver:self forKeyPath:@"sectionInsets" options:0 context:nil];
尽管我们没有做任何额外的工作,但我们免费获得了继承的属性值的键值通知!所以例如
// We change the root section attributes
rootAttrs.sectionInsets = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0);
即使我们正在更改祖先,sectionAttrs
也会通知观察者其 sectionInset
已更新,就像我们预期的那样。同样,如果实例有自己的属性值,我们也不会收到幽灵通知。
// This will generate a notification
sectionAttrs.sectionInsets = UIEdgeInsetsMake(40.0, 0.0, 10.0, 0.0);
// This won't generate a notification on sectionAttrs now that sectionAttrs has its own sectionInsets value.
rootAttrs.sectionInsets = UIEdgeInsetsMake(12.0, 12.0, 12.0, 12.0);
如果您想避免使用更冗长初始化器和方法导致的开销,可以使用关键值编码来禁用此行为。
sectionAttrs = [[CollectionViewSectionAttributes alloc] initWithAncestor:rootAttrs inheritKeyValueNotifications:NO];
sectionAttrs = [rootAttrs descendantInheritingKeyValueNotifications:NO];
请注意,标准的 -init
方法与 +new
方法等效于通过传递 YES
调用 -initWithAncestor:inheritKeyValueNotifications:
,而 -descendant
和 -descendantOf:
方法将使用祖先的 inheritsKeyValueNotifications
属性。
如果我们回到之前定义的 Person
类,可以看出 firstName
作为可继承属性并没有太多意义,因为我们不像姓氏那样直接继承名字。所以,让我们将其从考虑范围中移除。
+ (NSSet *)propertiesPassedToDescendants
{
// Note that eventually it would be performant to cache this altered set.
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"propertyName != %@", @"firstName"];
return [[super propertiesPassedToDescendants] filteredSetUsingPredicate:predicate];
}
通过修改 +propertiesPassedToDescendants
,我们永久地从继承中移除了 firstName
属性。对 -resumeInheritingValuesForPropertyName:
的调用将没有效果。
Person *james = [Person new];
james.firstName = @"James";
james.lastName = @"Potter";
Person *harry = [james descendant];
harry.firstName; // nil
harry.lastName; // "Potter"
AncestorKit 使用 Objective-C 运行时来检查 AKAncestor
的子类,定位可以在实例中继承的属性,以及交换这些属性的 getter。对于大多数消费者来说,不必担心这一点,但如果您计划自己使用大量的运行时技巧,请务必注意,[AKAncestor load]
运作如下:
AKAncestor
的子类。+propertiesPassedToDescendants
消息。AKPropertyDescription
定义的 propertyGetter
在子类中进行交换。这意味着在 +propertiesPassedToDescendants
方法中应特别小心,以确保只返回有效的属性。由于这一切都是在 [AKAncestor load]
方法中发生的,这也意味着不支持运行时添加的属性和类,因为它们将在 AKAncestor
完成加载后被添加。
只有对象属性才合格继承。这是因为对象属性可以是 nil
,这表示没有值。AncestorKit 依赖于“无值”的概念来确定何时应搜索祖先以查找可能存在的值。这使得与原始类型一起工作变得困难甚至不可能,因为一个 BOOL
属性可以是 NO
,因为它尚未设置,或者有目的地设置为这种方式。尽管可以将块转换为 id
,但它们也不被认为是继承的合格对象。
找到了问题?认为某些内容需要阐明或改进?请随时在 GitHub 上创建问题!我特别有兴趣看到如何测试这些类在密集使用时的性能。
AncestorKit 适用于 MIT 许可。有关更多信息,请参阅 LICENSE 文件。