草庐IT

iphone - SFHFKeychainUtils。 iOS 钥匙串(keychain)。弧兼容

coder 2023-07-26 原文

我想知道是否有人使用 SFHFKeychainUtils 设法修改它们以与 ARC 兼容。更准确地说是

NSDictionary *attributeResult = NULL;
NSMutableDictionary *attributeQuery = [query mutableCopy];
[attributeQuery setObject: (id) kCFBooleanTrue forKey:(__bridge id) kSecReturnAttributes];
OSStatus status = SecItemCopyMatching((CFDictionaryRef) attributeQuery,(CFTypeRef *)(attributeResult));

我试过了

OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) attributeQuery,(CFTypeRef *)(attributeResult));

还有

CFTypeRef subAttributeResult = (CFTypeRef *)(objc_unretainedPointer(attributeResult));
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) attributeQuery,(CFTypeRef *)(subAttributeResult));

这 2 种是我设法没有出错的仅有的 2 种方法。通过在此处添加 objc_XXX 而不是 CFTypeRef 的任何其他方法都会出现错误(从 obj-c 指针到 CFTypeRef 的隐式转换在 ARC 中是不允许的,到将 x 参数传递给 y 参数会丢弃限定符)。显然,第一段代码也给出了错误。虽然我在构建时没有遇到任何错误,但当到达这部分代码时应用程序崩溃并显示 EXC_BAD_ACCESS。

完整 SFHFKeychainUtils 的链接:https://github.com/ldandersen/scifihifi-iphone/tree/master/security

有什么帮助吗? 谢谢。

最佳答案

这里是 ARC 兼容的 SFHFKeychainUtils,

SFHFKeychainUtils.h

//
//  SFHFKeychainUtils.h
//
//  Created by Buzz Andersen on 10/20/08.
//  Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone.
//  Copyright 2008 Sci-Fi Hi-Fi. All rights reserved.
//
//  Permission is hereby granted, free of charge, to any person
//  obtaining a copy of this software and associated documentation
//  files (the "Software"), to deal in the Software without
//  restriction, including without limitation the rights to use,
//  copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following
//  conditions:
//
//  The above copyright notice and this permission notice shall be
//  included in all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//  OTHER DEALINGS IN THE SOFTWARE.
//

#import <UIKit/UIKit.h>


@interface SFHFKeychainUtils : NSObject {

}

+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error;
+ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;

@end

SFHFKeychainUtils.m

//  
//  SFHFKeychainUtils.m 
//

//  Created by Buzz Andersen on 10/20/08.   
//  Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone.    
//  Copyright 2008 Sci-Fi Hi-Fi. All rights reserved.   
//

//  Permission is hereby granted, free of charge, to any person 
//  obtaining a copy of this software and associated documentation  
//  files (the "Software"), to deal in the Software without 
//  restriction, including without limitation the rights to use,    
//  copy, modify, merge, publish, distribute, sublicense, and/or sell   
//  copies of the Software, and to permit persons to whom the   
//  Software is furnished to do so, subject to the following    
//  conditions: 
//

//  The above copyright notice and this permission notice shall be  
//  included in all copies or substantial portions of the Software. 
//  
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND    
//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,    
//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING    
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR   
//  OTHER DEALINGS IN THE SOFTWARE. 
//

#import "SFHFKeychainUtils.h"   
#import <Security/Security.h>   


static NSString *SFHFKeychainUtilsErrorDomain = @"SFHFKeychainUtilsErrorDomain";

#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR

@interface SFHFKeychainUtils (PrivateMethods)   
(SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error; 
@end

#endif

@implementation SFHFKeychainUtils

#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR

 +(NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {       
      if (!username || !serviceName) {          
           *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];          
            return nil;         
          }      

      SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
      if (*error || !item) {            
            return nil;         
          }

      // from Advanced Mac OS X Programming, ch. 16     
      UInt32 length;        
      char *password;       
      SecKeychainAttribute attributes[8];       
      SecKeychainAttributeList list;
      attributes[0].tag = kSecAccountItemAttr;      
      attributes[1].tag = kSecDescriptionItemAttr;      
      attributes[2].tag = kSecLabelItemAttr;
      attributes[3].tag = kSecModDateItemAttr;
      list.count = 4;       
      list.attr = attributes;
     OSStatus status = SecKeychainItemCopyContent(item, NULL, &list, &length, (void **)&password);
      if (status != noErr) {            
            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];            
            return nil;         
          }
     NSString *passwordString = nil;
     if (password != NULL) {        
           char passwordBuffer[1024];
            if (length > 1023) {                
                 length = 1023;             
                }           
            strncpy(passwordBuffer, password, length);
            passwordBuffer[length] = '\0';          
            passwordString = [NSString stringWithCString:passwordBuffer];           
          }
      SecKeychainItemFreeContent(&list, password);
      CFRelease(item);
      return passwordString;        
    }

+ (void) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error {  

      if (!username || !password || !serviceName) {         
            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];         
            return;         
          }
      OSStatus status = noErr;
      SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
      if (*error && [*error code] != noErr) {           
            return;         
          }
      *error = nil;     

      if (item) {           
            status = SecKeychainItemModifyAttributesAndData(item,NULL,strlen([password UTF8String]),[password UTF8String]);
            CFRelease(item);            
          }     
      else {            
            status = SecKeychainAddGenericPassword(NULL,strlen([serviceName UTF8String]),[serviceName UTF8String],strlen([username UTF8String]),[username UTF8String],strlen([password UTF8String]),[password UTF8String],NULL);

          }
      if (status != noErr) {            
            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];            
          }     
    }

+ (void) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {    
      if (!username || !serviceName) {          
            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: 2000 userInfo: nil];          
            return;        
          }

      *error = nil;

      SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
      if (*error && [*error code] != noErr) {        
          return;           
          }

      OSStatus status;    
      if (item) {        
           status = SecKeychainItemDelete(item);            
            CFRelease(item);        
          }

      if (status != noErr) {            
            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];            
          }     
    }

+ (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {    
      if (!username || !serviceName) {          
            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];        
            return nil;         
          }

      *error = nil;    
      SecKeychainItemRef item;   
      OSStatus status = SecKeychainFindGenericPassword(NULL,strlen([serviceName UTF8String]),[serviceName UTF8String],strlen([username UTF8String]),[username UTF8String], NULL,NULL,&item);

      if (status != noErr) {        
            if (status != errSecItemNotFound) {             
                  *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];              
                }        
            return nil;         
          }
          return item;    
    }

#else

+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {

      if (!username || !serviceName) {          
            if (error != nil) {             
                  *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];           
                }           
            return nil;        
          }      

      if (error != nil) {           
            *error = nil;           
          }   
      // Set up a query dictionary with the base query attributes: item type (generic), username, and service    
     NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, nil];  
     NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, nil];    
     NSMutableDictionary *query = [[NSMutableDictionary alloc] initWithObjects: objects forKeys: keys];    
      // First do a query for attributes, in case we already have a Keychain item with no password data set.    
      // One likely way such an incorrect item could have come about is due to the previous (incorrect)    
      // version of this code (which set the password as a generic attribute instead of password data).   
      NSMutableDictionary *attributeQuery = [query mutableCopy];   
      [attributeQuery setObject: (id) kCFBooleanTrue forKey:(__bridge_transfer id) kSecReturnAttributes];       
       CFTypeRef attrResult = NULL;  
      OSStatus status = SecItemCopyMatching((__bridge_retained CFDictionaryRef) attributeQuery, &attrResult);       
      //NSDictionary *attributeResult = (__bridge_transfer NSDictionary *)attrResult;   
      if (status != noErr) {        
            // No existing item found--simply return nil for the password           
           if (error != nil && status != errSecItemNotFound) {              
                  //Only return an error if a real exception happened--not simply for "not found."           
                  *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];              
                }    
            return nil;         
          }

     // We have an existing item, now query for the password data associated with it.    
      NSMutableDictionary *passwordQuery = [query mutableCopy];    
      [passwordQuery setObject: (id) kCFBooleanTrue forKey: (__bridge_transfer id) kSecReturnData];     
        CFTypeRef resData = NULL;       
      status = SecItemCopyMatching((__bridge_retained CFDictionaryRef) passwordQuery, (CFTypeRef *) &resData);      
      NSData *resultData = (__bridge_transfer NSData *)resData;    
      if (status != noErr) {       
            if (status == errSecItemNotFound) {            
                  // We found attributes for the item previously, but no password now, so return a special error.               
                  // Users of this API will probably want to detect this error and prompt the user to               
                  // re-enter their credentials.  When you attempt to store the re-entered credentials              
                  // using storeUsername:andPassword:forServiceName:updateExisting:error           
                  // the old, incorrect entry will be deleted and a new one with a properly encrypted               
                  // password will be added.

                  if (error != nil) {                   
                        *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];                 
                      }             
                }       
            else {              
                 // Something else went wrong. Simply return the normal Keychain API error code.            
                  if (error != nil) {                   
                        *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];                    
                     }              
                }           
           return nil;          
          }   
      NSString *password = nil;     
     if (resultData) {          
            password = [[NSString alloc] initWithData: resultData encoding: NSUTF8StringEncoding];          
          } 
      else {        
            // There is an existing item, but we weren't able to get password data for it for some reason,          
            // Possibly as a result of an item being incorrectly entered by the previous code.          
            // Set the -1999 error so the code above us can prompt the user again.

           if (error != nil) {      
                  *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];               
                }           
          }     
      return password;      
    }

+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error 

{           
     if (!username || !password || !serviceName)

          {            
                if (error != nil)                   
                    {                       
                         *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];                    
                 }              
                return NO;          
              }

    // See if we already have a password entered for these credentials.

      NSError *getError = nil;      
      NSString *existingPassword = [SFHFKeychainUtils getPasswordForUsername: username andServiceName: serviceName error:&getError];        

     if ([getError code] == -1999)     
          {            
                // There is an existing entry without a password properly stored (possibly as a result of the previous incorrect version of this code.

                // Delete the existing item before moving on entering a correct one.            
               getError = nil;          

                [self deleteItemForUsername: username andServiceName: serviceName error: &getError];            

                if ([getError code] != noErr)                 
                   {                   
                          if (error != nil)                         
                              {                            
                                    *error = getError;                           
                                 }                      
                          return NO;                        
                       }            
              }     
    else if ([getError code] != noErr)      
          {           
                if (error != nil)                   
                    {                       
                          *error = getError;                    
                        }               
                return NO;          
              }     
     if (error != nil)          
          {          
                *error = nil;               
              }

      OSStatus status = noErr;

      if (existingPassword)         
          {

                // We have an existing, properly entered item with a password.        
                // Update the existing item.            

               if (![existingPassword isEqualToString:password] && updateExisting)                
                    {                       
                          //Only update if we're allowed to update existing.  If not, simply do nothing.

                          NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass,kSecAttrService,kSecAttrLabel,kSecAttrAccount,nil];

                          NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword,serviceName,serviceName,username,nil];

                          NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];      

                          status = SecItemUpdate((__bridge_retained CFDictionaryRef) query, (__bridge_retained CFDictionaryRef) [NSDictionary dictionaryWithObject: [password dataUsingEncoding: NSUTF8StringEncoding] forKey: (__bridge_transfer NSString *) kSecValueData]);                      
                        }               
              }     
      else      
          {             
               // No existing entry (or an existing, improperly entered, and therefore now

                // deleted, entry).  Create a new entry.


                NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass,kSecAttrService,kSecAttrLabel,kSecAttrAccount,kSecValueData,nil];

                NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword,serviceName,serviceName,username,[password dataUsingEncoding: NSUTF8StringEncoding],nil];

                NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];            

                status = SecItemAdd((__bridge_retained CFDictionaryRef) query, NULL);               
              }     
      if (error != nil && status != noErr)          
          {            
                // Something went wrong with adding the new item. Return the Keychain error code.               
                *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];                
                return NO;            
              }     
      return YES;       
    }

+ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error    
{       
      if (!username || !serviceName)        
          {            
                if (error != nil)                   
                   {                        
                          *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];                       
                        }            
                return NO;          
              }     
      if (error != nil)       
          {             
               *error = nil;            
              }     
      NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, kSecReturnAttributes, nil];     
      NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, kCFBooleanTrue, nil];        
      NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];      
      OSStatus status = SecItemDelete((__bridge_retained CFDictionaryRef) query);       

      if (error != nil && status != noErr)        
          {           
                *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];            
                return NO;           
              }    
      return YES;    
    }
#endif
@end

关于iphone - SFHFKeychainUtils。 iOS 钥匙串(keychain)。弧兼容,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7663443/

有关iphone - SFHFKeychainUtils。 iOS 钥匙串(keychain)。弧兼容的更多相关文章

  1. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  2. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  3. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  4. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  5. ruby - 为什么不能使用类IO的实例方法noecho? - 2

    print"Enteryourpassword:"pass=STDIN.noecho(&:gets)puts"Yourpasswordis#{pass}!"输出:Enteryourpassword:input.rb:2:in`':undefinedmethod`noecho'for#>(NoMethodError) 最佳答案 一开始require'io/console'后来的Ruby1.9.3 关于ruby-为什么不能使用类IO的实例方法noecho?,我们在StackOverflow上

  6. ruby-on-rails - 不兼容的库版本 : nokogiri. bundle 需要 8.0.0 或更高版本,但 libiconv.2.dylib 提供 7.0.0 版本 - 2

    为了在我的mac上为一个rails项目安装mysql,我遵循了安装Homebrew软件和删除mac端口的在线建议。这是问题开始的地方。rails项目不会构建,我得到这个:[rake--prereqs]rakeaborted!dlopen(/Users/Parker/.rvm/gems/ruby-1.9.3-p448/gems/nokogiri-1.6.0/lib/nokogiri/nokogiri.bundle,9):Librarynotloaded:/opt/local/lib/libiconv.2.dylibReferencedfrom:/Users/Parker/.rvm/gem

  7. ruby - 为 IO::popen 拯救 "command not found" - 2

    当我将IO::popen与不存在的命令一起使用时,我在屏幕上打印了一条错误消息:irb>IO.popen"fakefake"#=>#irb>(irb):1:commandnotfound:fakefake有什么方法可以捕获此错误,以便我可以在脚本中进行检查? 最佳答案 是:升级到ruby​​1.9。如果您在1.9中运行它,则会引发Errno::ENOENT,您将能够拯救它。(编辑)这是在1.8中的一种hackish方式:error=IO.pipe$stderr.reopenerror[1]pipe=IO.popen'qwe'#

  8. ruby - 用于 CSS3 跨浏览器兼容性的 SASS 插件? - 2

    是否有一个SASS扩展可以采用SASS样式表,找到中性属性(例如border-radius)并为其输出所有特定于供应商的属性(例如-webkit-border-radius等)自动?我真的不想手动创建所有混入,也不想手动编写代码。我确定一定有这样的扩展名,但我找不到它。帮忙? 最佳答案 有一个非常好的gem可以满足您的需求。它叫做Bourbon它不会用特定于供应商的css替换您的css,因为它可以像SASS一样工作。它基本上是一个正确生成跨浏览器css的mixin集合。 关于ruby-用

  9. ruby - 我在哪里可以找到与其兼容的 Ruby 版本的 gems 版本号? - 2

    这个问题说明了一切。例如,我有一台安装了ruby​​1.8.6的服务器。当我尝试sudogeminstallroo时,它给出了错误nokogirirequiresRubyversion>=1.8.7。所以,我想安装与Ruby1.8.6兼容的旧版本roo。但我不知道去哪里搜索。我知道RubyForge,但它也没有说明Ruby的兼容版本。 最佳答案 蛮力方法是获取一个git克隆,搜索它指定的Ruby版本的位置,然后使用gitblame甚至gitpickaxe来确定最后一个没有的版本'没有那个要求。

  10. ruby - IO::EAGAINWaitReadable:资源暂时不可用 - 读取会阻塞 - 2

    当我尝试使用“套接字”库中的方法“read_nonblock”时出现以下错误IO::EAGAINWaitReadable:Resourcetemporarilyunavailable-readwouldblock但是当我通过终端上的IRB尝试时它工作正常如何让它读取缓冲区? 最佳答案 IgetthefollowingerrorwhenItrytousethemethod"read_nonblock"fromthe"socket"library当缓冲区中的数据未准备好时,这是预期的行为。由于异常IO::EAGAINWaitReadab

随机推荐