AncestorKit 0.3.0

AncestorKit 0.3.0

测试已测试
Lang语言 Obj-CObjective C
许可证 MIT
发布最新发布2015年3月

Zach Radke 维护。



  • Zach Radke

受 2014 年 WWDC 会话 '使用集合视图的高级用户界面,' 的启发,AncestorKit 提供了一种轻松创建基于实例的属性值继承的方法。利用它,您可以创建模型或配置对象,其属性值会逐级向下传递给后代。请相信我,当您看到实际应用时,它会更有意义。

使用

子类 AKAncestor

例如,假设我们正在构建一个家谱

@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 文件。