Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add shorthand syntax for KVC #31

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

NSExceptional
Copy link
Contributor

@NSExceptional NSExceptional commented Jan 22, 2019

Problem

When dealing with ivars or private properties of foreign classes, we often find ourselves writing code like this:

if ([[[self valueForKey:@"_layerView"] valueForKey:@"_actionButtons"] containsObject:@2]) {
    ...
}

NSString *privateID = [self.page valueForKey:@"_id"];
[[self.page valueForKey:@"loadedViews"] addObject:...];
[self.page setValue:@NO forKey:@"_shouldShowAds"];

We can do a little better if we write an interface for whatever self.page is:

@interface XYPage : NSObject {
    @public
    BOOL _shouldShowAds;
    ActionLayerView *_layerView;
}

@property (readonly) NSString *_id;
@property (readonly) NSMutableArray *loadedViews;

@end

...

if ([[[self valueForKey:@"_layerView"] valueForKey:@"_actionButtons"] containsObject:@2]) {
    ...
}

NSString *privateID = self.page._id;
[self.page.loadedViews addObject:...];
[self.page setValue:@NO forKey:@"_shouldShowAds"];

That cleaned up two lines nicely, but doesn't help us at all with the ivars we need to access, because ivar access requires a class symbol to link against. We could try a little harder, either by using MSHookIvar or using %property to create a property to wrap _layerView, but then we have to do it again for _actionButtons, and any other ivar we come across, and so on. And it just gets worse when you have long chains of ivar accesses, which isn't uncommon in complex tweaks.

As you can see, this quickly becomes cumbersome. Our code is doomed to be littered with boilerplate everywhere. Isn't this something Logos should be able to do for us? Well, it is. And fortunately, it can!

Proposed solution

This PR adds a slim shorthand syntax for Key-Value Coding. Instead of using .dot syntax for properties that require an @interface, or -> dereferencing ivars, just use .% to access any private property or ivar. Here's our code from earlier:

if ([self.%_layerView.%_actionButtons containsObject:@2]) {
    ...
}

NSString *privateID = self.page.%_id;
[self.page.%loadedViews addObject:...];
self.page.%_shouldShowAds = @NO;

Isn't that so much better? (Additionally, you can use .%() syntax to provide any arbitrary value as the key)

What can I do to get this merged?

This is my first "real" contribution to Theos. There's almost no documentation on where anything should go or how the project should be structured, or what the conventions are. I did my best to write neat code and put things where I felt they belonged, but for all I know, I did everything wrong and I've reinvented the wheel with my helper methods (scanPattern, scanIdentifier, etc).

And finally, what are your thoughts on this idea in general?

@NSExceptional NSExceptional changed the title Add shorthand syntax for KVC. Add shorthand syntax for KVC Jan 28, 2019
foo.% = bar;
id foo = bar.%_baz;
Allow `foo.%(...)` so that keys can be supplied via objects or as the result of a function or method call. Additionally, replacement is done with ranges of text instead of regular expressions based on what to replace. This is much less error-prone.

Also fixes some critical bugs that were revealed by these additions.
@NSExceptional
Copy link
Contributor Author

@uroboro Thoughts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant