Skip to content

Commit

Permalink
Swizzle lazy getter
Browse files Browse the repository at this point in the history
  • Loading branch information
mikkelee committed Apr 24, 2013
1 parent b06d2d0 commit 8154fac
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 15 deletions.
8 changes: 8 additions & 0 deletions Gedcom.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
3E1C3E81151C319D005E09E1 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E1C3E69151C319C005E09E1 /* Cocoa.framework */; };
3E1C3E84151C319D005E09E1 /* Gedcom.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E1C3E66151C319C005E09E1 /* Gedcom.framework */; };
3E1C3E8A151C319D005E09E1 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3E1C3E88151C319D005E09E1 /* InfoPlist.strings */; };
3E2001E917287428004D5EE5 /* NSObject+MELazyPropertySwizzlingAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 3E2001E717287428004D5EE5 /* NSObject+MELazyPropertySwizzlingAdditions.h */; };
3E2001EA17287428004D5EE5 /* NSObject+MELazyPropertySwizzlingAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E2001E817287428004D5EE5 /* NSObject+MELazyPropertySwizzlingAdditions.m */; };
3E21E9B5160E88EB001EE004 /* GCChangeInfoAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = 3E21E9B3160E88EB001EE004 /* GCChangeInfoAttribute.h */; settings = {ATTRIBUTES = (Public, ); }; };
3E21E9B6160E88EB001EE004 /* GCChangeInfoAttribute.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E21E9B4160E88EB001EE004 /* GCChangeInfoAttribute.m */; };
3E29DB1A154A1D9600756CF4 /* GCDateAgeMathTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E29DB18154A1C3800756CF4 /* GCDateAgeMathTests.m */; };
Expand Down Expand Up @@ -562,6 +564,8 @@
3E1C3E7F151C319D005E09E1 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; };
3E1C3E87151C319D005E09E1 /* GedcomTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GedcomTests-Info.plist"; sourceTree = "<group>"; };
3E1C3E89151C319D005E09E1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
3E2001E717287428004D5EE5 /* NSObject+MELazyPropertySwizzlingAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+MELazyPropertySwizzlingAdditions.h"; sourceTree = "<group>"; };
3E2001E817287428004D5EE5 /* NSObject+MELazyPropertySwizzlingAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+MELazyPropertySwizzlingAdditions.m"; sourceTree = "<group>"; };
3E21E9B3160E88EB001EE004 /* GCChangeInfoAttribute.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCChangeInfoAttribute.h; sourceTree = "<group>"; };
3E21E9B4160E88EB001EE004 /* GCChangeInfoAttribute.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = GCChangeInfoAttribute.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
3E29DB18154A1C3800756CF4 /* GCDateAgeMathTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDateAgeMathTests.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1726,6 +1730,8 @@
3ED92D411563D76500E53E47 /* Delegate protocols */,
3ED92D3D1563C70500E53E47 /* GCXrefProtocol.h */,
3ED92D3E1563C70500E53E47 /* GCXrefProtocol.m */,
3E2001E717287428004D5EE5 /* NSObject+MELazyPropertySwizzlingAdditions.h */,
3E2001E817287428004D5EE5 /* NSObject+MELazyPropertySwizzlingAdditions.m */,
);
name = Helpers;
sourceTree = "<group>";
Expand Down Expand Up @@ -1959,6 +1965,7 @@
3E0C330017241EE90022E4D1 /* GCRecord.h in Headers */,
3E053D8E1726B70F0060A622 /* GCGedcomAccessAdditions.h in Headers */,
3E053D921726CD740060A622 /* GCObject+GCSwizzlingAdditions.h in Headers */,
3E2001E917287428004D5EE5 /* NSObject+MELazyPropertySwizzlingAdditions.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -2372,6 +2379,7 @@
3E41BFCC1725FBFA003286E7 /* GCWillAttribute.m in Sources */,
3E053D8F1726B70F0060A622 /* GCGedcomAccessAdditions.m in Sources */,
3E053D931726CD740060A622 /* GCObject+GCSwizzlingAdditions.m in Sources */,
3E2001EA17287428004D5EE5 /* NSObject+MELazyPropertySwizzlingAdditions.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
3 changes: 2 additions & 1 deletion Gedcom/GCChangeInfoAttribute.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@
#import <Foundation/Foundation.h>

#import "GCAttribute.h"
#import "NSObject+MELazyPropertySwizzlingAdditions.h"

/**
An attribute that holds information regarding the change history of an entity.
*/
@interface GCChangeInfoAttribute : GCAttribute
@interface GCChangeInfoAttribute : GCAttribute <MELazyPropertySwizzling>

#pragma mark Objective-C properties

Expand Down
16 changes: 2 additions & 14 deletions Gedcom/GCChangeInfoAttribute.m
Original file line number Diff line number Diff line change
Expand Up @@ -85,22 +85,11 @@ - (void)encodeWithCoder:(NSCoder *)aCoder

#pragma mark Objective-C properties

- (NSDate *)lazyModificationDate
- (NSDate *)_lazyModificationDate
{
return dateFromNode(_lazyModificationDateNode);
}

- (NSDate *)modificationDate
{
dispatch_once(&_lazyModificationDateToken, ^{
if (!_modificationDate) {
_modificationDate = [self lazyModificationDate];
}
});

return _modificationDate;
}

@synthesize noteReferences = _noteReferences;

- (NSMutableArray *)mutableNoteReferences {
Expand Down Expand Up @@ -137,5 +126,4 @@ - (void)setValueWithGedcomString:(NSString *)string
return;
}

@end

@end
48 changes: 48 additions & 0 deletions Gedcom/NSObject+MELazyPropertySwizzlingAdditions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// NSObject+MELazyPropertySwizzlingAdditions.h
// Gedcom
//
// Created by Mikkel Eide Eriksen on 24/04/13.
// Copyright (c) 2013 Mikkel Eide Eriksen. All rights reserved.
//

#import <Foundation/Foundation.h>

/**
Usage: Add this protocol to a class
For each property that you wish to load lazily, implement the following lazy getter:
```
- (id)_lazy<PropertyName>
{
return <LazyPropertyValue>;
}
```
Additionally, create an ivar:
```
@implementation MyClass {
dispatch_once_t _lazy<PropertyName>Token;
}
//...
@end
```
That's it, you're done. The first time a property is accessed, the _lazy getter is called in to set the ivar. Subsequently, the getters & setters are used as normal.
*/

static const char lazyPrefix[] = "_lazy";

@protocol MELazyPropertySwizzling <NSObject>

@end

@interface NSObject (MELazyPropertySwizzlingAdditions)

@end
94 changes: 94 additions & 0 deletions Gedcom/NSObject+MELazyPropertySwizzlingAdditions.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// NSObject+MELazyPropertySwizzlingAdditions.m
// Gedcom
//
// Created by Mikkel Eide Eriksen on 24/04/13.
// Copyright (c) 2013 Mikkel Eide Eriksen. All rights reserved.
//

#import "NSObject+MELazyPropertySwizzlingAdditions.h"
#import <objc/runtime.h>
#import <string.h>

@implementation NSObject (MELazyPropertySwizzlingAdditions)

+ (void)load
{
const size_t prefixLen = strlen(lazyPrefix);

int numClasses;
Class *classes = NULL;

classes = NULL;
numClasses = objc_getClassList(NULL, 0);

if (numClasses > 0 )
{
classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
for (int i = 0; i < numClasses; i++) {
Class cls = classes[i];

if (!class_conformsToProtocol(cls, @protocol(MELazyPropertySwizzling))) {
continue;
}

int unsigned numMethods;
Method *methods = class_copyMethodList(cls, &numMethods);
for (int i = 0; i < numMethods; i++) {
SEL lazySel = method_getName(methods[i]);
const char *lazySelName = sel_getName(lazySel);

if (strncmp(lazyPrefix, lazySelName, prefixLen) != 0) {
continue;
}

//NSLog(@"%s", lazySelName);

size_t tmpSize = strlen(lazySelName)-prefixLen;

char *getterName = malloc(tmpSize * sizeof(char));
strncpy(getterName, lazySelName+prefixLen, tmpSize);
getterName[0] = getterName[0] + 32; // lowercase first char

char *ivarName = malloc((tmpSize+1) * sizeof(char));
strncpy(ivarName+1, getterName, tmpSize);
ivarName[0] = '_';
ivarName[tmpSize+1] = '\0';

tmpSize = strlen(lazySelName);
char *tokenName = malloc((tmpSize+5) * sizeof(char));
strncpy(tokenName, lazySelName, tmpSize);
strncpy(tokenName+tmpSize, "Token", 5);

SEL getterSel = sel_registerName(getterName);

Ivar ivar = class_getInstanceVariable(cls, ivarName);
Ivar tokenIvar = class_getInstanceVariable(cls, tokenName);

Method lazyGetter = class_getInstanceMethod(cls, lazySel);
Method origGetter = class_getInstanceMethod(cls, getterSel);

IMP lazyIMP = method_getImplementation(lazyGetter);
IMP origIMP = method_getImplementation(origGetter);

IMP newIMP = imp_implementationWithBlock(^(id _s) {
dispatch_once_t token = (dispatch_once_t)object_getIvar(_s, tokenIvar);

dispatch_once(&token, ^{
if (!object_getIvar(_s, ivar)) {
object_setIvar(_s, ivar, lazyIMP(_s, lazySel));
}
});

return origIMP(_s, getterSel);
});

method_setImplementation(origGetter, newIMP);
}
}
free(classes);
}
}

@end

0 comments on commit 8154fac

Please sign in to comment.