- 浏览: 22410 次
- 性别:
- 来自: 北京
最新评论
Method Swizzling
- 博客分类:
- ios
转载:http://www.cocoachina.com/ios/20150324/11410.html
利用 Objective-C 的 Runtime 特性,我们可以给语言做扩展,帮助解决项目开发中的一些设计和技术问题。这一篇,我们来探索一些利用 Objective-C Runtime 的黑色技巧。这些技巧中最具争议的或许就是 Method Swizzling 。
介绍一个技巧,最好的方式就是提出具体的需求,然后用它跟其他的解决方法做比较。
所以,先来看看我们的需求:对 App 的用户行为进行追踪和分析。简单说,就是当用户看到某个 View 或者点击某个 Button 的时候,就把这个事件记下来。
手动添加
最直接粗暴的方式就是在每个 viewDidAppear 里添加记录事件的代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@implementation MyViewController ()- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated]; // Custom code
// Logging
[Logging logWithEventName:@“my view did appear”];
}
- (void)myButtonClicked:(id)sender
{ // Custom code
// Logging
[Logging logWithEventName:@“my button clicked”];
}
这种方式的缺点也很明显:它破坏了代码的干净整洁。因为 Logging 的代码本身并不属于 ViewController 里的主要逻辑。随着项目扩大、代码量增加,你的 ViewController 里会到处散布着 Logging 的代码。这时,要找到一段事件记录的代码会变得困难,也很容易忘记添加事件记录的代码。
你可能会想到用继承或类别,在重写的方法里添加事件记录的代码。代码可以是长的这个样子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@implementation UIViewController ()
- (void)myViewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Custom code
// Logging
[Logging logWithEventName:NSStringFromClass([self class])];
}
- (void)myButtonClicked:(id)sender
{ // Custom code
// Logging
NSString *name = [NSString stringWithFormat:@“my button in %@ is clicked”, NSStringFromClass([self class])];
[Logging logWithEventName:name];
}
Logging 的代码都很相似,通过继承或类别重写相关方法是可以把它从主要逻辑中剥离出来。但同时也带来新的问题:
你需要继承 UIViewController, UITableViewController, UICollectionViewController 所有这些 ViewController ,或者给他们添加类别;
每个 ViewController 里的 ButtonClick 方法命名不可能都一样;
你不能控制别人如何去实例化你的子类;
对于类别,你没办法调用到原来的方法实现。大多时候,我们重写一个方法只是为了添加一些代码,而不是完全取代它。
如果有两个类别都实现了相同的方法,运行时没法保证哪一个类别的方法会给调用。
Method Swizzling
Method Swizzling 利用 Runtime 特性把一个方法的实现与另一个方法的实现进行替换。
上一篇文章 有讲到每个类里都有一个 Dispatch Table ,将方法的名字(SEL)跟方法的实现(IMP,指向 C 函数的指针)一一对应。Swizzle 一个方法其实就是在程序运行时在 Dispatch Table 里做点改动,让这个方法的名字(SEL)对应到另个 IMP 。
首先定义一个类别,添加将要 Swizzled 的方法:
1
2
3
4
5
6
@implementation UIViewController (Logging)- (void)swizzled_viewDidAppear:(BOOL)animated
{ // call original implementation
[self swizzled_viewDidAppear:animated];
// Logging
[Logging logWithEventName:NSStringFromClass([self class])];
}
代码看起来可能有点奇怪,像递归不是么。当然不会是递归,因为在 runtime 的时候,函数实现已经被交换了。调用 viewDidAppear: 会调用你实现的 swizzled_viewDidAppear:,而在 swizzled_viewDidAppear: 里调用 swizzled_viewDidAppear: 实际上调用的是原来的 viewDidAppear: 。
接下来实现 swizzle 的方法 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@implementation UIViewController (Logging)void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)
{
// the method might not exist in the class, but in its superclass
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// class_addMethod will fail if original method already exists
BOOL didAddMethod = class_addMethod(class, originalSelector,
method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// the method doesn’t exist and we just added one
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
这里唯一可能需要解释的是 class_addMethod 。要先尝试添加原 selector 是为了做一层保护,因为如果这个类没有实现 originalSelector ,但其父类实现了,那 class_getInstanceMethod 会返回父类的方法。这样 method_exchangeImplementations 替换的是父类的那个方法,这当然不是你想要的。所以我们先尝试添加 orginalSelector ,如果已经存在,再用 method_exchangeImplementations 把原方法的实现跟新的方法实现给交换掉。
最后,我们只需要确保在程序启动的时候调用 swizzleMethod 方法。比如,我们可以在之前 UIViewController 的 Logging 类别里添加 +load: 方法,然后在 +load: 里把 viewDidAppear 给替换掉:
1
2
3
4
5
6
@implementation UIViewController (Logging)
+ (void)load
{
swizzleMethod([self class], @selector(viewDidAppear:),
@selector(swizzled_viewDidAppear:));
}
一般情况下,类别里的方法会重写掉主类里相同命名的方法。如果有两个类别实现了相同命名的方法,只有一个方法会被调用。但 +load: 是个特例,当一个类被读到内存的时候, runtime 会给这个类及它的每一个类别都发送一个 +load: 消息。
其实,这里还可以更简化点:直接用新的 IMP 取代原 IMP ,而不是替换。只需要有全局的函数指针指向原 IMP 就可以。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void (gOriginalViewDidAppear)(id, SEL, BOOL);
void newViewDidAppear(UIViewController *self, SEL _cmd, BOOL animated)
{ // call original implementation
gOriginalViewDidAppear(self, _cmd, animated); // Logging
[Logging logWithEventName:NSStringFromClass([self class])];
}
+ (void)load
{
Method originalMethod = class_getInstanceMethod(self, @selector(viewDidAppear:));
gOriginalViewDidAppear = (void *)method_getImplementation(originalMethod);
if(!class_addMethod(self, @selector(viewDidAppear:), (IMP) newViewDidAppear, method_getTypeEncoding(originalMethod))) {
method_setImplementation(originalMethod, (IMP) newViewDidAppear);
}
}
通过 Method Swizzling ,我们成功把逻辑代码跟处理事件记录的代码解耦。当然除了 Logging ,还有很多类似的事务,如 Authentication 和 Caching。这些事务琐碎,跟主要业务逻辑无关,在很多地方都有,又很难抽象出来单独的模块。这种程序设计问题,业界也给了他们一个名字 - Cross Cutting Concerns。
而像上面例子用 Method Swizzling 动态给指定的方法添加代码,以解决 Cross Cutting Concerns 的编程方式叫:Aspect Oriented Programming
Aspect Oriented Programming (面向切面编程)
Wikipedia 里对 AOP 是这么介绍的:
1
“An aspect can alter the behavior of the base code by applying advice (additional behavior) at various join points (points in a program) specified in a quantification or query called a pointcut (that detects whether a given join point matches).”
在 Objective-C 的世界里,这句话意思就是利用 Runtime 特性给指定的方法添加自定义代码。有很多方式可以实现 AOP ,Method Swizzling 就是其中之一。而且幸运的是,目前已经有一些第三方库可以让你不需要了解 Runtime ,就能直接开始使用 AOP 。
Aspects就是一个不错的 AOP 库,封装了 Runtime , Method Swizzling 这些黑色技巧,只提供两个简单的API:
1
2
3
4
5
6
7
+ (id)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;- (id)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
使用 Aspects 提供的 API,我们之前的例子会进化成这个样子:
1
2
3
4
5
6
7
8
9
10
11
@implementation UIViewController (Logging)
+ (void)load
{
[UIViewController aspect_hookSelector:@selector(viewDidAppear:)
withOptions:AspectPositionAfter
usingBlock:^(id aspectInfo) {
NSString *className = NSStringFromClass([[aspectInfo instance] class]);
[Logging logWithEventName:className];
} error:NULL];
}
你可以用同样的方式在任何你感兴趣的方法里添加自定义代码,比如 IBAction 的方法里。更好的方式,你提供一个 Logging 的配置文件作为唯一处理事件记录的地方:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@implementation AppDelegate (Logging)
+ (void)setupLogging
{ NSDictionary *config = @{ @"MainViewController": @{
GLLoggingPageImpression: @"page imp - main page",
GLLoggingTrackedEvents: @[
@{
GLLoggingEventName: @"button one clicked",
GLLoggingEventSelectorName: @"buttonOneClicked:",
GLLoggingEventHandlerBlock: ^(id aspectInfo) {
[Logging logWithEventName:@"button one clicked"];
},
},
@{
GLLoggingEventName: @"button two clicked",
GLLoggingEventSelectorName: @"buttonTwoClicked:",
GLLoggingEventHandlerBlock: ^(id aspectInfo) {
[Logging logWithEventName:@"button two clicked"];
},
},
],
}, @"DetailViewController": @{
GLLoggingPageImpression: @"page imp - detail page",
}
};
[AppDelegate setupWithConfiguration:config];
}
+ (void)setupWithConfiguration:(NSDictionary *)configs
{ // Hook Page Impression
[UIViewController aspect_hookSelector:@selector(viewDidAppear:)
withOptions:AspectPositionAfter
usingBlock:^(id aspectInfo) {
NSString *className = NSStringFromClass([[aspectInfo instance] class]);
[Logging logWithEventName:className];
} error:NULL]; // Hook Events
for (NSString *className in configs) {
Class clazz = NSClassFromString(className);
NSDictionary *config = configs[className];
if (config[GLLoggingTrackedEvents]) {
for (NSDictionary *event in config[GLLoggingTrackedEvents]) {
SEL selekor = NSSelectorFromString(event[GLLoggingEventSelectorName]);
AspectHandlerBlock block = event[GLLoggingEventHandlerBlock];
[clazz aspect_hookSelector:selekor
withOptions:AspectPositionAfter
usingBlock:^(id aspectInfo) {
block(aspectInfo);
} error:NULL];
}
}
}
}
然后在 -application:didFinishLaunchingWithOptions: 里调用 setupLogging:
1
2
3
4
5
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[self setupLogging]; return YES;
}
最后的话
利用 Objective-C Runtime 特性和 Aspect Oriented Programming ,我们可以把琐碎事务的逻辑从主逻辑中分离出来,作为单独的模块。它是对面向对象编程模式的一个补充。Logging 是个经典的应用,这里做个抛砖引玉,发挥想象力,可以做出其他有趣的应用。
利用 Objective-C 的 Runtime 特性,我们可以给语言做扩展,帮助解决项目开发中的一些设计和技术问题。这一篇,我们来探索一些利用 Objective-C Runtime 的黑色技巧。这些技巧中最具争议的或许就是 Method Swizzling 。
介绍一个技巧,最好的方式就是提出具体的需求,然后用它跟其他的解决方法做比较。
所以,先来看看我们的需求:对 App 的用户行为进行追踪和分析。简单说,就是当用户看到某个 View 或者点击某个 Button 的时候,就把这个事件记下来。
手动添加
最直接粗暴的方式就是在每个 viewDidAppear 里添加记录事件的代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@implementation MyViewController ()- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated]; // Custom code
// Logging
[Logging logWithEventName:@“my view did appear”];
}
- (void)myButtonClicked:(id)sender
{ // Custom code
// Logging
[Logging logWithEventName:@“my button clicked”];
}
这种方式的缺点也很明显:它破坏了代码的干净整洁。因为 Logging 的代码本身并不属于 ViewController 里的主要逻辑。随着项目扩大、代码量增加,你的 ViewController 里会到处散布着 Logging 的代码。这时,要找到一段事件记录的代码会变得困难,也很容易忘记添加事件记录的代码。
你可能会想到用继承或类别,在重写的方法里添加事件记录的代码。代码可以是长的这个样子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@implementation UIViewController ()
- (void)myViewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Custom code
// Logging
[Logging logWithEventName:NSStringFromClass([self class])];
}
- (void)myButtonClicked:(id)sender
{ // Custom code
// Logging
NSString *name = [NSString stringWithFormat:@“my button in %@ is clicked”, NSStringFromClass([self class])];
[Logging logWithEventName:name];
}
Logging 的代码都很相似,通过继承或类别重写相关方法是可以把它从主要逻辑中剥离出来。但同时也带来新的问题:
你需要继承 UIViewController, UITableViewController, UICollectionViewController 所有这些 ViewController ,或者给他们添加类别;
每个 ViewController 里的 ButtonClick 方法命名不可能都一样;
你不能控制别人如何去实例化你的子类;
对于类别,你没办法调用到原来的方法实现。大多时候,我们重写一个方法只是为了添加一些代码,而不是完全取代它。
如果有两个类别都实现了相同的方法,运行时没法保证哪一个类别的方法会给调用。
Method Swizzling
Method Swizzling 利用 Runtime 特性把一个方法的实现与另一个方法的实现进行替换。
上一篇文章 有讲到每个类里都有一个 Dispatch Table ,将方法的名字(SEL)跟方法的实现(IMP,指向 C 函数的指针)一一对应。Swizzle 一个方法其实就是在程序运行时在 Dispatch Table 里做点改动,让这个方法的名字(SEL)对应到另个 IMP 。
首先定义一个类别,添加将要 Swizzled 的方法:
1
2
3
4
5
6
@implementation UIViewController (Logging)- (void)swizzled_viewDidAppear:(BOOL)animated
{ // call original implementation
[self swizzled_viewDidAppear:animated];
// Logging
[Logging logWithEventName:NSStringFromClass([self class])];
}
代码看起来可能有点奇怪,像递归不是么。当然不会是递归,因为在 runtime 的时候,函数实现已经被交换了。调用 viewDidAppear: 会调用你实现的 swizzled_viewDidAppear:,而在 swizzled_viewDidAppear: 里调用 swizzled_viewDidAppear: 实际上调用的是原来的 viewDidAppear: 。
接下来实现 swizzle 的方法 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@implementation UIViewController (Logging)void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)
{
// the method might not exist in the class, but in its superclass
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// class_addMethod will fail if original method already exists
BOOL didAddMethod = class_addMethod(class, originalSelector,
method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// the method doesn’t exist and we just added one
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
这里唯一可能需要解释的是 class_addMethod 。要先尝试添加原 selector 是为了做一层保护,因为如果这个类没有实现 originalSelector ,但其父类实现了,那 class_getInstanceMethod 会返回父类的方法。这样 method_exchangeImplementations 替换的是父类的那个方法,这当然不是你想要的。所以我们先尝试添加 orginalSelector ,如果已经存在,再用 method_exchangeImplementations 把原方法的实现跟新的方法实现给交换掉。
最后,我们只需要确保在程序启动的时候调用 swizzleMethod 方法。比如,我们可以在之前 UIViewController 的 Logging 类别里添加 +load: 方法,然后在 +load: 里把 viewDidAppear 给替换掉:
1
2
3
4
5
6
@implementation UIViewController (Logging)
+ (void)load
{
swizzleMethod([self class], @selector(viewDidAppear:),
@selector(swizzled_viewDidAppear:));
}
一般情况下,类别里的方法会重写掉主类里相同命名的方法。如果有两个类别实现了相同命名的方法,只有一个方法会被调用。但 +load: 是个特例,当一个类被读到内存的时候, runtime 会给这个类及它的每一个类别都发送一个 +load: 消息。
其实,这里还可以更简化点:直接用新的 IMP 取代原 IMP ,而不是替换。只需要有全局的函数指针指向原 IMP 就可以。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void (gOriginalViewDidAppear)(id, SEL, BOOL);
void newViewDidAppear(UIViewController *self, SEL _cmd, BOOL animated)
{ // call original implementation
gOriginalViewDidAppear(self, _cmd, animated); // Logging
[Logging logWithEventName:NSStringFromClass([self class])];
}
+ (void)load
{
Method originalMethod = class_getInstanceMethod(self, @selector(viewDidAppear:));
gOriginalViewDidAppear = (void *)method_getImplementation(originalMethod);
if(!class_addMethod(self, @selector(viewDidAppear:), (IMP) newViewDidAppear, method_getTypeEncoding(originalMethod))) {
method_setImplementation(originalMethod, (IMP) newViewDidAppear);
}
}
通过 Method Swizzling ,我们成功把逻辑代码跟处理事件记录的代码解耦。当然除了 Logging ,还有很多类似的事务,如 Authentication 和 Caching。这些事务琐碎,跟主要业务逻辑无关,在很多地方都有,又很难抽象出来单独的模块。这种程序设计问题,业界也给了他们一个名字 - Cross Cutting Concerns。
而像上面例子用 Method Swizzling 动态给指定的方法添加代码,以解决 Cross Cutting Concerns 的编程方式叫:Aspect Oriented Programming
Aspect Oriented Programming (面向切面编程)
Wikipedia 里对 AOP 是这么介绍的:
1
“An aspect can alter the behavior of the base code by applying advice (additional behavior) at various join points (points in a program) specified in a quantification or query called a pointcut (that detects whether a given join point matches).”
在 Objective-C 的世界里,这句话意思就是利用 Runtime 特性给指定的方法添加自定义代码。有很多方式可以实现 AOP ,Method Swizzling 就是其中之一。而且幸运的是,目前已经有一些第三方库可以让你不需要了解 Runtime ,就能直接开始使用 AOP 。
Aspects就是一个不错的 AOP 库,封装了 Runtime , Method Swizzling 这些黑色技巧,只提供两个简单的API:
1
2
3
4
5
6
7
+ (id)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;- (id)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
使用 Aspects 提供的 API,我们之前的例子会进化成这个样子:
1
2
3
4
5
6
7
8
9
10
11
@implementation UIViewController (Logging)
+ (void)load
{
[UIViewController aspect_hookSelector:@selector(viewDidAppear:)
withOptions:AspectPositionAfter
usingBlock:^(id aspectInfo) {
NSString *className = NSStringFromClass([[aspectInfo instance] class]);
[Logging logWithEventName:className];
} error:NULL];
}
你可以用同样的方式在任何你感兴趣的方法里添加自定义代码,比如 IBAction 的方法里。更好的方式,你提供一个 Logging 的配置文件作为唯一处理事件记录的地方:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@implementation AppDelegate (Logging)
+ (void)setupLogging
{ NSDictionary *config = @{ @"MainViewController": @{
GLLoggingPageImpression: @"page imp - main page",
GLLoggingTrackedEvents: @[
@{
GLLoggingEventName: @"button one clicked",
GLLoggingEventSelectorName: @"buttonOneClicked:",
GLLoggingEventHandlerBlock: ^(id aspectInfo) {
[Logging logWithEventName:@"button one clicked"];
},
},
@{
GLLoggingEventName: @"button two clicked",
GLLoggingEventSelectorName: @"buttonTwoClicked:",
GLLoggingEventHandlerBlock: ^(id aspectInfo) {
[Logging logWithEventName:@"button two clicked"];
},
},
],
}, @"DetailViewController": @{
GLLoggingPageImpression: @"page imp - detail page",
}
};
[AppDelegate setupWithConfiguration:config];
}
+ (void)setupWithConfiguration:(NSDictionary *)configs
{ // Hook Page Impression
[UIViewController aspect_hookSelector:@selector(viewDidAppear:)
withOptions:AspectPositionAfter
usingBlock:^(id aspectInfo) {
NSString *className = NSStringFromClass([[aspectInfo instance] class]);
[Logging logWithEventName:className];
} error:NULL]; // Hook Events
for (NSString *className in configs) {
Class clazz = NSClassFromString(className);
NSDictionary *config = configs[className];
if (config[GLLoggingTrackedEvents]) {
for (NSDictionary *event in config[GLLoggingTrackedEvents]) {
SEL selekor = NSSelectorFromString(event[GLLoggingEventSelectorName]);
AspectHandlerBlock block = event[GLLoggingEventHandlerBlock];
[clazz aspect_hookSelector:selekor
withOptions:AspectPositionAfter
usingBlock:^(id aspectInfo) {
block(aspectInfo);
} error:NULL];
}
}
}
}
然后在 -application:didFinishLaunchingWithOptions: 里调用 setupLogging:
1
2
3
4
5
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[self setupLogging]; return YES;
}
最后的话
利用 Objective-C Runtime 特性和 Aspect Oriented Programming ,我们可以把琐碎事务的逻辑从主逻辑中分离出来,作为单独的模块。它是对面向对象编程模式的一个补充。Logging 是个经典的应用,这里做个抛砖引玉,发挥想象力,可以做出其他有趣的应用。
发表评论
-
RunLoop
2015-06-10 11:38 532转载:http://www.cocoachina.com/io ... -
GCD使用注意
2015-05-11 10:27 500转载:http://www.cocoachina.com/io ... -
CABaseAnimation
2015-04-24 13:30 713转载:http://blog.csdn.net/iosevan ... -
NSOperation, NSOperationQueue
2015-03-23 14:55 504转载:http://greenchiu.github.io/b ... -
ios申请真机调试( xcode 5)详细解析
2015-03-20 14:28 383转载:http://my.oschina.net/u/1245 ... -
一个苹果证书怎么多次使用——导出p12文件
2015-03-20 14:27 1185转载:http://my.oschina.net/u/1245 ... -
iOS申请证书,Certificates, Identifiers &Profiles 简介
2015-03-20 14:26 1026转载:http://my.oschina.net/u/1245 ... -
GCD 深入理解
2015-03-19 15:30 750转载:http://www.cocoachina.com/io ... -
发布iOS应用(xcode5)到App Store(苹果商店) 详细解析
2015-03-17 15:22 383转载:http://my.oschina.net/u/1245 ... -
Objective-C Runtime
2015-03-13 16:52 403转载:http://www.cocoachina.com/io ... -
ScrollView 与 Autolayout
2015-03-04 15:45 427http://www.cocoachina.com/ios/2 ... -
初探 ios 8 Size Class
2015-02-13 10:12 570转载:http://blog.csdn.net/please ... -
ios 8通知中心快速回复
2015-02-12 11:32 443转载:http://www.cocoachina.com/i ... -
StoryBoard
2015-02-09 12:54 349storyboard 学习资料1 http://www. ... -
Autolayout
2015-02-03 14:36 466推荐的链接: ios 自动布局教程(1) http:/ ...
相关推荐
Method Swizzling 示例
AopTestDemo: iOS埋点统计方案: 1.Method Swizzling 2.AOP编程
AOP 编程必看代码,iOS 逆向开发,iOS SDK开发,iOS深入开发的源点
在简书的文章中详细的讲解了Method Swizzling,为了方便大家学习和理解,所以在Github上写了这个小Demo,可以帮助大家更好的理解Method Swizzling。 在Demo中简单实现了一个崩溃拦截的代码,等以后有时间的话,我...
介绍个技巧,最好的式就是提出具体的需求,然后它跟其他的解决法做较。所以,先来看看我们的需求:对 App 的户为进追踪和分析。简单说,就是当户看到某个 View
在阅读团队一项目源码时,发现Method Swizzling的写法有些瑕疵。这篇文章主要就介绍iOS Method Swizzling的正确写法应该是什么样的。 下面是iOS Method Swizzling的一种实现: + (void)load { Class class = [self...
主要介绍了iOS 11 使用方法替换(Method Swizzling)去掉导航栏返回按钮的文字,需要的朋友可以参考下
原理是method swizzling替换了系统方法,处理参数边界,现多个项目使用,一切正常 原名DurexKit,名字太无节操,改为SafeKit SafeKit中针对每个可能crash的方法处理,目前处理的范围有限,如有兴趣,欢迎贡献代码...
这是iOS运行时在几个场景中的应用代码,主要就是在分类中添加属性,对定时器的内存泄漏问题的处理,还有就是Method Swizzling的使用。
Method Swizzling 原理 在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的...
作者CoderJackyHuang,源码RuntimeDemo,在我学习runtime的method swizzling特性之前,有很多同事或者朋友经常在我耳边说起swizzling特性,一个个在我面前说这个东西千万不能用,会引起很多问题的。但是,在我学习完...
Exploring and Method Swizzling Objective-C Frameworks You’ll cap off this round of dynamic framework exploration by digging into Objective-C frameworks using the Objective-C runtime to hook and ...
本文介绍了ios UITableView实现无数据占位图片,分享给大家,具体如下: 国际惯例,上效果图 该效果的实现主要是使用runtime的交叉方法实现,将tableView的... Method originMethod = class_getInstanceMethod(self
源码TYSwizzleDemo,method swizzling封装成 C函数和 NSObject分类,使用非常简单,TYSwizzleDemo method swizzling easy to use,C Function and NSObject Categary 我已经把 method swizzling 封装成 C 函数 和 ...
统计所有的点击事件是采用Method Swizzling实现的,可以做到使用中不需要一行代码实现统计所有事件,具体细节将来我会专门抽几篇文章介绍。 今天主要说说如何统计APP中的所有网络请求。公司网络请求如果不是静态库...
本文主要介绍Objective-C对象模型的实现细节,以及Objective-C语言对象模型中对isaswizzling和methodswizzling的支持。希望本文能加深你对Objective-C对象的理解。Objective-C是一门面向对象的编程语言。每一个对象...
2.监控官方SDK的参数格式的方式是hook相关的方法,本项目中也提供了一段使用了方法绞合(Method Swizzling)来监控openURL方法的代码 demo 本项目主要实现了将文本,图片,链接等信息分享到微信,QQ,微博的功能。 ...