Skip to content

Commit

Permalink
1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
XuNing committed Jan 23, 2019
1 parent 2abab23 commit b590bee
Show file tree
Hide file tree
Showing 51 changed files with 2,617 additions and 1 deletion.
83 changes: 82 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,83 @@
# XNGNotificationProxy
wip

English | [中文](https://www.jianshu.com/p/3fe728c4d7a3)

XNGNotificationProxy is a replacement of custom NSNotification.

## A common scenario of NSNotification in use

You followed a user in VC_U, so the follow buttons in VC_A and VC_B should update state.

```
// VC_U.m
[[NSNotificationCenter defaultCenter] postNotificationName:@"UserDidFollowUser"
object:nil
userInfo:@{@"isFollowed": @(YES), @"userID": @(123456)}];
```
```
// VC_A.m or VC_B.m
[[NSNotificationCenter defaultCenter] addObserverForName:@"UserDidFollowUser"
object:nil
queue:nil
usingBlock:^(NSNotification * _Nonnull note) {
BOOL isFollowed = [note.userInfo[@"isFollowed"] boolValue];
NSNumber *userID = note.userInfo[@"userID"];
}];
```
There are a lot of magic strings. We may define some global constant strings to solve this, but we can never know what parameters are in the userInfo intuitively.

## Use XNGNotificationProxy

You need create a category for XNGNotificationProxy that conforms a custom protocol first.
```
// XNGNotificationProxy+Protocol.h
@protocol UserActionProtocol <NSObject>
@optional
- (void)userDidFollow:(BOOL)isFollowed userID:(NSNumber *)userID;
@end
@interface XNGNotificationProxy (Protocol) <UserActionProtocol>
@end
```
then
```
// VC_U.m
[[XNGNotificationProxy sharedProxy] userDidFollow:YES userID:@(123456)];
```
```
// VC_A.m
@interface VC_A () <UserActionProtocol>
@end
// -viewDidLoad or somewhere else
[[XNGNotificationProxy sharedProxy] registerProtocol:@protocol(UserActionProtocol) forObject:self];
//
- (void)userDidFollow:(BOOL)isFollowed userID:(NSNumber *)userID {
// do something
}
```

## Requirements

iOS 7.0 or later.

## Installation

There are two ways to use XNGNotificationProxy in your project:
- using CocoaPods
- drag `XNGNotificationProxy` folder into your project

## Licenses

XNGNotificationProxy is released under the MIT license. See [LICENSE](https://github.com/xuning0/XNGNotificationProxy/blob/master/LICENSE) for details.




13 changes: 13 additions & 0 deletions XNGNotificationProxy.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Pod::Spec.new do |s|
s.name = "XNGNotificationProxy"
s.version = "1.0"
s.summary = "A replacement of custom NSNotification using NSProxy."
s.homepage = "https://github.com/xuning0/XNGNotificationProxy"
s.license = "MIT"
s.author = { "XuNing" => "xuning0@outlook.com" }
s.platform = :ios, "7.0"
s.source = { :git => "https://github.com/xuning0/XNGNotificationProxy.git", :tag => "#{s.version}" }
s.requires_arc = true

s.source_files = "XNGNotificationProxy/*.{h,m}"
end
21 changes: 21 additions & 0 deletions XNGNotificationProxy/XNGNotificationProxy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// XNGNotificationProxy.h
// XNGNotificationProxy
//
// Created by XuNing on 2019/1/22.
// Copyright © 2019 xuning. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface XNGNotificationProxy : NSProxy

+ (instancetype)sharedProxy;

- (void)registerProtocol:(Protocol *)protocol forObject:(id)obj;

@end

NS_ASSUME_NONNULL_END
105 changes: 105 additions & 0 deletions XNGNotificationProxy/XNGNotificationProxy.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//
// XNGNotificationProxy.m
// XNGNotificationProxy
//
// Created by XuNing on 2019/1/22.
// Copyright © 2019 xuning. All rights reserved.
//

#import "XNGNotificationProxy.h"
#import <objc/runtime.h>

@interface XNGNotificationProxy ()
// { method1: [obj1, obj2], method2: [obj3, obj4], ... }
@property(nonatomic, strong) NSMutableDictionary<NSString *, NSHashTable *> *methodDictionary;
// for self.methodDictionary thread safty
@property(nonatomic, strong) dispatch_queue_t modificationQueue;
@end

@implementation XNGNotificationProxy

+ (instancetype)sharedProxy {
static XNGNotificationProxy *proxy;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
proxy = [XNGNotificationProxy alloc];
proxy.methodDictionary = [NSMutableDictionary dictionary];
proxy.modificationQueue = dispatch_queue_create("com.notificationProxy.modification", DISPATCH_QUEUE_SERIAL);
});
return proxy;
}

- (void)registerProtocol:(Protocol *)protocol forObject:(id)obj {
NSParameterAssert(protocol);
NSParameterAssert(obj);
NSAssert([obj conformsToProtocol:protocol], @"object %@ does not conform to protocol: %@", obj, protocol);
NSArray *methodNames = [XNGNotificationProxy getAllMethodNamesInProtocol:protocol];
for (NSString *name in methodNames) {
dispatch_sync(self.modificationQueue, ^{
NSHashTable *hashTable = self.methodDictionary[name];
if (!hashTable.anyObject) {
hashTable = [NSHashTable weakObjectsHashTable];
}
[hashTable addObject:obj];
self.methodDictionary[name] = hashTable;
});
}
}

#pragma mark - Override Method
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
for (id obj in [self objectsInHashTableForSelector:sel]) {
if ([obj respondsToSelector:sel]) {
return [obj methodSignatureForSelector:sel];
}
}
// if this method returns nil, a selector not found exception is raised.
// https://github.com/facebookarchive/AsyncDisplayKit/pull/1562
return [NSMethodSignature signatureWithObjCTypes:"@^v^c"];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
for (id obj in [self objectsInHashTableForSelector:invocation.selector]) {
if ([obj respondsToSelector:invocation.selector]) {
[invocation invokeWithTarget:obj];
}
}
}

#pragma mark - Private Method
- (NSArray *)objectsInHashTableForSelector:(SEL)sel {
__block NSArray *objects;
dispatch_sync(self.modificationQueue, ^{
objects = self.methodDictionary[NSStringFromSelector(sel)].allObjects;
});
return objects;
}

+ (void)enumerateMethodsInProtocol:(Protocol *)protocol
isRequired:(BOOL)isRequired
usingBlock:(void (^)(struct objc_method_description method))block {
unsigned int methodsCountInProtocol = 0;
struct objc_method_description *methods = protocol_copyMethodDescriptionList(protocol, isRequired, YES, &methodsCountInProtocol);
for (unsigned int i = 0; i < methodsCountInProtocol; i++) {
struct objc_method_description method = methods[i];
!block ?: block(method);
}
free(methods);
}

+ (NSArray *)getAllMethodNamesInProtocol:(Protocol *)protocol {
NSMutableArray *methodNames = [NSMutableArray array];
[self enumerateMethodsInProtocol:protocol
isRequired:YES
usingBlock:^(struct objc_method_description method) {
[methodNames addObject:NSStringFromSelector(method.name)];
}];
[self enumerateMethodsInProtocol:protocol
isRequired:NO
usingBlock:^(struct objc_method_description method) {
[methodNames addObject:NSStringFromSelector(method.name)];
}];
return [methodNames copy];
}

@end
9 changes: 9 additions & 0 deletions XNGNotificationProxyDemo/Podfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
platform :ios, '9.0'

target 'XNGNotificationProxyDemo' do

use_frameworks!

pod 'XNGNotificationProxy', :path => '..'

end
16 changes: 16 additions & 0 deletions XNGNotificationProxyDemo/Podfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
PODS:
- XNGNotificationProxy (1.0)

DEPENDENCIES:
- XNGNotificationProxy (from `..`)

EXTERNAL SOURCES:
XNGNotificationProxy:
:path: ".."

SPEC CHECKSUMS:
XNGNotificationProxy: 746998e2e0d0b55f6ad825197b6ec12aebe11ceb

PODFILE CHECKSUM: 2a37536700d33c5101136eb7474edbd371fa61c1

COCOAPODS: 1.5.3

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions XNGNotificationProxyDemo/Pods/Manifest.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b590bee

Please sign in to comment.