“함정 밟기” 경험 공유: Swift 언어 구현 실습

저자 | 루 타오, 옌홍

소개 

Swift는 iOS/macOS 애플리케이션 개발에 적합한 서버측 프로그래밍 언어입니다. Apple이 2014년 Swift 언어를 출시한 이후 Swift5는 ABI 안정성, 모듈 안정성 및 라이브러리 진화를 달성했으며 Objective-C(이하 "OC")와 비교하여 Swift는 개발 효율성, 보안, 컴파일 최적화, 실행 성능 및 메모리 관리에는 상당한 이점이 있습니다. (공식 블로그: https://www.swift.org/about/ )

바이두 앱은 이미 엔지니어링과 환경 측면에서 Swift 개발을 지원하고 있습니다. 바이두 서치의 프론트엔드 팀은 검색 서비스의 안정적인 구현을 담당하고 있습니다. 우리는 Swift의 적용을 적극적으로 모색하고 있으며, 개발 효율성과 유연성을 크게 향상시키고, 최종 사용자의 검색 경험. 그러나 구현 과정에서 코드가 오래되어 Swift를 지원하지 않거나, 직원이 Swift에 능숙하지 않고 인식하지 못하며, 협력자가 Swift를 충분히 지원하지 않는 등 다양한 문제에 직면할 수 있습니다.

다른 언어의 경우 Swift는 상대적으로 젊기 때문에 독자들이 Swift를 사용하여 보다 원활하게 프로그래밍하고 연구 및 개발 효율성을 향상시킬 수 있도록 돕기 위해 실습 중에 몇 가지 일반적인 문제와 해결 방법을 정리했습니다.

전문은 6947단어이며, 예상 읽는 시간은 18분이다.

01 Swift 적용 시나리오

Swift 도입 여부를 결정하기 전에 해당 시나리오가 적합한지 여부를 판단해야 합니다. 일반적인 상황에서 Swift는 OC를 사용할 수 있는 모든 시나리오에 적합하지만 다음과 같이 직접 교체에 적합하지 않고 주의가 필요한 일부 시나리오도 있습니다.

1. 런타임에 자주 작동하는 속성과 메서드인 OC의 역학을 포함합니다.

2. 핵심 기본 기능, 문제 발생 시 더 큰 영향을 미치는 논리.

3. C++를 호출합니다(현재 Swift는 C++를 직접 호출할 수 없습니다).

4. Swift 구성 요소를 지원하지 않는 클래스를 상속합니다.

또한 오랫동안 OC를 사용해 온 프로젝트의 경우 Swift를 사용하기 전에 다음 사항에도 주의해야 합니다.

1. 엔지니어링 환경 및 개별 모듈에서 Swift를 지원할 수 있습니다.

2. 모듈이 많은 프로젝트의 경우 내부 및 외부 OC와 Swift를 혼합할 수 있습니다.

3. Swift Waring으로 인해 발생할 수 있는 문제를 방지하려면 SWIFT_TREAT_WARNINGS_AS_ERRORS를 YES로 설정하면 경고가 오류로 처리되어 프로그래머가 코드를 더 잘 표준화하는 데 도움이 됩니다.

4. 모듈이 수정된 후에는 우산 헤더의 공용 헤더 파일을 유지하는 데 주의를 기울여야 합니다.

참고: 이 문서의 "구성 요소"는 프로젝트의 "대상"을 나타냅니다.

02 Swift의 기본 사용법

2.1 Swift 문자열을 사용하기 어려운 이유는 무엇입니까?

예: 문자열은 인덱스로 문자를 검색할 수 없습니다.

  • 이유: Swift는 문자열이 문자소 클러스터 로 구성되어 있다고 믿습니다 . 문자소 클러스터의 크기는 고정되어 있지 않으므로 정수로 색인화할 수 없습니다(문자소 클러스터는 실제로 Swift의 Character 클래스입니다).

  • 해결 방법: 아래 첨자로 문자를 가져오려면 아래 첨자에 있는 String에 확장을 추가하여 Int 인덱스를 전달하고 이를 아래 첨자에 있는 String.index로 변환하여 해당 문자를 가져오는 방법을 구현할 수 있습니다.

2.2 try try?try!의 차이점

파일 작업을 수행할 때 try, try? 및 try!를 사용해야 하는 상황이 발생할 수 있습니다. 예외 처리가 다릅니다.

1. try를 사용할 때 예외가 발생하면 프로그램은 예외 처리 프로세스로 들어가는데, 이 예외를 catch 문 블록에서 처리할 수 있습니다.

2. try?를 사용할 때 예외가 발생하면 예외 처리 프로세스로 진입하지 않고 선택적 값 유형을 반환합니다. 즉, 예외가 발생하면 nil을 반환합니다.

3. try!를 사용할 때 예외가 계속 전파되는 것을 허용하지 않습니다. 예외가 발생하면 프로그램 실행이 즉시 중지됩니다.

따라서 파일 작업 시 필요에 따라 적절한 예외 처리 방법을 선택할 수 있습니다. 일반적으로 Baidu 앱에서는 try?를 사용하는 것이 좋습니다.

2.3 공개와 공개의 차이점

Swift 언어에서 public과 open은 모두 모듈에서 외부에 노출되어야 하는 함수를 선언하는 데 사용되는 키워드이지만 상속과 노출 정도가 다릅니다.

1. public 키워드로 수정된 클래스는 모듈 외부에서 상속될 수 없습니다. 즉, 다른 모듈이 이 클래스를 상속하려고 하면 컴파일러가 오류를 보고합니다. 이러한 제한은 클래스의 무결성을 보호하지만 다른 모듈에서의 재사용성을 제한할 수도 있습니다.

2. open 키워드는 임의 상속을 허용합니다. open 키워드로 클래스를 수정하면 다른 모듈의 클래스도 이 클래스를 아무런 제한 없이 자유롭게 상속받을 수 있습니다. 이러한 수준의 공개는 open 키워드로 수정된 클래스의 재사용성과 확장성을 모듈 간 더욱 유연하게 만듭니다.

공개 정도에 있어서는 public이 open보다 제한이 더 엄격하므로 public < open, 즉 public이 open보다 공개 정도가 낮다고 할 수 있다.

2.4 JSON 파싱

Swift에서 JSON을 파싱할 때, JSON을 직접 Dictionary로 변환한다면 타입 판단, 변환 등의 연산이 필요하고, 코드도 비교적 복잡합니다. 현재 타사 라이브러리 SwiftyJSON, ObjectMapper 또는 시스템 라이브러리 JSONEncoder를 사용하여 작업을 단순화하고 개발 효율성을 향상시킬 수 있습니다.

2.5 UIView 하위 클래스에 init를 추가해야 하는 이유(코더 디코더: NSCoder)

1. 이는 NSCoding 프로토콜에 의해 정의되며 NSCoding 프로토콜을 준수하는 모든 클래스는 상속되어야 합니다. 단지 암시적 상속이 수행되는 경우도 있고 명시적인 구현이 필요한 경우도 있습니다.

2. 하위 클래스에서 지정된 초기화를 정의할 때(상위 클래스 지정 초기화의 사용자 정의 및 재정의 포함) 필수 init?(코더 aDecoder: NSCoder)를 명시적으로 구현해야 하지만 다른 경우에는 암시적으로 상속되므로 무시할 수 있습니다. 그것.

3. 스토리보드를 사용하여 인터페이스를 구현하면 프로그램은 이 초기화 프로그램을 호출합니다.

4. fatalError 제거에 주의하세요 FatalError 는 무조건 실행을 중지하고 인쇄한다는 의미입니다.

2.6 Swift 클래스 및 서브클래스 초기화

Swift의 클래스 및 하위 클래스 초기화에는 두 가지 주요 단계가 포함됩니다. 먼저 모든 저장 속성에 초기값이 할당되었는지 확인한 후 인스턴스를 사용할 준비가 되기 전에 저장 속성의 값을 사용자 정의할 수 있습니다. 이 두 단계의 성공을 보장하기 위해 아래에 설명된 대로 4단계 보안 검사가 구현됩니다.

1. 이 클래스의 모든 저장 속성 할당을 완료한 후 지정된 생성자는 상위 클래스의 생성자로 상향 프록시될 수 있습니다.

2. 상속된 속성에 대한 새 값을 설정하기 전에 지정된 생성자는 상위 클래스 생성자를 프록시 위로 호출해야 합니다.

3. 편의 초기화는 모든 속성(동일한 클래스에 정의된 모든 속성 포함)에 새 값을 할당하기 전에 먼저 다른 생성자를 호출해야 합니다.

4. 생성의 첫 번째 단계가 완료되기 전에 생성자는 인스턴스 메서드를 호출할 수 없고 인스턴스 속성 값을 읽을 수 없으며 self를 값으로 참조할 수 없습니다.

간단히 말해서, 클래스 초기화 중에 완료해야 하는 작업 중 하나는 모든 저장 속성에 초기 값을 갖도록 만드는 것입니다(선택 사항 제외). 부모 클래스에 지정된 초기화가 있는 경우 하위 클래스도 지정된 초기화가 있어야 하며 부모 클래스의 지정된 초기화 중 하나를 호출하고(초기화해야 하는 경우 오버로드됨) 2단계 초기화 규칙을 따라야 합니다. . 편의 초기화는 동일한 클래스(다른 편의 초기화 또는 지정된 초기화)의 초기화 메서드를 호출해야 하지만 결국에는 지정된 초기화가 호출됩니다. 간편 초기화는 2단계 초기화 규칙을 따르지 않으며 하위 클래스에서 호출하거나 오버로드할 수 없습니다.

03 OC와 Swift 간의 상호 호출 및 점프

3.1 구성 요소의 Swift 파일은 공개 OC 헤더 파일을 호출합니다.

  • 공용 OC 헤더 파일(예: xyz.h)을 구성요소(예: ABC) 상위 헤더(예: #import)에 추가합니다.

  • 공개 OC 헤더 파일의 내용은 Swift 파일에서 직접 호출됩니다.

3.2 구성 요소의 Swift 파일은 비공개(비공개) OC 파일을 호출합니다.

컴포넌트는 공개적으로 노출되는 헤더 파일을 최대한 줄여야 하지만 Swift와 OC가 혼합된 경우 필연적으로 OC 비공개 헤더 파일을 사용하므로 다음과 같은 조치를 취할 수 있습니다. 프레임워크에서 비공개 헤더 파일을 비공개 모듈로 선언합니다( modulemap) 구성 요소의 Swift 소스 코드에서 비공개 모듈을 가져오기만 하면 됩니다.

1. Private.modulemap 파일을 생성합니다. NewModule을 컴포넌트 이름으로 하여 NewModule.private.modulemap으로 이름을 지정할 수 있습니다. 내용은 다음과 같습니다. 모듈 뒤에 _Private를 추가합니다.

  • 목록 헤더 파일 형식
framework module NewModule_Private {
  header "xxxxx.h"
}
  • 루트 헤더 파일을 사용하여 NewModule_Private.h 헤더 파일을 추가합니다.
framework module NewModule_Private {
  umbrella header "NewModule_Private.h"

  export *
  module * { export * }
}

2. 구성 요소 빌드 설정에서 MODULEMAP_PRIVATE_FILE 경로를 구성합니다(MODULEMAP_PRIVATE_FILE='NewModule.private.modulemap'). Baidu 앱에서는 NewModule.boxspec에 다음 코드로 경로를 설정합니다.

s.xcconfig = {
    'MODULEMAP_PRIVATE_FILE' => '${BOX_ROOT}/NewModule.private.modulemap'
}

3. 프로젝트 디렉터리에 NewModule.private.modulemap을 추가하고 Baidu 앱의 NewModule.boxspec에 다음 코드로 경로를 설정합니다.

s.refer_files = [
      "NewModule.private.modulemap",
]

4. xxxxx.h를 Private 헤더로 설정하세요 Baidu 앱에서 NewModule.boxspec에 다음 코드를 사용하여 xxxxx.h를 Private 헤더로 설정하세요.

s.private_headers = [
    "Sources/xxxxx.h"
  ]

5. 호출방법

import NewModule_Private
let objectX = xxxxx()
print(objectX)

알아채다:

  • 추가된 Private 헤더 파일은 헤더 파일을 전달할 수 있습니다. 즉, 다른 헤더 파일을 가져오려면 전달된 헤더 파일도 NewModule_Private에 추가해야 하며 가져오기에서는 꺾쇠 괄호를 사용해야 합니다.

  • Private Header도 프레임워크에 노출되므로 외부 구성 요소가 Public Header를 사용하는 데 동의하고 Private Header 사용을 피할 수 있습니다. 비즈니스 개발 및 Swift&OC 혼합으로 Private Header가 불안정하기 때문입니다.

3.3 구성 요소의 OC 파일에서 Swift 파일을 호출하는 방법은 무엇입니까?

  • Swift 클래스는 NSObject를 상속하고, 메소드 앞에 @objc 표시를 추가하고 공개 또는 개방형이어야 합니다.

  • 도입방법#import"

3.4 OC의 전달 선언, 구성 요소가 Swift 파일에서 참조되는 경우 오류가 보고됩니다.

오류: 'xxxProtocol'에 대한 프로토콜 정의를 찾을 수 없습니다.

  • 이유: 이 오류는 OC의 코드 경고입니다. Baidu 앱에서는 기본적으로 Swift의 SWIFT_TREAT_WARNINGS_AS_ERRORS가 YES로 설정되어 OC의 경고가 오류로 간주됩니다.

  • 해결책: 세 가지 중 하나를 선택하십시오.

1. SWIFT_TREAT_WARNINGS_AS_ERRORS를 일시적으로 NO로 설정합니다.

2. xxxProtocol 가져오기 앞으로 선언하지 않음

3. 경고를 무시하려면 pragma를 사용하세요.

3.5 Swift는 OC에서 정의한 매크로를 어떻게 사용합니까?

  • Swift에서는 상수로 정의된 매크로를 직접 사용할 수 있고, 메소드 호출이 있는 매크로를 사용할 수 없으며, 정적 상수를 사용할 수 없습니다.
下面这种定义为常量的宏可以使用
#define APP_LANGUAGE_EN @"en" 
#define kNavigationBarHeight 44.0

下面带有方法调用的宏不可以使用
#define kScreenHeight [[UIScreen mainScreen] bounds].size.height
#define kScreenWidth [[UIScreen mainScreen] bounds].size.width

下面带有静态常量swift不能使用,可以改成宏
static NSString *const StopTabRefreshNotifyNameHtml = @"TabRefreshNotifyNameHtml";

3.6 Swift와 OC Generics 혼합

  • 기본 프레임워크에는 다음과 같은 OC 제네릭을 사용하는 클래스가 있습니다.
@interface BBAXYZ<T> : NSObject <BBAXYZEventProtocol>  
@property (nonatomic, weak) T page;  
@end

이 제네릭을 사용하면 Swift를 사용하여 BBAXYZ의 하위 클래스를 상속하고 개발하는 것이 불가능해집니다. 하지만 이 기본 프레임워크는 비즈니스의 핵심 부분이므로 앞으로는 Swift 개발을 지원해야 합니다.

  • 주의 깊게 관찰하고 분석한 결과, 페이지 속성 유형을 지정하는 데 제네릭이 주로 사용된다는 사실을 발견했습니다. 따라서 제네릭을 제거하고 대신 적절한 유형을 반환하는 메서드를 제공하는 것을 고려할 수 있습니다. 이런 식으로 우리는 Swift에서 이 기본 프레임워크를 성공적으로 상속하고 사용할 수 있습니다. 수정된 코드는 다음과 같습니다.
@interface BBAXYZ : NSObject <BBAXYZEventProtocol>  
- (id<BBAXYZEventProtocol>)page;  
@end

그런 다음 OC 클래스를 생성하여 이 기본 프레임워크를 구현하고 모든 하위 클래스가 이 OC 클래스를 상속하고 페이지 메서드를 구현하여 적절한 유형의 개체를 반환하도록 할 수 있습니다. 이런 식으로 우리는 Swift에서 이 기본 프레임워크를 성공적으로 상속하고 사용할 수 있습니다.

예를 들어:

@interface BBAABC : BBAXYZ  
- (UIViewController<BBAXYZEventProtocol> *)page; 
@end

이러한 수정으로 가벼운 중간 OC 클래스가 추가되기는 하지만 여전히 Swift와 OC의 혼합을 달성하고 Swift에서 새로운 하위 클래스를 개발할 수 있다는 점에 유의해야 합니다. 이러한 접근 방식은 코드 호환성을 보장할 뿐만 아니라 OC의 장점을 계속해서 활용할 수 있게 해줍니다.

  • 용법
class BBAEFG: BBAABC {
    
}

3.7 Swift는 OC 인터페이스를 호출하고 OC의 null 허용 여부 주석을 사용할 때 주의 사항

문제 시나리오:

1. OC 인터페이스는 nonnull로 정의되어 있으며, swfit에서 호출 시 일반적으로 non-ional type으로 사용되는데, 이때 OC 인터페이스가 사양에서 nil을 반환하면 런타임 크래시가 발생하게 된다.

2. OC 인터페이스는 null이 아니거나 nullable을 정의하지 않습니다. 이 경우 컴파일러는 OC 포인터 유형을 암시적으로 확인된 선택적 유형 (예: String!)으로 Swift로 가져옵니다. Swift를 호출할 때 OC 인터페이스가 nil을 반환하면 nil인 선택적 값의 암시적 구문 분석으로 인해 런타임 충돌이 발생합니다.

해결책:

1. Swift가 OC 인터페이스를 호출할 때 OC 인터페이스가 nonnull로 선언되거나 null 허용 여부가 지정되지 않은 경우 OC 인터페이스가 비어 있지 않음이 분명한 경우에만 호출할 수 있습니다 .

2. OC 환경에서는 널이 아닌 포인터에 nil이 할당되어도 문제가 되지 않습니다. 컴파일러는 경고만 생성합니다. 이를 위해서는 프로그래머가 사양에 따라 OC 코드를 작성하고, null 허용 여부 주석을 올바르게 사용하고 , 이전 버전과의 호환성을 지원하기 위해 런타임 null 어설션을 추가해야 합니다.

04 기타 FAQ

4.1 Xcode 컴파일에서는 컴파일 오류만 표시되고 정보는 거의 표시되지 않습니다.

  • 이유: Swift 언어를 사용하여 개발된 구성 요소는 모듈화를 지원하지 않는 구성 요소에 의존하므로 구성 요소는 성공적으로 컴파일되지만 전체 프로젝트가 컴파일되지 않습니다.

  • 해결책: 둘 중 하나를 선택하십시오

1. 빌드 설정 구성과 같은 모든 종속 구성 요소가 변조되었는지 확인하고 확인합니다.

2. 구성 요소에 Swift 파일을 추가합니다(빈 파일도 작동함).

4.2 Library Evolution을 켜는 구성 요소로 인해 발생하는 컴파일 오류

오류 표시: 'xxxxx' 하위 클래스 확장의 @objc' 인스턴스 메서드에는 iOS 13.0.0이 필요합니다.

이는 BUILD_LIBRARY_FOR_DISTRIBUTION 스위치에 의해 제어되는 Library Evolution을 켜는 구성 요소로 인해 발생합니다.

라이브러리는 Library Evolution을 활성화하고 종속성 체인의 라이브러리 다운스트림에서는 다음을 수행합니다.

1. 해당 클래스에 대해 @objc 하위 클래스를 구현합니다.

2. 확장을 사용하여 해당 클래스에 @objc 메서드를 구현합니다(이는 UIKit 프로토콜에서 자주 발생합니다).

3. 해당 클래스에 대한 하위 클래스를 구현하고, @objc 메서드를 추가하고, 상위 클래스 유형을 메서드의 매개 변수로 사용합니다.

이러한 기능은 구현 제한 사항입니다. Swift 런타임에는 일부 해당 변경이 필요한 것으로 추정되므로 Swift 5.1(iOS 13) 런타임에서만 실행할 수 있습니다.

또한 컴파일 중에는 오류가 보고되지 않지만 런타임 중에 충돌이 발생하거나 잘못된 결과가 발생하는 상황이 있습니다.

Library Evolution은 Baidu 앱에서 기본적으로 켜져 있습니다. 구성 요소에 대해 Library Evolution을 끄면 바이너리 비호환성이 발생하며 현재 해결 방법이 없습니다.

4.3 노출된 Private 헤더 파일을 큰따옴표를 사용하여 가져오는 경우 경고가 보고되며 이를 꺾쇠괄호로 변경해야 합니다.

import <xxxx.h>를 사용하면 프로젝트 아래의 다른 대상은 참조되지 않습니다. 예를 들어 Baidu 앱의 디버그 모듈이 이 Private 헤더 파일을 참조하면 <angled> 포함을 찾을 수 없다는 오류가 보고됩니다. 대신 "따옴표"를 사용하세요.

  • 이유: 다른 대상이 기본 모듈을 참조할 때 기본적으로 현재 프로젝트의 헤더 파일을 참조하고 꺾쇠 괄호 메서드는 시스템 라이브러리 또는 사용자 라이브러리를 참조하기 때문입니다.

  • 해결 방법: 다른 대상은 큰따옴표와 꺾쇠 괄호를 모두 사용하여 HEADER_SEARCH_PATHS에 개인 헤더를 구성합니다.

05 요약

위 내용은 Swift 개발 중에 직면하는 몇 가지 일반적인 문제와 해당 솔루션입니다. 그러나 Swift 개발의 광대한 바다를 더 깊이 파고들수록 점점 더 독특한 문제가 나타날 것입니다. 우리는 대다수 개발자에게 귀중한 참고 자료를 제공하기 위해 이러한 새로운 문제와 해당 솔루션을 계속해서 편집하고 게시할 것입니다. 누구나 토론을 위해 메시지를 남길 수 있습니다.

바이두서치 프론트엔드팀에서는 iOS/Android/웹 프론트엔드 R&D 엔지니어를 계속 모집하고 있습니다.

이력서는 [email protected]으로 제출해 주시기 바랍니다.

--끝--

추천도서

모바일 안티 스크린샷 및 화면 녹화 기술이 Baidu 계정 시스템에 구현되었습니다.

AI 네이티브 엔지니어링: 바이두 앱 AI 인터랙티브 기술 실습

이벤트 루프의 미스터리 밝혀내기

Baidu 검색 디스플레이 서비스 재구성: 진행 및 최적화

Baidu APP iOS 패키지 크기 50M 최적화 실습 (7) 컴파일러 최적화

Broadcom은 기존 VMware 파트너 프로그램 deepin-IDE 버전 업데이트를 종료하고 새로운 모습을 발표했습니다. WAVE SUMMIT가 10번째 에디션을 축하합니다. Wen Xinyiyan이 최신 정보를 공개합니다! Zhou Hongyi: Hongmeng 네이티브는 반드시 성공할 것입니다. GTA 5의 전체 소스 코드가 공개되었습니다. Linus: 크리스마스 이브에는 코드를 읽지 않을 것입니다. Java 도구 세트 Hutool-5.8.24 의 새 버전을 출시할 것입니다. 내년에 퓨리온에 대해 함께 불평하자 상업적 탐색: 보트 통과 Wan Zhongshan, v4.9.1.15 Apple, 오픈 소스 다중 모달 대형 언어 모델 출시 Ferret Yakult Company는 95G 데이터가 유출되었음을 확인했습니다
{{o.이름}}
{{이름}}

추천

출처my.oschina.net/u/4939618/blog/10443406