不搞原理,直接上代码:
- 分类可以把私有方法向前引用,就是变成公开的,可调用,上例子:
创建一个person类,代码如下:
//.h代码
@interface Person : NSObject
+ (void)getAge;
@property(nonatomic,copy)NSString *test1;
@property(nonatomic,assign) int num;
@end
//.m代码
//这里其实就是个类扩展的使用
@interface Person()
+ (void)getName; //这里的方法是私有的方法,创建的属性也是私有的,如果内容过多可以单独写一个类扩展
@end
@implementation Person
+ (void)getAge {
NSLog(@"age:18");
}
+ (void)getName {
NSLog(@"name:XXX");
}
上面的person类有个私有方法:+ (void)getName;
,如果外界想调用这个方法,怎么办?创建一个分类就可以了,代码如下:
//.h
@interface Person (Test)
+ (void)getName; //这个是person的私有方法,在category内并没有去实现,我们只是把Person的这个私有方法向前引用了,这样外界k就可以调用了
@end
//.m文件没有内容
这个时候只要导入分类头部#import "Person+Test.h"
,就可以调用到私有方法了,感觉OC中其实并没有绝对的私有方法,我们所说的私有,其实只是我们直接写这个方法,不会提示,系统报错而已.
2.类A如何在分类中调用类A的方法?(真绕)
上代码:
//创建一个vc
#import <UIKit/UIKit.h>
#import "SuperViewController.h"
@interface ViewController : SuperViewController
-(void)eat;
-(void)sleep;
@end
#import "ViewController.h"
#import "ViewController+test.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self code];
[self sleep];
}
-(void)eat{
NSLog(@"vc----就知道吃,越来越胖了吧~~~");
}
-(void)sleep{
NSLog(@"vc___sleep");
}
@end
现在的问题是:如何在分类中调用ViewController
的eat
方法:
创建一个分类
@interface ViewController (test)
-(void)code;
-(void)eat;
-(void)sleep;
@end
-(void)code{
//分类super调用的是 VC的父类的eat方法,那VC的eat这么调用呢???
[super eat];
NSLog(@"category---吃饱了才有力气敲代码");
//分类如何调用原类的方法,比如这里的ViewController的eat方法
[self VC_eat];
}
-(void)eat{
NSLog(@"category----eat");
}
-(void)VC_eat{
unsigned int count;
Method *method = class_copyMethodList([self class], &count);
NSInteger index = 0;
for (int i=0; i<count; i++) {
SEL name = method_getName(method[i]);
NSString *nameString = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
NSLog(@"遍历到的方法===== %@",nameString);
if([nameString isEqualToString:@"eat"]){
index = i;
NSLog(@"找到一个 i== -----%d",i);
//这里找到了不加break,我们要找到VC的eat,为什么可以这样
//1.class_copyMethodList([self class], &count);只会获取类和类中加载了的分类的方法
//2.因为方法列表的加载顺序,先分类的方法,在vc的方法;
//3.分类的中方法加载无序,看系统编译时那个在前那个在后.
}
}
SEL sel = method_getName(method[index]);
IMP imp = method_getImplementation(method[index]);
((void (*)(id,SEL))imp)(self,sel);
}
- (void)sleep{
NSLog(@"test___sleep");
}
其实办法就是通过遍历找到VC的eat方法的IMP,直接调用IMP执行方法,主要代码就是上面的-(void)VC_eat
- 分类中添加成员变量(runtime的关联属性)(简单版),还一个复杂版的封装,没搞明白,先不记录
//.h
@interface Person (Run)
@property(nonatomic,copy)NSString * name;
@end
//.m
#import "Person+Run.h"
#import <objc/runtime.h>
@implementation Person (Run)
-(void)setName:(NSString *)name{
objc_setAssociatedObject(self, @"name", name,OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)name{
return objc_getAssociatedObject(self, @"name");
}
- 方法的本质为消息发送,如果通过objc_msgSend直接发送消息呢?(这个好像不该放分类里面,是runtime的)
创建一个类,实现了一些方法
@interface MyClass : NSObject
-(void)showAge;
-(void)showName:(NSString *)aName;
-(void)showSizeWithWidth:(float)aWidth andHeight:(float)aHeight;
-(float)getHeight;
-(NSString *)getInfo;
@end
//.m
#import "MyClass.h"
@implementation MyClass
- (instancetype)init
{
self = [super init];
if (self) {
[self showUserName];
}
return self;
}
-(void)showUserName{
NSLog(@"打印我的名字~~~");
}
-(void)showAge{
NSLog(@"24");
}
-(void)showName:(NSString *)aName{
NSLog(@"name is %@",aName);
}
-(void)showSizeWithWidth:(float)aWidth andHeight:(float)aHeight{
NSLog(@"size is %.2f * %.2f",aWidth, aHeight);
}
-(float)getHeight{
return 187.5f;
}
-(NSString *)getInfo{
return @"Hi, my name is XXX";
}
现在在VC中引入头文件,并通过objc_msgSend调用方法:
MyClass *objct = [[MyClass alloc] init];
((void (*)(id, SEL)) objc_msgSend) (objct, sel_registerName("showAge"));
((void (*) (id, SEL, NSString *)) objc_msgSend) (objct, sel_registerName("showName:"), @"Dave Ping");
((void (*) (id, SEL, float, float)) objc_msgSend) (objct, sel_registerName("showSizeWithWidth:andHeight:"), 110.5f, 200.0f);
float f = ((float (*) (id, SEL)) objc_msgSend_fpret) (objct, sel_registerName("getHeight"));
NSLog(@"height is %.2f",f);
NSString *info = ((NSString* (*) (id, SEL)) objc_msgSend) (objct, sel_registerName("getInfo"));
NSLog(@"%@",info);
其实好简单,只是没怎么这样写,看起来很别扭而已;
5.通过分类给button添加一个block处理点击事件(关联属性的使用实例)
//创建一个button的分类.h🔥
#import <UIKit/UIKit.h>
typedef void(^clickBlock)(void);
@interface UIButton (ClickBlock)
@property (nonatomic,copy) clickBlock click;
@end
//.m🔥
#import "UIButton+ClickBlock.h"
#import <objc/runtime.h>
static const void *associatedKey = "associatedKey";
@implementation UIButton (ClickBlock)
//Category中的属性,只会生成setter和getter方法,不会生成成员变量
-(void)setClick:(clickBlock)click{
objc_setAssociatedObject(self, associatedKey, click, OBJC_ASSOCIATION_COPY_NONATOMIC);
[self removeTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
if (click) {
[self addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
}
}
-(clickBlock)click{
return objc_getAssociatedObject(self, associatedKey);
}
-(void)buttonClick{
if (self.click) {
self.click();
}
}
然后在需要使用的地方调用:
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
btn.frame = CGRectMake(100, 200, 100, 40);
[self.view addSubview:btn];
[btn setTitle:@"点我" forState:0];
[btn.titleLabel setFont:[UIFont systemFontOfSize:20]];
[btn setTitleColor:[UIColor grayColor] forState:0];
//把btn的业务逻辑和功能逻辑放在了一起,很骚啊~~~
btn.click = ^{
NSLog(@"333333");
};
这样的写法的好处是,我们可以把button的业务逻辑和功能逻辑放在一起,不需要再去调整查看button到底在干什么了,有点像RX.
- RunLoop子线程保活
//创建一个线程
//.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ZHThread : NSThread
@end
NS_ASSUME_NONNULL_END
//.m
#import "ZHThread.h"
@implementation ZHThread
- (void)dealloc
{
NSLog(@"重写dealloc 方法,---销毁调用");
}
@end
给线程添加事件
_strongThread = [[ZHThread alloc] initWithTarget:self selector:@selector(threadTest) object:nil];
_strongThread.name = @"子线程2";
[_strongThread start];
//**************************
-(void)threadTest{
for (int i=0; i<5; i++) {
NSLog(@"----%d--%@",i,[NSThread currentThread]);
}
//这个任务被执行完毕后会销毁子线程,如果强引用,线程不会销毁,但是这个线程是没有任何作用的,线程不销毁,说明runloop就还在,但是为啥无效了呢?
//因为每次调用runloop会指定一个mode,任务完成会调用释放池销毁timer,source,observer,想要这个线程能继续使用,就需要在创建一个mode,一个空的也行。
NSLog(@"-------------------------- \n %@ \n ------------------",[NSRunLoop currentRunLoop]);
//没有这个,后面的点击屏幕无效了,因为runloop出问题了
//启动runloop,如果没有一个输入源或者timer附加于runloop上,runloop就会立刻退出
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"\n---22222222222----%@",[NSRunLoop currentRunLoop]);//run起来后这个方法不会走
}
主要代码就上面的两句:
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
然后我们可以测试一下:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self performSelector:@selector(test_two) onThread:_strongThread withObject:nil waitUntilDone:NO];
}
-(void)test_two{
NSLog(@"--%@--",[NSThread currentThread]);
NSLog(@"\n---再次激活后的runloop----%@",[NSRunLoop currentRunLoop]);
}
发现还是在同一条线程执行任务,且线程dealloc方法没有被调用;
同时,通过打印的runloop可以看出,[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
后runloop的ignoreWakeUps = false,current mode = kCFRunLoopDefaultMode,主要是这两货改变了;
7.对外只读,对内读写;
苹果的API中这种操作其实遍地都是,代码如下:
创建一个person类,包含一个name属性,此属性为可对外修改的;
//.h
#import <UIKit/UIKit.h>
@interface Person : NSObject
@property(nonatomic,copy)NSString *name;
@end
//.m 没有内容
创建一个personVC,包含一个只读的person类
#import <UIKit/UIKit.h>
#import "Person.h"
@interface PersonViewController : UIViewController
@property (nonatomic,strong,readonly)Person *perView;
@end
//.m
#import "PersonViewController.h"
@interface PersonViewController ()
@property (nonatomic,strong,readwrite)Person *perView;
@end
@implementation PersonViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
//内部给一个person的初始化操作,外界无法创建Person
-(Person *)perView {
if (!_perView) {
_perView = [[Person alloc] init];
_perView.name = @"小李";
}
return _perView;
}
@end
然后在vc界面调用:
- (void)viewDidLoad {
[super viewDidLoad];
PersonViewController *perVC = [[PersonViewController alloc] init];
// perVC.perView = [[Person alloc] init]; //只读属性,修改会报错,只读是对于perVC的perView属性,本质其实就是perView的指针是不可改变的.
NSLog(@"---%@",perVC.perView.name);
perVC.perView.name = @"小红";
NSLog(@"---%@",perVC.perView.name);
}
其实就是person的实例对象的指针是只读的,所以我们不能改变person对象的指针,但是修改person内的属性name并不会对指针产生影响;
8 对象本质,占内存大小;
对象的本质是结构体,这个不用说了,内存大小,看例子,直接在main函数上写的:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
@interface Student : NSObject{
@public
int _a;
int _b;
int _c;
// int _d;
// int _e;
// NSString *abc;
}
@end
@implementation Student
@end
int main(int argc, char * argv[]) {
@autoreleasepool {
🔥前提:64位的系统
🔥 一个指针在32位环境下占用4个字节,64位环境下占用8个字节
Student *stu = [[Student alloc] init];
// stu -> _a = 4;
// stu -> _b = 5;
🔥获得 NSObject 实例对象的成员变量所占用的大小 >> 8
NSLog(@"NSObject------%zd", class_getInstanceSize([NSObject class])); // 输出为8
NSLog(@"Student------%zd", class_getInstanceSize([Student class]));
🔥这个是需要的空间大小,按8的倍数分配,不是系统会分配的大小
NSLog(@"stu----%lu",sizeof(stu)); //实例对象stu指针占得大小
NSLog(@"int * ----%lu",sizeof(int *));//int指针,也是8,😑
NSLog(@"\n---------------------------------------------\n");
NSLog(@"int----%lu",sizeof(int));
//
NSLog(@"NSString----%lu",sizeof(@""));
🔥malloc_size 系统到底为实例对象分配了多少内存空间
🔥 获得 stu 指针所指向内存的大小 >> 16 ,最小为16,一个int4个字节,5个= 20 + stu结构体的isa =8 ;一共28 ,所以使用32个字节存储
NSLog(@"stu------%zd", malloc_size((__bridge const void *)(stu))); // 输出为 32
NSObject *obj = [[NSObject alloc] init];
NSLog(@"obj 实际系统分配的内存空间------%zd", malloc_size((__bridge const void *)(obj))); // 输出为 16,实际使用只有8个字节,为了内存对齐,分配16个字节
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
这里对内存对齐说明下:
内存对齐
class_getInstanceSize为结构体分配内存空间时,所分配的内存空间大小必须是结构体中占用最大内存空间成员所占用内存大小的倍数。 在上面例子中,占用最大内存空间成员为isa指针,占用8个字节,所以,能容纳20个字节的数据,最小的8的倍数就是24。
malloc_size的结果为什么是32。
iOS操作系统本身,在分配内存时也做了内存对齐的处理,规定分配内存大小必须是16的倍数,所以结果为32。
- .....
网友评论