KK


  • 首页

  • 归档

未命名

发表于 2022-12-22

Mac OS 终端利器 iTerm2

(http://www.cnblogs.com/xishuai/p/mac-iterm2.html)

之前一直使用 Mac OS 自带的终端,用起来虽然有些不太方便,但总体来说还是可以接受的,是有想换个终端的想法,然后今天偶然看到一个终端利器 iTerm2,发现真的很强大,也非常的好用,按照网上配置了主题什么的,还是有些坑的,这边再记录下,以便后面查阅。

1. 安装 iTerm2

下载地址:https://www.iterm2.com/downloads.html

下载的是压缩文件,解压后是执行程序文件,你可以直接双击,或者直接将它拖到 Applications 目录下。

或者你可以直接使用 Homebrew 进行安装:

1
$ brew cask install iterm2

2. 配置 iTerm2 主题

iTerm2 最常用的主题是 Solarized Dark theme,下载地址:http://ethanschoonover.com/solarized

下载的是压缩文件,你先解压一下,然后打开 iTerm2,按Command + ,键,打开 Preferences 配置界面,然后Profiles -> Colors -> Color Presets -> Import,选择刚才解压的solarized->iterm2-colors-solarized->Solarized Dark.itermcolors文件,导入成功,最后选择 Solarized Dark 主题,就可以了。

3. 配置 Oh My Zsh

Oh My Zsh 是对主题的进一步扩展,地址:https://github.com/robbyrussell/oh-my-zsh

一键安装:

1
$ sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

安装好之后,需要把 Zsh 设置为当前用户的默认 Shell(这样新建标签的时候才会使用 Zsh):

1
$ chsh -s /bin/zsh

然后,我们编辑vim ~/.zshrc文件,将主题配置修改为ZSH_THEME="agnoster"。

agnoster是比较常用的 zsh 主题之一,你可以挑选你喜欢的主题,zsh 主题列表:https://github.com/robbyrussell/oh-my-zsh/wiki/themes

效果如下(配置了声明高亮):

4. 配置 Meslo 字体

使用上面的主题,需要 Meslo 字体支持,要不然会出现乱码的情况,字体下载地址:Meslo LG M Regular for Powerline.ttf

下载好之后,直接在 Mac OS 中安装即可。

然后打开 iTerm2,按Command + ,键,打开 Preferences 配置界面,然后Profiles -> Text -> Font -> Chanage Font,选择 Meslo LG M Regular for Powerline 字体。

当然,如果你觉得默认的12px字体大小不合适,可以自己进行修改。

另外,VS Code 的终端字体,也需要进行配置,打开 VS Code,按Command + ,键,打开用户配置,搜索fontFamily,然后将右边的配置增加"terminal.integrated.fontFamily": "Meslo LG M for Powerline",示例:

5. 声明高亮

效果就是上面截图的那样,特殊命令和错误命令,会有高亮显示。

使用 Homebrew 安装:

1
$ brew install zsh-syntax-highlighting

安装成功之后,编辑vim ~/.zshrc文件,在最后一行增加下面配置:

1
source /usr/local/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh

6. 自动建议填充

这个功能是非常实用的,可以方便我们快速的敲命令。

配置步骤,先克隆zsh-autosuggestions项目,到指定目录:

1
$ git clone https://github.com/zsh-users/zsh-autosuggestions ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions

然后编辑vim ~/.zshrc文件,找到plugins配置,增加zsh-autosuggestions插件。

注:上面声明高亮,如果配置不生效的话,在plugins配置,再增加zsh-syntax-highlighting插件试试。

有时候因为自动填充的颜色和背景颜色很相似,以至于自动填充没有效果,我们可以手动更改下自动填充的颜色配置,我修改的颜色值为:586e75,示例:

效果:

7. 左右键跳转

主要是按住option + → or ←键,在命令的开始和结尾跳转切换,原本是不生效的,需要手动开启下。

打开 iTerm2,按Command + ,键,打开 Preferences 配置界面,然后Profiles → Keys → Load Preset... → Natural Text Editing,就可以了。

8. iTerm2 快速隐藏和显示

这个功能也非常使用,就是通过快捷键,可以快速的隐藏和打开 iTerm2,示例配置(Commond + .):

9. iTerm2 隐藏用户名和主机名

有时候我们的用户名和主机名太长,比如我的xishuai@xishuaideMacBook-Pro,终端显示的时候会很不好看(上面图片中可以看到),我们可以手动去除。

编辑vim ~/.zshrc文件,增加DEFAULT_USER="xishuai"配置,示例:

我们可以通过whoami命令,查看当前用户,效果(另外分屏的效果):

10. iTerm2 配置代理

编辑~ vim ~/.zshrc,增加下面配置(使用的 shadowsocks):

1
2
3
# proxy list
alias proxy='export all_proxy=socks5://127.0.0.1:1086'
alias unproxy='unset all_proxy'

iTerm2 需要新建标签页,才有效果:

1
2
3
4
5
6
7
$ proxy
$ curl ip.cn
当前 IP:185.225.14.5 来自:美国

$ unproxy
$ curl ip.cn
当前 IP:115.236.186.130 来自:浙江省杭州市 电信

我们可以测试下:

1
2
3
4
5
6
7
8
9
10
11
12
$ curl https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64

<html>
<head>
<title>Directory listing for /yum/repos/kubernetes-el7-x86_64/</title>
</head>
<body>
<h2>Index of /yum/repos/kubernetes-el7-x86_64/</h2>
<p></p>
<a href="/yum/repos/kubernetes-el7-x86_64/repodata">repodata</a><br />
</body>
</html>

11. iTerm2 快捷命令

快捷命令说明:

命令 说明
command + t 新建标签
command + w 关闭标签
command + 数字 command + 左右方向键 切换标签
command + enter 切换全屏
command + f 查找
command + d 垂直分屏
command + shift + d 水平分屏
command + option + 方向键 command + [ 或 command + ] 切换屏幕
command + ; 查看历史命令
command + shift + h 查看剪贴板历史
ctrl + u 清除当前行
ctrl + l 清屏
ctrl + a 到行首
ctrl + e 到行尾
ctrl + f/b 前进后退
ctrl + p 上一条命令
ctrl + r 搜索命令历史

参考资料:

  • iTerm2 + Oh My Zsh + Solarized color scheme + Meslo powerline font + [Powerlevel9k] - (macOS)(推荐)
  • iTerm2 + oh my zsh + solarized + Meslo powerline font (OS X / macOS)
  • Mac 下终端配置(item2 + oh-my-zsh + solarized 配色方案)
  • MAC 下 iTerm 主题配置
  • iTerm2 快捷键大全

一些想法

发表于 2022-12-08

市场行情

疫情三年终于结束,各行各业也开始恢复之前的样子,移动互联网经历了高光期渐渐趋于稳定期再到低谷期,公司都在缩减开支,对应的人员也会减少,福利待遇也大不如从前,只希望疫情结束能迎来一个上升期.

技术思考

移动端从纯原生-混合App-RN-uniapp-Flutter经历了一系列的技术更新,大厂追求性能稳定流畅,小厂追求快速开发效率优先,使得开发者需要更高的技术应对,随着技术的不断更新移动端也不断向大前端靠拢,这就要求我们要学习更多的技术栈才能适应现在的市场

焦虑

随着年龄的增长,焦虑状态也愈发明显,是一直做技术,还是偏管理,还是都有,不管怎样我们都要有自己的核心竞争力,不然很多会被这个社会淘汰,先说这么多.

焦虑

发表于 2022-12-08

市场行情

疫情三年终于结束,各行各业也开始恢复之前的样子,移动互联网经历了高光期渐渐趋于稳定期再到低谷期,公司都在缩减开支,对应的人员也会减少,福利待遇也大不如从前,只希望疫情结束能迎来一个上升期.

技术思考

移动端从纯原生-混合App-RN-uniapp-Flutter经历了一系列的技术更新,大厂追求性能稳定流畅,小厂追求快速开发效率优先,使得开发者需要更高的技术应对,随着技术的不断更新移动端也不断向大前端靠拢,这就要求我们要学习更多的技术栈才能适应现在的市场

焦虑

随着年龄的增长,焦虑状态也愈发明显,是一直做技术,还是偏管理,还是都有,不管怎样我们都要有自己的核心竞争力,不然很多会被这个社会淘汰,先说这么多.

iOS判断iphoneX系列手机

发表于 2019-04-17

是否是iPhoneX系列手机 各种宏记录下

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
//iPhoneX系列

//是否是ipad
#define isPad ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)

//是否是X系列手机
#define IS_IPHONE (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
#define IS_IOS_11 ([[[UIDevice currentDevice] systemVersion] floatValue] >= 11.f)
#define IS_IPHONE_XSeries (IS_IOS_11 && IS_IPHONE && (MIN([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height) >= 375 && MAX([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height) >= 812))

//是否是X
#define IS_IPHONE_X ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size) && !isPad : NO)//[UIScreen mainScreen] .bounds (size = (width = 375, height = 812))

//是否是XR
#define IS_IPHONE_Xr ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(828, 1792), [[UIScreen mainScreen] currentMode].size) && !isPad : NO)//[UIScreen mainScreen] .bounds (size = (width = 414, height = 896))

//是否是XS_Max
#define IS_IPHONE_Xs_Max ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1242, 2688), [[UIScreen mainScreen] currentMode].size) && !isPad : NO)//和XR一样[UIScreen mainScreen] .bounds (size = (width = 414, height = 896))

#define k_Height_NavContentBar 44.0f

//状态栏高度
#define k_Height_StatusBar ((IS_IPHONE_X == YES || IS_IPHONE_Xr == YES || IS_IPHONE_Xs_Max == YES) ? 44.0 : 20.0)

//导航整体高度
#define k_Height_NavBar ((IS_IPHONE_X == YES || IS_IPHONE_Xr ==YES || IS_IPHONE_Xs_Max == YES) ? 88.0 : 64.0)

//home栏高度
#define k_Height_TabBar ((IS_IPHONE_X == YES || IS_IPHONE_Xr == YES || IS_IPHONE_Xs_Max == YES) ? 83.0 : 49.0)
#define k_Height_TabBar_Home ((IS_IPHONE_X == YES || IS_IPHONE_Xr == YES || IS_IPHONE_Xs_Max == YES) ? 34.0 : 0)

喜欢的收藏下, thanks !

iOS开发小技巧

发表于 2019-04-01

.UITableView的plain样式下,取消区头停滞效果

  • (void)scrollViewDidScroll:(UIScrollView *)scrollView
    {
    CGFloat sectionHeaderHeight = sectionHead.height;
    if (scrollView.contentOffset.y=0)
    {
    scrollView.contentInset = UIEdgeInsetsMake(-scrollView.contentOffset.y, 0, 0, 0);
    }
    else if(scrollView.contentOffset.y>=sectionHeaderHeight)
    {
    scrollView.contentInset = UIEdgeInsetsMake(-sectionHeaderHeight, 0, 0, 0);
    }
    }

iOS 获取汉字的拼音

  • (NSString )transform:(NSString )chinese
    {
    //将NSString装换成NSMutableString
    NSMutableString *pinyin = [chinese mutableCopy];
    //将汉字转换为拼音(带音标)
    CFStringTransform((bridge CFMutableStringRef)pinyin, NULL, kCFStringTransformMandarinLatin, NO);
    NSLog(@”%@”, pinyin);
    //去掉拼音的音标
    CFStringTransform((
    bridge CFMutableStringRef)pinyin, NULL, kCFStringTransformStripCombiningMarks, NO);
    NSLog(@”%@”, pinyin);
    //返回最近结果
    return pinyin;
    }

//阿拉伯数字转中文格式

  • (NSString )translation:(NSString )arebic
    {
    NSString str = arebic;
    NSArray
    arabic_numerals = @[@”1”,@”2”,@”3”,@”4”,@”5”,@”6”,@”7”,@”8”,@”9”,@”0”];
    NSArray chinese_numerals = @[@”一”,@”二”,@”三”,@”四”,@”五”,@”六”,@”七”,@”八”,@”九”,@”零”];
    NSArray
    digits = @[@”个”,@”十”,@”百”,@”千”,@”万”,@”十”,@”百”,@”千”,@”亿”,@”十”,@”百”,@”千”,@”兆”];
    NSDictionary dictionary = [NSDictionary dictionaryWithObjects:chinese_numerals forKeys:arabic_numerals];
    NSMutableArray
    sums = [NSMutableArray array];
    for (int i = 0; i < str.length; i ++) {
    NSString substr = [str substringWithRange:NSMakeRange(i, 1)];
    NSString
    a = [dictionary objectForKey:substr];
    NSString b = digits[str.length -i-1];
    NSString
    sum = [a stringByAppendingString:b];
    if ([a isEqualToString:chinese_numerals[9]])
    {
    if([b isEqualToString:digits[4]] || [b isEqualToString:digits[8]])
    {
    sum = b;
    if ([[sums lastObject] isEqualToString:chinese_numerals[9]])
    {
    [sums removeLastObject];
    }
    }else
    {
    sum = chinese_numerals[9];
    }
    if ([[sums lastObject] isEqualToString:sum])
    {
    continue;
    }
    }

[sums addObject:sum];
}
NSString sumStr = [sums componentsJoinedByString:@””];
NSString
chinese = [sumStr substringToIndex:sumStr.length-1];
NSLog(@”%@”,str);
NSLog(@”%@”,chinese);
return chinese;
}

图片添加水印
-(UIImage )aspjpegImage:(UIImage )img withWord:(NSString )word
{
NSString
mark = word;

int w = img.size.width;

int h = img.size.height;
//UIGraphicsBeginImageContext创建一个基于位图的上下文(context),并将其设置为当前上下文(context)
UIGraphicsBeginImageContext(img.size);

[img drawInRect:CGRectMake(0, 0, w, h)];

NSDictionary *attr = @{

NSFontAttributeName: [UIFont boldSystemFontOfSize:20], //设置字体

NSForegroundColorAttributeName : [UIColor redColor] //设置字体颜色

};
//文本绘制 attr代表文本属性
[mark drawInRect:CGRectMake(0, 10, 100, 50) withAttributes:attr]; //左上角

[mark drawInRect:CGRectMake(w - 200, 10, 200, 50) withAttributes:attr]; //右上角

[mark drawInRect:CGRectMake(w - 200, h - 50 - 10, 200, 50) withAttributes:attr]; //右下角

[mark drawInRect:CGRectMake(0, h - 50 - 10, 200, 50) withAttributes:attr]; //左下角

UIImage *newImg = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

return newImg;

}

取上整与取下整
floor(x),有时候也写做Floor(x),其功能是“下取整”,即取不大于x的最大整数 例如:
x=3.14,floor(x)=3
y=9.99999,floor(y)=9
与floor函数对应的是ceil函数,即上取整函数。

ceil函数的作用是求不小于给定实数的最小整数。
ceil(2)=ceil(1.2)=cei(1.5)=2.00
floor函数与ceil函数的返回值均为double型

给UIView设置图片
UIImage *image = [UIImage imageNamed:@”image”];
self.MYView.layer.contents = (__bridge id _Nullable)(image.CGImage);
self.MYView.layer.contentsRect = CGRectMake(0, 0, 0.5, 0.5);

41.防止scrollView手势覆盖侧滑手势
[scrollView.panGestureRecognizerrequireGestureRecognizerToFail:self.navigationController.interactivePopGestureRecognizer];

//字符串中是否含有中文

  • (BOOL)checkIsChinese:(NSString *)string
    {
    for (int i=0; i<string.length; i++)
    {
    unichar ch = [string characterAtIndex:i];
    if (0x4E00 <= ch && ch <= 0x9FA5)
    {
    return YES;
    }
    }
    return NO;
    }

Xcode清理垃圾文件

发表于 2019-03-06
  1. ~/Library/Developer/Xcode/DerivedData/

这个文件夹中保存的是Xcode的缓存文件,曾经在Xcode跑过的所有项目的索引、build的信息等都会保存在这里。删除后在下次打开项目编译的时候将会重新生成。由于这里包含了大量已经没用的项目的信息又懒得去筛选,于是把整个文件夹删了。

  1. ~/Library/Developer/Xcode/iOS DeviceSupport/

每次把一个设备接入电脑进行真机调试之前,电脑会对设备建立索引,也在此文件夹下生成对该设备系统的支持文件。于是这里存在了一堆对旧版本iOS设备支持的文件。而我最近基本只对iOS9.3的设备进行真机调试。于是删除了所有低于9.3的文件夹。

  1. ~/Library/Developer/Xcode/Archives/

每次打包App的dSYM等数据就保存在这里,把一些没用的版本删了。如果是上线了的版本还是保留吧。

  1. ~/Library/Developer/Xcode/Products/

同上,把没用的删了。

  1. ~/Library/Developer/CoreSimulator/Devices/

一堆模拟器的数据。每个文件夹里包含的就是一个特定系统版本的设备的数据。每个文件夹对应哪个设备可以在其下device.plist中查看。亲测删除之后的效果跟在模拟器里重置相同。省得一个个去重置了,删吧。

  1. ~/Library/Developer/XCPGDevices/

这里保存了playground的项目缓存。全删了。

iOS objc_msgSend详解

发表于 2019-01-02

前言
想要通过runtime发送消息,就必须要掌握runtime如何发送消息,是调用哪个函数?又是如何调用的?本篇文章只是记录笔者学习objc_msgSend函数的使用笔记,若有误解之处,还请指出。谢谢!

objc_msgSend
我们先来看看官方函数objc_msgSend的声明:

1
2
3
4
5
6
7
8
9
10
/* Basic Messaging Primitives
*
* On some architectures, use objc_msgSend_stret for some struct return types.
* On some architectures, use objc_msgSend_fpret for some float return types.
* On some architectures, use objc_msgSend_fp2ret for some float return types.
*
* These functions must be cast to an appropriate function pointer type
* before being called.
*/
void objc_msgSend(void /* id self, SEL op, ... */ )

从这个函数的注释可以看出来了,这是个最基本的用于发送消息的函数。另外,这个函数并不能发送所有类型的消息,只能发送基本的消息。比如,在一些处理器上,我们必须使用objc_msgSend_stret来发送返回值类型为结构体的消息,使用objc_msgSend_fpret来发送返回值类型为浮点类型的消息,而又在一些处理器上,还得使用objc_msgSend_fp2ret来发送返回值类型为浮点类型的消息。

最关键的一点:无论何时,要调用objc_msgSend函数,必须要将函数强制转换成合适的函数指针类型才能调用。

从objc_msgSend函数的声明来看,它应该是不带返回值的,但是我们在使用中却可以强制转换类型,以便接收返回值。另外,它的参数列表是可以任意多个的,前提也是要强制函数指针类型。

学习使用
我们建立一个类,就专门学习如何运用objc_msgSend函数来发送消息。我们建立了一个HYBMsgSend类来学习。

1.创建并初始化对象
我们一直以来都是使用类似这样的[[HYBMsgSend alloc] init]来创建并初始化对象吧,其实在编译时,这一行代码也会转换成类似如下的代码:

1
2
3
4
5
// 1.创建对象
HYBMsgSend *msg = ((HYBMsgSend * (*)(id, SEL))objc_msgSend)((id)[HYBMsgSend class], @selector(alloc));

// 2.初始化对象
msg = ((HYBMsgSend * (*)(id, SEL))objc_msgSend)((id)msg, @selector(init));

要发送消息给msg对象,并将创建的对象返回,那么我们需要强转函数指针类型。(HYBMsgSend ()(id, SEL)这是带一个对象指针返回值和两个参数的函数指针,这样就可以调用了。

2.发送无参数无返回值消息
我们先定义一个方法:

1
2
3
- (void)noArgumentsAndNoReturnValue {
NSLog(@"%s was called, and it has no arguments and return value", __FUNCTION__);
}

然后,我们发送消息,测试一下结果。

1
2
// 2.调用无参数无返回值方法
((void (*)(id, SEL))objc_msgSend)((id)msg, @selector(noArgumentsAndNoReturnValue));

结果如下,说明成功地接收到消息并处理了:

1
-[HYBMsgSend noArgumentsAndNoReturnValue] was called, and it has no arguments and return value

3.带参数不带返回值消息
我们定义一个方法只带参数,不带返回值:

1
2
3
- (void)hasArguments:(NSString *)arg {
NSLog(@"%s was called, and argument is %@", __FUNCTION__, arg);
}

然后尝试发送消息试试:

// 3.调用带一个参数但无返回值的方法

1
((void (*)(id, SEL, NSString *))objc_msgSend)((id)msg, @selector(hasArguments:), @"带一个参数,但无返回值");

同样,我们也是需要强转函数指针类型,否则会报错的。其实,只有调用runtime函数来发送消息,几乎都需要强转函数指针类型为合适的类型。

其打印结果说明成功接收到消息并处理了:

1
-[HYBMsgSend hasArguments:] was called, and argument is 带一个参数,但无返回值

4.带返回值不带参数消息
当消息带有返回值时,我们如何接收呢?我们先声明一个方法,让它带有返回值,但是不带参数:

1
2
3
4
- (NSString *)noArgumentsButReturnValue {
NSLog(@"%s was called, and return value is %@", __FUNCTION__, @"不带参数,但是带有返回值");
return @"不带参数,但是带有返回值";
}

然后发送消息,并接收返回值:

1
2
3
// 4.调用带返回值,但是不带参数
NSString *retValue = ((NSString * (*)(id, SEL))objc_msgSend)((id)msg, @selector(noArgumentsButReturnValue));
NSLog(@"5. 返回值为:%@", retValue);

打印结果说明成功地发送了消息并得到处理,且成功地获取到了返回值:

1
-[HYBMsgSend noArgumentsButReturnValue] was called, and return value is 不带参数,但是带有返回值

5.带参数带返回值的消息
定义一个带参数带普通返回值的方法:

1
2
3
4
- (int)hasArguments:(NSString *)arg andReturnValue:(int)arg1 {
NSLog(@"%s was called, and argument is %@, return value is %d", __FUNCTION__, arg, arg1);
return arg1;
}

发送消息,并接收返回值:

1
2
3
4
5
6
7
// 6.带参数带返回值
int returnValue = ((int (*)(id, SEL, NSString *, int))
objc_msgSend)((id)msg,
@selector(hasArguments:andReturnValue:),
@"参数1",
2016);
NSLog(@"6. return value is %d", returnValue);

其结果如下,说明调用成功,参数也传过去了,返回值也接收到了:

1
2
-[HYBMsgSend hasArguments:andReturnValue:] was called, and argument is 参数1, return value is 2016
6. return value is 2016

6.动态添加方法再调用
我们声明一个C语言函数:

1
2
3
4
5
// C函数
int cStyleFunc(const void *arg1, const void *arg2) {
NSLog(@"%s was called, arg1 is %s, and arg2 is %s", __FUNCTION__, arg1, arg2);
return 1;
}

这个函数并不属性对象方法,因此我们不能直接调用,但是我们可以动态地添加方法到对象中,然后再发送消息:

1
2
3
4
5
6
7
class_addMethod(msg.class, NSSelectorFromString(@"cStyleFunc"), (IMP)cStyleFunc, "v@:");
returnValue = ((int (*)(id, SEL, const void *, const void *))
objc_msgSend)((id)msg,
NSSelectorFromString(@"cStyleFunc"),
"参数1",
"参数2");
NSLog(@"7. return value is %d", returnValue);

7.带浮点返回值的消息
对于发送带浮点返回值类型的消息,我们可以使用objc_msgSend_fpret也可以使用objc_msgSend。不过这两个函数返回结果是一样的。笔者并不是很清楚,这两个函数的主要区别是什么。根据注释说明,只是说有一些处理器上,需要使用objc_msgSend_fpret来发送带浮点返回值类型的消息。

1
2
3
4
5
float retFloatValue = ((float (*)(id, SEL))objc_msgSend_fpret)((id)msg, @selector(returnFloatType));
NSLog(@"%f", retFloatValue);

retFloatValue = ((float (*)(id, SEL))objc_msgSend)((id)msg, @selector(returnFloatType));
NSLog(@"%f", retFloatValue);

8.带结构体返回值的消息
对于返回值类型为结构体的消息,我们必须使用objc_msgSend_stret而不能直接使用objc_msgSend函数,否则会crash:

// 9.返回结构体时,不能使用objc_msgSend,而是要使用objc_msgSend_stret,否则会crash

1
2
CGRect frame = ((CGRect (*)(id, SEL))objc_msgSend_stret)((id)msg, @selector(returnTypeIsStruct));
NSLog(@"9. return value is %@", NSStringFromCGRect(frame));

摘自:https://blog.csdn.net/woaifen3344/article/details/50448010
源代码:https://github.com/CoderJackyHuang/RuntimeDemo

iOS人脸识别方案浅谈

发表于 2018-12-20

注:本文没有代码,只有思想和集成的一些个人的经验和看法,想要代码可以私聊.

近段时间项目上有人脸识别的需求,所以对这个技术研究了下,查了下市面上的一些第三方SDK大多数都是收费的,想着自己做做看能不能实现,研究了一番发现,真要自己实现一套人脸识别+人脸对比+人脸搜索+活体检测技术的话真不是个小工程,自己真实现了我感觉也可以像第三方公司一样收费供别人集成开发了. 所以为了省时省力就想着接入第三方的SDK来实现,没想到到处是坑啊!!
首先说下第三方的SDK都有哪些, 百度,腾讯,阿里都看了一遍发现只有百度能行,又看了Face++,讯飞,虹软,等一些不太出名商家,发现虹软的居然免费,还有这好事,仔细研究发现没有活体检测,能满足一般的识别需求,感觉还是不太完美,face++看了下功能很齐全但收费太高了,讯飞是基于face++集成的暂时免费,识别度不太高 ,最后看了下百度的,没想到啊,买坑刚开始,首先是iOS版本SDK下载必须是企业认证,你妹的我还得要公司的营业执照,弄了半天搞到了 上传完审核半天没通过,卧槽啊,给客服打电话问为啥 ,重新又传了一遍终于通过了 ,想着能下载了,居然还不行,要填详细信息再去审核,审核通过后才能下载 ,卧槽啊 下载个sdk还费劲啊 折腾大半天还没下载到sdk,提交了审核信息,说是1-5个工作日出结果 ,瞬间崩溃了,又给人工打电话,让他帮忙催下,又等了大半天终于通过了,才开始下载SDK,心好累啊,到此只是把SDK和demo下载下来.
下载完开始研究demo,Demo写的还可以,能满足我们要的需求,但需求大的话也是收费的,领导不是很满意,我只能先研究咋集成,整体感觉还行 很简单的就能集成到自己的项目.不知道性能咋样 ,还需要详细测试,总结一下,如果只是简单的人脸识别建议自己写不要用第三方,可以看下openCV和系统自带的都可以,如果是要实现人脸识别+活体检测就有点复杂了,也可以自己写,就是不太灵敏,(可以研究下openCV很强大的图像算法框架),最后我还是放弃了,用了第三方SDK来实现 ,总的来说百度的还是不错的,能满足绝大多数需求,face++也不错识别度好像是最好的 但有点贵 听说支付宝就是用的他家的,虹软的也nice 除了活体检测还没开放(Android开放了),其他都挺好的,关键是免费啊,哈哈 ,以上是我自己的一些看法,如有异议,欢迎指导批评.

百度人脸识别考勤
https://cloud.baidu.com/product/face/collect 演示
https://ai.baidu.com/support/video 视频演示
https://cloud.baidu.com/product/face/collect
http://ai.baidu.com/docs#/FaceAttendance/top
http://ai.baidu.com/docs#/Face-Pricing/top

iOS 消息发送与转发详解

发表于 2018-12-06

Objective-C 是一门动态语言,它将很多静态语言在编译和链接时期做的事情,放到了运行时来处理。之所以能具备这种特性,离不开 Runtime 这个库。Runtime 很好的解决了如何在运行时期找到调用方法这样的问题。

消息发送

在 Objective-C 中,方法调用称为向对象发送消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// MyClass 类
@interface MyClass: NSObject
- (void)printLog;
@end
@implementation MyClass
- (void)printLog {
NSLog(@"print log !");
}
@end

MyClass *myClass = [[MyClass alloc] init];
[myClass printLog];

// 输出: print log !

上面代码中的 [myClass printLog] 也可以这么写:

1
((void (*)(id, SEL))(void *) objc_msgSend)(myClass, @selector(printLog));

[myClass printLog] 经过编译后就是调用 objc_msgSend 方法。

我们看看这个方法的文档定义:

1
id objc_msgSend(id self, SEL op, ...);

self:消息的接收者 op: 消息的方法名,C 字符串 … :参数列表

Runtime 是如何找到实例方法的具体实现的?

基础概念

讲之前,我们需要先明白一些基础概念:Objective-C 是一门面向对象的语言,对象又分为实例对象、类对象、元类对象以及根元类对象。它们是通过一个叫 isa 的指针来关联起来,具体关系如下图:
Snip20181129_4.png

以我们上文的代码为例:

1
MyClass *myClass = [[MyClass alloc] init];

整理下相互间的关系:

  • myClass 是实例对象
  • MyClass 是类对象
  • MyClass 的元类的元类就是 NSObject 的元类
  • NSObject 就是 Root class (class)
  • NSObject 的 superclass 为 nil
  • NSObject 的元类就是它自己
  • NSObject 的元类的 superclass 就是 NSObject

对应上图中的位置关系如下:
Snip20181129_3.png

接着,我们用代码来验证下上文的关系:

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
MyClass *myClass = [[MyClass alloc] init];

Class class = [myClass class];
Class metaClass = object_getClass(class);
Class metaOfMetaClass = object_getClass(metaClass);
Class rootMetaClass = object_getClass(metaOfMetaClass);
Class superclass = class_getSuperclass(class);
Class superOfSuperclass = class_getSuperclass(superclass);
Class superOfMetaOfSuperclass = class_getSuperclass(object_getClass(superclass));

NSLog(@"MyClass 实例对象是:%p",myClass);
NSLog(@"MyClass 类对象是:%p",class);
NSLog(@"MyClass 元类对象是:%p",metaClass);
NSLog(@"MyClass 元类对象的元类对象是:%p",metaOfMetaClass);
NSLog(@"MyClass 根元类对象是:%p",rootMetaClass);
NSLog(@"MyClass 父类是:%@",class_getSuperclass(class));
NSLog(@"MyClass 父类的父类是:%@",superOfSuperclass);
NSLog(@"MyClass 父类的元类的父类是:%@",superOfMetaOfSuperclass);

NSLog(@"NSObject 元类对象是:%p",object_getClass([NSObject class]));
NSLog(@"NSObject 父类是:%@",[[NSObject class] superclass]);
NSLog(@"NSObject 元类对象的父类是:%@",[object_getClass([NSObject class]) superclass]);

//输出:
MyClass 实例对象是:0x60c00000b8d0
MyClass 类对象是:0x109ae3fd0
MyClass 元类对象是:****0x109ae3fa8
MyClass 元类对象的元类对象是:****0x10ab02e58**
MyClass 根元类对象是:0x10ab02e58
MyClass 父类是:NSObject
MyClass 父类的父类是:(null)
MyClass 父类的元类的父类是:NSObject
NSObject 元类对象是:0x10ab02e58
NSObject 父类是:(null)
NSObject 元类对象的父类是:NSObject

可以发现,输出结果是完全符合我们的结论的!

现在我们能知道各种对象之间的关系:

实例对象通过 isa 指针,找到类对象 Class;类对象同样通过 isa 指针,找到元类对象;元类对象也是通过 isa 指针,找到根元类对象;最后,根元类对象的 isa 指针,指向自己。可以发现 NSObject 是整个消息机制的核心,绝大数对象都继承自它。

寻找流程

上文提到了,一个 Objective-C 方法会被编译成 objc_msgSend,这个函数有两个默认参数,id 类型的 self, SEL 类型的 op。我们先看看 id 的定义:

1
2
3
4
5
typedef struct objc_object *id;

struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};

我们可以看到,在 objc_object 结构体中,只有一个指向 Class 类型的 isa 指针。

我们再看看 Class 的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

里面有很多参数,很显眼的能看到这一行:

1
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;

看名字也容易理解,这个 methodLists 就是用来存放方法列表的。我们再看看 objc_method_list 这个结构体:

1
2
3
4
5
6
7
8
9
10
struct objc_method_list {
struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE;

int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}

里面的 objc_method ,也就是我们熟悉的 Method:

1
2
3
4
5
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}

Method 里面保存了三个参数:

  • 方法的名称
  • 方法的类型
  • 方法的具体实现,由 IMP 指针指向

经过层层挖掘,我们能明白实例对象调用方法的大致逻辑:

1
2
MyClass *myClass = [[MyClass alloc] init];
[myClass printLog];

  • 先被编译成 ((void ()(id, SEL))(void ) objc_msgSend)(myClass, @selector(printLog));
  • 沿着入参 myClass 的 isa 指针,找到 myClass 的类对象(Class),也就是 MyClass
  • 接着在 MyClass 的方法列表 methodLists 中,找到对应的 Method
  • 最后找到 Method 中的 IMP 指针,执行具体实现

类对象的类方法又是怎么找到并执行的?

由上文,我们已经知道,实例对象是通过 isa 指针,找到其类对象(Class)中保存的方法列表中的具体实现的。

比如:

1
2
MyClass *myClass = [[MyClass alloc] init];
[myClass printLog];

可以理解为:printLog 方法就是保存在 MyClass 中的。

那么如果是个类方法,又是保存在什么地方的呢?

我们回顾下 Class 的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

可以发现到这一行:

1
Class _Nonnull isa OBJC_ISA_AVAILABILITY;

这里的 isa 同样是指向一个 Class 的指针。上文中,我们也知道了类对象的 isa 指针是指向元类对象的。那么不难得出:

类对象的类方法,是保存在元类对象中的!

类对象和元类对象都是 Class 类型,仅仅服务的对象不同罢了。找到了元类对象,自然就找到了元类对象中的 methodLists,接下来就和实例对象的方法寻找调用一样的流程了。

如何提高方法查找的效率?

上文中,我们大概知道,方法是通过 isa 指针,查找 Class 中的 methodLists 的。如果子类没实现对应的方法实现,还会沿着父类去查找。整个工程,可能有成万上亿个方法,是如何解决性能问题的呢?

例如:

1
2
3
4
for (int i = 0; i < 100000; ++i) {
MyClass *myObject = myObjects[i];
[myObject methodA];
}

这种高频次的调用 methodA,如果每调用一次都需要遍历,性能是非常差的。所以引入了 Class Cache 机制:

Class Cache 认为,当一个方法被调用,那么它之后被调用的可能性就越大。

查找方法时,会先从缓存中查找,找到直接返回 ;找不到,再去 Class 的方法列表中找。

在上文中 Class 的定义中,我们可以发现 cache:

1
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;

说明了缓存是存在类中的,每个类都有一份方法缓存,而不是每个类的 object 都保存了一份。

关于父类(superclass)

在 Objective-C 中,子类调用一个方法,如果没有子类没有实现,父类实现了,会去调用父类的实现。上文中,找到 methodLists 后,寻找 Method 的大致过程如下:

Snip20181129_2.png

ps: 其实这里的寻找过程远没有这么简单,可能会遍历很多遍,因为我们可能会在运行时动态的添加方法(比如 category)。遍历的过程中同样不时的去查询缓存表。

消息转发


如果方法列表(methodLists)没找到对应的 selector 呢?

// ViewController.m 中 (未实现 myTestPrint 方法)

1
[self performSelector:@selector(myTestPrint:) withObject:@",你好 !"];

系统会提供三次补救的机会。

第一次
1
2
+ (BOOL)resolveInstanceMethod:(SEL)sel {} (实例方法)
+ (BOOL)resolveClassMethod:(SEL)sel {} (类方法)

这两个方法,一个针对实例方法;一个针对类方法。返回值都是 Bool。

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ViewController.m 中

void myMethod(id self, SEL _cmd,NSString *nub) {
NSLog(@"ifelseboyxx%@",nub);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (sel == @selector(myTestPrint:)) {
#pragma clang diagnostic pop
class_addMethod([self class],sel,(IMP)myMethod,"v@:@");
return YES;
}else {
return [super resolveInstanceMethod:sel];
}
}

我们只需要在 resolveInstanceMethod: 方法中,利用 class_addMethod 方法,将未实现的 myTestPrint: 绑定到 myMethod 上就能完成转发,最后返回 YES。

第二次
1
- (id)forwardingTargetForSelector:(SEL)aSelector {}

这个方法要求返回一个 id。使用场景一般是将 A 类的某个方法,转发到 B 类的实现中去。

使用示例:

想转发到 Person 类中的 -myTestPrint: 方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@interface Person : NSObject
@end

@implementation Person
- (void)myTestPrint:(NSString *)str {
NSLog(@"ifelseboyxx%@",str);
}
@end
复制代码
// ViewController.m 中

- (id)forwardingTargetForSelector:(SEL)aSelector {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (aSelector == @selector(myTestPrint:)) {
#pragma clang diagnostic pop
return [Person new];
}else{
return [super forwardingTargetForSelector:aSelector];
}
}

第三次
1
2
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {}
- (void)forwardInvocation:(NSInvocation *)anInvocation {}

第一个要求返回一个方法签名,第二个方法转发具体的实现。二者相互依赖,只有返回了正确的方法签名,才会执行第二个方法。

这次的转发作用和第二次的比较类似,都是将 A 类的某个方法,转发到 B 类的实现中去。不同的是,第三次的转发相对于第二次更加灵活,forwardingTargetForSelector: 只能固定的转发到一个对象;forwardInvocation: 可以让我们转发到多个对象中去。

使用实例:

想转发到 Person 类以及 Animal 类中的 -myTestPrint: 方法中:

1
2
3
4
5
6
7
8
@interface Person : NSObject
@end

@implementation Person
- (void)myTestPrint:(NSString *)str {
NSLog(@"ifelseboyxx%@",str);
}
@end

1
2
3
4
5
6
7
8
@interface Animal : NSObject
@end

@implementation Animal
- (void)myTestPrint:(NSString *)str {
NSLog(@"tiger%@",str);
}
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ViewController.m 中

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (aSelector == @selector(myTestPrint:)) {
#pragma clang diagnostic pop
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
Person *person = [Person new];
Animal *animal = [Animal new];
if ([person respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:person];
}
if ([animal respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:animal];
}
}

⚠️ 如果到了第三次机会,还没找到对应的实现,就会 crash:

1
unrecognized selector sent to instance 0x7f9f817072b0

总结

到这里,我们大概能了解消息发送与转发的过程了。整理了下大致的流程,有问题欢迎大家积极提出来:

Snip20181129_1.png

摘自: https://juejin.im/post/5aa79411f265da237a4cb045

谢谢!

Objective-C初始化对象的时候为什么要写:self = [super init]

发表于 2018-11-28

Objective-C初始化对象的时候为什么总是有一句:if (self = [super init])?

众所周知,Objective-C是一门面向对象的语言,一般情况下,我们在Objective-C中定义一个类时,总要提供一个初始化方法,一般大家都是这样写的:

  • (MyClass *)init
    {
    self = [super init];
    if (self) {
    //执行一些资源、变量的初始化工作
    }
    return self;
    }
    这样一段简单的代码,却有很多可以思考的问题:
    1、为什么要通过[super init]来调用父类的初始化方法,父类的初始化方法里又执行了什么东西?

首先,我们知道对象继承的概念,一个子类从父类继承,那么也要实现父类的所有功能,这就是is-a的关系,比如说狗是哺乳动物,那么狗必定具有哺乳动物的特征和功能。所以在子类的初始化方法中,必须首先调用父类的初始化方法,以实现父类相关资源的初始化。例如我们在初始化狗这一对象时,必须先初始化哺乳动物这一对象,并把结果赋予狗,以使狗满足属于哺乳动物这一特征。

典型的,在iOS下,所有的类都继承于NSObject,而NSObject的init方法很简单,就是return self。当父类的初始化完成之后,即self不为nil的情况下,就可以开始做子类的初始化了。

2、是否一定要提供初始化方法,是否一定要使用init作为初始化方法?

我们在Objective-C中创建一个对象通常使用

MyClass newclass = [[MyClass alloc] init];
或者
MyClass
newclass = [Myclass new];
new方法是NSObject对象的一个静态方法,根据apple的文档,该方法实际上就是alloc和init方法的组合,实际上二者是一样的,但 apple还是推荐我们使用第一种方法,为什么呢?因为使用第一种方法,你可以使用自己定义的init方法来做一些初始化,当然,如果子类没有提供 init方法,自然调用的就是父类的init方法了。所以说,从安全性的角度来收,作为开发者我们在对象使用之前是一定要对对象进行初始化的,因此在定义类的时候一定要提供初始化方法。但是否一定要使用init作为方法名呢?答案是不一定。使用init作为方法名只是你重写了NSObject的init方法而已,如果你自己重新定义一个初始化方法,也是完全可以的,只要你在使用的时候记得调用新定义的初始化方法就可以了。
但是,这种方法从设计角度来看我觉得是不可取的。在可复用性方面会比较差,如果确有必要定义一些接受不同参数的初始化方法,我的建议是,先定义一个init的公用方法,再到其他方法中调用它,如:

  • (id)init
    {
    self = [super init];
    if (self) {

}
return self;
}

  • (id)initWithString:(NSString *)aString
    {
    [self init];
    self.name = aString;
    }

  • (id)initWithImage:(UIImage *)aImage
    {
    [self init];
    self.image = aImage;
    }
    补充:
    在面向对象编程中,如果编写一个类而没有包含构造函数,这个类仍能编译并且完全可以正常使用。如果类没有提供显式的构造函数,编译器会提供一个默认的构造函数给你。除了创建对象本身,默认构造函数的唯一工作就是调用其超类的构造函数。在很多情况下,这个超类是语言框架的一部分,如java中的 Object类,objective-c 中的NSObject类。

不论是何种情况,在类中至少包含一个构造函数是一种很好的编程实践,如果类中有属性,好的实践往往是初始化这些属性。

——以上摘自《The Object-Oriented Thought Process》 by Matt Weisfeld

12

likuan

19 日志
© 2022 likuan
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4