Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iTeaTime(技术清谈)【006期】【代号:布加迪】 #7

Open
ChenYilong opened this issue Jul 16, 2019 · 0 comments
Open

iTeaTime(技术清谈)【006期】【代号:布加迪】 #7

ChenYilong opened this issue Jul 16, 2019 · 0 comments
Assignees

Comments

@ChenYilong
Copy link
Member

iTeaTime(技术清谈)【006期】【代号:布加迪】



出题:微博@iOS程序犭袁 和他的小伙伴们
本期代号:布加迪

enter image description here


【今日话题】: 你如何看待张小龙的预言:未来2年内,小程序将取代80%的App市场。每次都能听到“2岁多的小程序,终于又双叒迎来了“春天”?”的声音,似乎native每天都在冬天,你会因为市场的影响而更新技术栈,或者调整编程的百分比,为前端多多增加百分比吗?未来前端在你的占比中多少比较合适?


1.【问题】【iOS】参考代码注释内容:

int main(int argc, const char * argv[]) {
   //在这里插入一行代码,使下面的代码输出 "Goodbye World"
   NSLog(@"Hello World");
   return 0;
}

【 难度🌟🌟🌟🌟】【出题人 孙源Sunny@dd】

【答案】

几种 tricky 版本:

重定义 NSLog

以下几种宏定义皆可:

   #define NSLog(FORMAT, ...) fprintf(stderr, "Goodbye World\n")
   #define NSLog(x) printf("Goodbye World\n")
   #define NSLog NSLog(@"Goodbye World");

[鹅喵-便利蜂移动端]:

voidNSLog)(id)=(id _){CFLog(0, CFSTR("Goodbye World"));};

利用编译器的注释特性

[鹅喵-便利蜂移动端]:

NSLog("Goodbye World"); //\
NSLog(@"Hello World"); 

下面着重介绍两种,更有技术含量的版本:

深入理解 NSString

NSString 的内存分配实际是很复杂的,可能分配在栈区、堆区、常量区。

我们常常以为@"foo",这样的字符串是常量区(也称为常量存储区或 _TEXT 区。),运行时不能改,内存区域映射都是 dyld 干的。

其实我们可以简单理解为 NSString 是一个 map 结构,key 存在常量区,的确无法修改,但是 value 是一个静态变量,我们可以运行时修改。

常量字符串的内存复制方案

[molon-杭州]提供思路:

首先 Objective-C 中任何两个相同字符串值的声明,即使是存储在不同的变量名中,也是指向同一个对象。

常量区的变量内存地址是连续的。

而常量字符串的指针在 CFString 段里面,内存复制的话只复制 CFString 的 size 就够了。

直接操作内存就可修改,只要有权限,内存当中的任何地方都能操作。程序运行起来,可以理解为其汇编代码也是写入内存的。你想靠修改对应位置,塞入汇编代码,修改运行中逻辑都可以。很多计算机病毒就是这么做的。但是一般操作系统提供的 API 会做一定的权限控制,例如不能修改其他进程的,等等,但是只要你能提权也是可以操作,很多外挂、破解基本上都是这么个原理。相对来说, Objective-C 反而显得不安全,对 hook 的亲和力太特么强。像其他语言一般还需要用内联汇编去做层 inline hook,控制堆栈平衡等,而 Objective-C 却没有。

比如如果逆向以下代码:

int main(int argc, const char * argv[]) {

   NSString *a = @"Hello world";
   NSString *b = @"bye";
   memmove((void *)(@"Hello world"),(void *)(@"Goodbye world"),17);//此处17为随意填写,并无特定含义,请查看下文完整的取值计算方案。
   NSString *c = @"ok";

   NSLog(@"Hello world");
   NSLog(@"ok");
   NSLog(@"bye");
   return 0;
}

那么,下图红框部分即为 Objective-C 的常量区:

(逆向结果由[molon-杭州]提供)

enter image description here

常量区:

enter image description here

然后这个常量区 CFString 里对应的 char指针对应的那块内存区域也是连续的。

上述代码中 memmove 拷贝的是 CFString 的内容,不是这块区域的。

首先从<CoreFoundation/CFString.h> 可以查看 CFString 结构体,

可以发现其大小跟 CPU 架构有关,结构并不简单,而且 __CFConstStr 属于内部 API 无法访问,所以我们可以模仿重新定义一个类似的结构:

以下方案由[Never-成都iOS]提供:

#import <Cocoa/Cocoa.h>

struct CYLConstStr {
   struct {
       uintptr_t _cfisa;
       uint32_t _swift_strong_rc;
       uint32_t _swift_weak_rc;
       uint8_t _cfinfo[4];
       uint8_t _pad[4];
   } _base;
   uint8_t *_ptr;
#if defined(__LP64__) && defined(__BIG_ENDIAN__)
   uint64_t _length;
#else
   uint32_t _length;
#endif
};

int main(int argc, const char * argv[]) {
   //在这里插入一行代码,使下面的代码输出 "Goodbye World"
   memmove((void *)(@"Hello World"), (void *)(@"Goodbye World"), sizeof(struct CYLConstStr));
   NSLog(@"Hello World");
   return 0;
}

运行结果:

enter image description here

参考文献:

修改 CFString __DATA 段 方案

[孙源Sunny@dd] 提供思路:

cstring 存在 __TEXT ,是不可变区域,CFString 存在 __DATA,可以修改,该情况与修改 static 变量没什么区别。

CFString__DATA 端我觉得原因是它的结构里有个 isa 指针指向了 __CFConstantStringClassReference ,这种在编译时无法确定,得在动态链接时才知道这个地址并赋值上去。

根据 WWDC 2016 - Session 406-Optimizing App Startup Time 的说明:

enter image description here

可知:

区域 作用
__TEXT ,是不可变区域,CFString 存在 __DATA,可以修改,该情况与修改 static 变量没什么区别。

CFString__DATA 端我觉得原因是它的结构里有个 isa 指针指向了 __CFConstantStringClassReference ,这种在编译时无法确定,得在动态链接时才知道这个地址并赋值上去。

根据 WWDC 2016 - Session 406-Optimizing App Startup Time 的说明:

enter image description here

可知:

区域 作用
__DATA 头文件,代码,只读常量
__DATA 所有可读写内容(全局变量、静态变量等等)

举例说明 NSString_TEXT_DATA 三者的关系,

比如 《iOS控制代码段大小的一些经验》 一文中提到:

如果定义一个 NSString 数组,元素数量庞大,可能会导致 __TEXT 代码段非常大。仅仅下面的代码,生成的目标文件大小就可以达到 89.51KB。具体原因并不是字符串的总字节数多,而是元素数量大,中间的取值指令过多。
代码:

enter image description here

用到的宏定义:

enter image description here

尽管编译器只在text段的字符常量区生成一份字符,多次使用不会增加字符常量区的大小,但是会增加 __TEXT 段代码的大小,使用 MachOView 工具查看可执行文件,结果如下图:

enter image description here

cstring 只有一份,多次调用不会存在多份,但是再通过反编译查看调用语句:

enter image description here

调用的函数会转变成上图的指令,可以看到是从字符常量区取到 rax, 再取到栈空间。这样一个一个的取,由于有非常多个字符串,那么相应的指令就会存在非常多,导致调用函数的代码段变得非常大。而且这样会非常浪费栈空间。

总结就是:

在使用 NSString 时,cstring 只有一份,多次调用不会存在多份,但调用的函数中间还有一步指令,是从字符常量区取到 rax, 再取到栈空间,rax对应的值保存在 _DATA 中。

上面的题目,修改的就是 _DATA 中的值。

cstring 才是 __TEXT
cfstring 是 __DATA

CFString__DATA 端我觉得原因是它的结构里有个 isa 指针指向了 __CFConstantStringClassReference,这种在编译时无法确定,得在动态链接时才知道这个地址并赋值上去.

[鹅喵-便利蜂移动端]提供代码实现:

int main(int argc, const char * argv[]) {
   typedef struct STR {
       size_t padding[2];
       char const *s;
       size_t len;
   }STR;
   STR *str =(STR *)@"Hello World";
   str->s = "GoodBy World";
   NSLog(@"Hello World");
   return 0;
}

enter image description here

一行代码模式:

// 改 cstring 的方式
int main(int argc, char **argv) {
 typedef struct STR { size_t padding[2]; char const *s; size_t len;} STR; STR *goodbye = (STR*)@"Hello World"; goodbye->s = "Goodbye World"; goodbye->len = 13;
 NSLog(@"Hello World");
 return 0;
}

运行结果:

enter image description here

Apple 的 opensource 的 CF 是 CFLite,跟生产环境的不一样,可以参考其 README 说明:

Apple 的 opensource 版本:

enter image description here

生产环境版本:

enter image description here


2【iOS】有没有办法通过提供的ipa包然后判断出是支持ipad还是iphone,还是都支持。【 难度🌟🌟】【出题人 SuperDanny-轩宇-珠海iOS】

把IPA解压,包内容的info.plist有个UIDeviceFamily,值=1是iPhone,=2是iPad,=1,2是都支持

/usr/libexec/PlistBuddy -c 'Print :UIDeviceFamily' xxx.app/Info.plist

enter image description here

[鹅喵-便利蜂移动端][M.W-不知名小作坊-iOS-北京]回答正确


3 【算法】使用熟悉的编程语言,编写一个函数,要求输入与以下列表相似的结构,则返回值为true,否则为false。【注意】输入字符串无限制,英文字符、标点符号均无需特殊处理,与中文一样视为作完整字符。

  • 上海自来水来自海上
  • 黄山落叶松叶落山黄
  • 山东落花生花落东山
  • 山西悬空寺空悬西山
  • 湖南过路车路过南湖

【 难度🌟🌟🌟】【出题人 微博@iOS程序犭袁】

【提示】算法实际为回文算法,题目主要在于边界情况处理,在函数顶端要有判空等操作。可自行搜索,下面提供群里提供的答案,如有瑕疵可以指正。

【答案】

python3 的,天生编码支持好,递归做法:

def check_re(str):
if not str:
 return True
if str[0] != str[-1]:
 return False
else:
 return check_re(str[1:-1])

print(check_re("asdffdsa) //True
print(check_re("asdfdsa)) //True
print(check_re("上海自来水来自海上)) //True
print(check_re(黄山落叶松叶落山黄")) //True
print(check_re("山东落花生花落东山")) //True
print(check_re("山西悬空寺空悬西山") //True
print(check_re("湖南过路车路过南湖")) //True
print(check_re("123456")//False

jS版本:

function judgeIsPalindrome (str) {
if (null == str || str.length < 2) {
return 'false';
} else {
if (str.split(""). reverse().join("")= str) {
return 'true';
} else {
return 'false';
}
}
}
console.log(judgeIsPalindrome('🍎🍌🍇🍇🍌🍎'));
console. log(judgeIsPalindrome('abccba'));

输出结果:

enter image description here

另一 java 版本:

// still 3 
public static boolean isPalindrome(String s) {
 if (s == null) return false;
 int left = 0; int right = s.length()-1;
 while (left < right) {
  if (s.charAt(left) == ' ') left++;
  else if (s.charAt(right) == ' ') right--;
  else if (s.charAt(left) != s.charAt(right)) return false;
  left++;
  right--;
 }
 return true;
}

[Jenny]回答。

边界情况,可酌情添加,为加分项目:

  • 字符里面也许应该排除一下 ZWJ 组合 emoji
  • 不考虑 unicode 组合字的话,Pokémon 这种字倒过来就会变成 noḿekoP,注音符号跑偏了,所以至少是标准库或第三方库要有良好的 unicode 支持 (鹅喵-便利蜂移动端)

4.【算法】要求使用熟悉的编程语言,编写一个函数,要求输入任意字符串,都能返回对称结构的字符串。【注意】输入字符串无限制,英文字符、标点符号均无需特殊处理,与中文一样视为作完整字符。

举例:

输入的原始字符串:

  • 走马灯灯马走灯熄马停步

输出的字符串:

  • 走马灯灯马走

【 难度🌟🌟🌟】【出题人 微博@iOS程序犭袁】

【答案】 详细可以搜:最长回文子串算法。

下面是一种时间复杂度为 O(n^2)的写法,并非最优解,最优解为 Manacher 算法, 时间复杂度O(n), 空间复杂度O(n),可自行搜索。:

public static void main(String[] args) {
       // write your code here
       String str = "步停马熄灯走马灯灯马走你";
       String str1 = "qqqqbcddcbasf";
       String str2 = "abcdeff";
       String str3 = "11118889999888";
       String str4 = "5544334433111";

       System.out.println(getStr(str));
       System.out.println(getStr(str1));
       System.out.println(getStr(str2));
       System.out.println(getStr(str3));
       System.out.println(getStr(str4));
   }

   static String getStr(String source) {
       if (source == null || source.length() < 1) {
           return "\n";
       }
       for (int i = source.length() / 2; i > 0 ; i--) {
           StringBuilder builder = new StringBuilder();
           builder.append(String.join("", Collections.nCopies(i, "(.)")));
           for (int j = i; j > 0; j--) {
               builder.append("\\" + j);
           }
           String patternStr = builder.toString();
           Pattern pattern = Pattern.compile(patternStr);
           Matcher m = pattern.matcher(source);
           if (m.find()) {
               return m.group(0);
           }
       }
       return "\n";
   }

5【iOS】看下面的方法执行完之后 ViewController 会被销毁吗,ViewController 的 view 会被销毁吗?为什么?

- (void)addViewController { 
UIViewController *viewController = [[UIViewContrnller alloc] init];
[self.view addSubview: viewController.view]; 
}
…

【 难度🌟🌟】【出题人 Saber-ios-望京】

【答案】view被引用,vc没被引用,所以VC被销毁,view不销毁。

详细解释:
vc引用view,view对vc无引用。 vc在view在,view在与vc可不在。vc为局部变量,方法结束后直接销毁;vc.view被添加在self.view上,所以不会被销毁.


6【iOS】请给出以下代码的输出结果:

NSArray *array = @[@"1"]; 

NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithObjects:@"123’, nil];

void(ˆblock)(void) = ˆ{ 
NSLog(@"array %@",array); 
NSLog(@"mutableArray %@",mutableArray);
};
array = @[@"2"]; 
[mutableArray add0bject:@"456"];
block(); 

【 难度🌟】【出题人 微博@iOS程序犭袁 】

【答案】

array 1
mutableArray 123,456

参考: 《iOS中__block 关键字的底层实现原理》


7【问题】【iOS】segment 页面如何进行内存优化。需求描述:segment一次性把所有页面加载出来会导致内存爆增。几个segment 子页面的图片都是高清大图。【难度🌟🌟🌟】【出题人:中鼎iOS攻城狮】

下面答案由【BM-成都iOS】提供:

题目主要有2个核心:1.多个页面 2.高清大图

针对多个页面,用lazy的形式,用时加载就好了。所以题目主要是讨论高清大图。

作为另外一个补充:

关于高清大图上传问题。

  1. 理论应该尽可能还原用户创造的原始数据。比如用户上传一个头像,虽然现实控件只有100像素。但是理论上不应该客户端直接把图片压缩了上传给服务器,万一以后这张头像会作为背景图呢?

  2. 关于流程问题。很多对于资源类型的上传直接使用后台接口上传data。

    但是有一个问题是,整个过程是这样的:1.客户端上传data给后台主机。2.主机拿到图片上传到对象服务器。3.对象服务器给主机回调。4.主机给客户端上传结果回调。(举例一个栗子:3个角色,你,财务,人事。(事情是核对你的工资是否正确) 难道不觉得这样的一个流程很傻逼么?流程:财务给你发工资,然后你拿到钱,找人事问应该给你发多少,如果发错了,你再找财务说给你发错了,重新核对。 过程完全可以优化为:财务向人事核对工资是否正确,然后给你发工资

    所以,整个过程的标准流程为:向服务器请求token,Bucket,路径等一系列参数。由客户端直接向对象存储器上传,上传成功以后请求接口告知主机上传成功。在这个过程中,主流的对象存储器的sdk都有断点续传等乱七八糟的优化。会一定范围内解决上传内存暴增情况


Posted by 微博@iOS程序犭袁
原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0


One more thing...

【非礼勿视】以下为彩蛋部分,建议28岁以上男性观看


image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant